/*
Unobtrusive JavaScript
https://github.com/rails/rails/blob/master/actionview/app/assets/javascripts
Released under the MIT license
 */
;

(function() {
  var context = this;

  (function() {
    (function() {
      this.Rails = {
        linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]',
        buttonClickSelector: {
          selector: 'button[data-remote]:not([form]), button[data-confirm]:not([form])',
          exclude: 'form button'
        },
        inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',
        formSubmitSelector: 'form',
        formInputClickSelector: 'form input[type=submit], form input[type=image], form button[type=submit], form button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])',
        formDisableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled',
        formEnableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled',
        fileInputSelector: 'input[name][type=file]:not([disabled])',
        linkDisableSelector: 'a[data-disable-with], a[data-disable]',
        buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]'
      };

    }).call(this);
  }).call(context);

  var Rails = context.Rails;

  (function() {
    (function() {
      var nonce;

      nonce = null;

      Rails.loadCSPNonce = function() {
        var ref;
        return nonce = (ref = document.querySelector("meta[name=csp-nonce]")) != null ? ref.content : void 0;
      };

      Rails.cspNonce = function() {
        return nonce != null ? nonce : Rails.loadCSPNonce();
      };

    }).call(this);
    (function() {
      var expando, m;

      m = Element.prototype.matches || Element.prototype.matchesSelector || Element.prototype.mozMatchesSelector || Element.prototype.msMatchesSelector || Element.prototype.oMatchesSelector || Element.prototype.webkitMatchesSelector;

      Rails.matches = function(element, selector) {
        if (selector.exclude != null) {
          return m.call(element, selector.selector) && !m.call(element, selector.exclude);
        } else {
          return m.call(element, selector);
        }
      };

      expando = '_ujsData';

      Rails.getData = function(element, key) {
        var ref;
        return (ref = element[expando]) != null ? ref[key] : void 0;
      };

      Rails.setData = function(element, key, value) {
        if (element[expando] == null) {
          element[expando] = {};
        }
        return element[expando][key] = value;
      };

      Rails.$ = function(selector) {
        return Array.prototype.slice.call(document.querySelectorAll(selector));
      };

    }).call(this);
    (function() {
      var $, csrfParam, csrfToken;

      $ = Rails.$;

      csrfToken = Rails.csrfToken = function() {
        var meta;
        meta = document.querySelector('meta[name=csrf-token]');
        return meta && meta.content;
      };

      csrfParam = Rails.csrfParam = function() {
        var meta;
        meta = document.querySelector('meta[name=csrf-param]');
        return meta && meta.content;
      };

      Rails.CSRFProtection = function(xhr) {
        var token;
        token = csrfToken();
        if (token != null) {
          return xhr.setRequestHeader('X-CSRF-Token', token);
        }
      };

      Rails.refreshCSRFTokens = function() {
        var param, token;
        token = csrfToken();
        param = csrfParam();
        if ((token != null) && (param != null)) {
          return $('form input[name="' + param + '"]').forEach(function(input) {
            return input.value = token;
          });
        }
      };

    }).call(this);
    (function() {
      var CustomEvent, fire, matches, preventDefault;

      matches = Rails.matches;

      CustomEvent = window.CustomEvent;

      if (typeof CustomEvent !== 'function') {
        CustomEvent = function(event, params) {
          var evt;
          evt = document.createEvent('CustomEvent');
          evt.initCustomEvent(event, params.bubbles, params.cancelable, params.detail);
          return evt;
        };
        CustomEvent.prototype = window.Event.prototype;
        preventDefault = CustomEvent.prototype.preventDefault;
        CustomEvent.prototype.preventDefault = function() {
          var result;
          result = preventDefault.call(this);
          if (this.cancelable && !this.defaultPrevented) {
            Object.defineProperty(this, 'defaultPrevented', {
              get: function() {
                return true;
              }
            });
          }
          return result;
        };
      }

      fire = Rails.fire = function(obj, name, data) {
        var event;
        event = new CustomEvent(name, {
          bubbles: true,
          cancelable: true,
          detail: data
        });
        obj.dispatchEvent(event);
        return !event.defaultPrevented;
      };

      Rails.stopEverything = function(e) {
        fire(e.target, 'ujs:everythingStopped');
        e.preventDefault();
        e.stopPropagation();
        return e.stopImmediatePropagation();
      };

      Rails.delegate = function(element, selector, eventType, handler) {
        return element.addEventListener(eventType, function(e) {
          var target;
          target = e.target;
          while (!(!(target instanceof Element) || matches(target, selector))) {
            target = target.parentNode;
          }
          if (target instanceof Element && handler.call(target, e) === false) {
            e.preventDefault();
            return e.stopPropagation();
          }
        });
      };

    }).call(this);
    (function() {
      var AcceptHeaders, CSRFProtection, createXHR, cspNonce, fire, prepareOptions, processResponse;

      cspNonce = Rails.cspNonce, CSRFProtection = Rails.CSRFProtection, fire = Rails.fire;

      AcceptHeaders = {
        '*': '*/*',
        text: 'text/plain',
        html: 'text/html',
        xml: 'application/xml, text/xml',
        json: 'application/json, text/javascript',
        script: 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript'
      };

      Rails.ajax = function(options) {
        var xhr;
        options = prepareOptions(options);
        xhr = createXHR(options, function() {
          var ref, response;
          response = processResponse((ref = xhr.response) != null ? ref : xhr.responseText, xhr.getResponseHeader('Content-Type'));
          if (Math.floor(xhr.status / 100) === 2) {
            if (typeof options.success === "function") {
              options.success(response, xhr.statusText, xhr);
            }
          } else {
            if (typeof options.error === "function") {
              options.error(response, xhr.statusText, xhr);
            }
          }
          return typeof options.complete === "function" ? options.complete(xhr, xhr.statusText) : void 0;
        });
        if ((options.beforeSend != null) && !options.beforeSend(xhr, options)) {
          return false;
        }
        if (xhr.readyState === XMLHttpRequest.OPENED) {
          return xhr.send(options.data);
        }
      };

      prepareOptions = function(options) {
        options.url = options.url || location.href;
        options.type = options.type.toUpperCase();
        if (options.type === 'GET' && options.data) {
          if (options.url.indexOf('?') < 0) {
            options.url += '?' + options.data;
          } else {
            options.url += '&' + options.data;
          }
        }
        if (AcceptHeaders[options.dataType] == null) {
          options.dataType = '*';
        }
        options.accept = AcceptHeaders[options.dataType];
        if (options.dataType !== '*') {
          options.accept += ', */*; q=0.01';
        }
        return options;
      };

      createXHR = function(options, done) {
        var xhr;
        xhr = new XMLHttpRequest();
        xhr.open(options.type, options.url, true);
        xhr.setRequestHeader('Accept', options.accept);
        if (typeof options.data === 'string') {
          xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded; charset=UTF-8');
        }
        if (!options.crossDomain) {
          xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
          CSRFProtection(xhr);
        }
        xhr.withCredentials = !!options.withCredentials;
        xhr.onreadystatechange = function() {
          if (xhr.readyState === XMLHttpRequest.DONE) {
            return done(xhr);
          }
        };
        return xhr;
      };

      processResponse = function(response, type) {
        var parser, script;
        if (typeof response === 'string' && typeof type === 'string') {
          if (type.match(/\bjson\b/)) {
            try {
              response = JSON.parse(response);
            } catch (error) {}
          } else if (type.match(/\b(?:java|ecma)script\b/)) {
            script = document.createElement('script');
            script.setAttribute('nonce', cspNonce());
            script.text = response;
            document.head.appendChild(script).parentNode.removeChild(script);
          } else if (type.match(/\b(xml|html|svg)\b/)) {
            parser = new DOMParser();
            type = type.replace(/;.+/, '');
            try {
              response = parser.parseFromString(response, type);
            } catch (error) {}
          }
        }
        return response;
      };

      Rails.href = function(element) {
        return element.href;
      };

      Rails.isCrossDomain = function(url) {
        var e, originAnchor, urlAnchor;
        originAnchor = document.createElement('a');
        originAnchor.href = location.href;
        urlAnchor = document.createElement('a');
        try {
          urlAnchor.href = url;
          return !(((!urlAnchor.protocol || urlAnchor.protocol === ':') && !urlAnchor.host) || (originAnchor.protocol + '//' + originAnchor.host === urlAnchor.protocol + '//' + urlAnchor.host));
        } catch (error) {
          e = error;
          return true;
        }
      };

    }).call(this);
    (function() {
      var matches, toArray;

      matches = Rails.matches;

      toArray = function(e) {
        return Array.prototype.slice.call(e);
      };

      Rails.serializeElement = function(element, additionalParam) {
        var inputs, params;
        inputs = [element];
        if (matches(element, 'form')) {
          inputs = toArray(element.elements);
        }
        params = [];
        inputs.forEach(function(input) {
          if (!input.name || input.disabled) {
            return;
          }
          if (matches(input, 'select')) {
            return toArray(input.options).forEach(function(option) {
              if (option.selected) {
                return params.push({
                  name: input.name,
                  value: option.value
                });
              }
            });
          } else if (input.checked || ['radio', 'checkbox', 'submit'].indexOf(input.type) === -1) {
            return params.push({
              name: input.name,
              value: input.value
            });
          }
        });
        if (additionalParam) {
          params.push(additionalParam);
        }
        return params.map(function(param) {
          if (param.name != null) {
            return (encodeURIComponent(param.name)) + "=" + (encodeURIComponent(param.value));
          } else {
            return param;
          }
        }).join('&');
      };

      Rails.formElements = function(form, selector) {
        if (matches(form, 'form')) {
          return toArray(form.elements).filter(function(el) {
            return matches(el, selector);
          });
        } else {
          return toArray(form.querySelectorAll(selector));
        }
      };

    }).call(this);
    (function() {
      var allowAction, fire, stopEverything;

      fire = Rails.fire, stopEverything = Rails.stopEverything;

      Rails.handleConfirm = function(e) {
        if (!allowAction(this)) {
          return stopEverything(e);
        }
      };

      allowAction = function(element) {
        var answer, callback, message;
        message = element.getAttribute('data-confirm');
        if (!message) {
          return true;
        }
        answer = false;
        if (fire(element, 'confirm')) {
          try {
            answer = confirm(message);
          } catch (error) {}
          callback = fire(element, 'confirm:complete', [answer]);
        }
        return answer && callback;
      };

    }).call(this);
    (function() {
      var disableFormElement, disableFormElements, disableLinkElement, enableFormElement, enableFormElements, enableLinkElement, formElements, getData, matches, setData, stopEverything;

      matches = Rails.matches, getData = Rails.getData, setData = Rails.setData, stopEverything = Rails.stopEverything, formElements = Rails.formElements;

      Rails.handleDisabledElement = function(e) {
        var element;
        element = this;
        if (element.disabled) {
          return stopEverything(e);
        }
      };

      Rails.enableElement = function(e) {
        var element;
        element = e instanceof Event ? e.target : e;
        if (matches(element, Rails.linkDisableSelector)) {
          return enableLinkElement(element);
        } else if (matches(element, Rails.buttonDisableSelector) || matches(element, Rails.formEnableSelector)) {
          return enableFormElement(element);
        } else if (matches(element, Rails.formSubmitSelector)) {
          return enableFormElements(element);
        }
      };

      Rails.disableElement = function(e) {
        var element;
        element = e instanceof Event ? e.target : e;
        if (matches(element, Rails.linkDisableSelector)) {
          return disableLinkElement(element);
        } else if (matches(element, Rails.buttonDisableSelector) || matches(element, Rails.formDisableSelector)) {
          return disableFormElement(element);
        } else if (matches(element, Rails.formSubmitSelector)) {
          return disableFormElements(element);
        }
      };

      disableLinkElement = function(element) {
        var replacement;
        replacement = element.getAttribute('data-disable-with');
        if (replacement != null) {
          setData(element, 'ujs:enable-with', element.innerHTML);
          element.innerHTML = replacement;
        }
        element.addEventListener('click', stopEverything);
        return setData(element, 'ujs:disabled', true);
      };

      enableLinkElement = function(element) {
        var originalText;
        originalText = getData(element, 'ujs:enable-with');
        if (originalText != null) {
          element.innerHTML = originalText;
          setData(element, 'ujs:enable-with', null);
        }
        element.removeEventListener('click', stopEverything);
        return setData(element, 'ujs:disabled', null);
      };

      disableFormElements = function(form) {
        return formElements(form, Rails.formDisableSelector).forEach(disableFormElement);
      };

      disableFormElement = function(element) {
        var replacement;
        replacement = element.getAttribute('data-disable-with');
        if (replacement != null) {
          if (matches(element, 'button')) {
            setData(element, 'ujs:enable-with', element.innerHTML);
            element.innerHTML = replacement;
          } else {
            setData(element, 'ujs:enable-with', element.value);
            element.value = replacement;
          }
        }
        element.disabled = true;
        return setData(element, 'ujs:disabled', true);
      };

      enableFormElements = function(form) {
        return formElements(form, Rails.formEnableSelector).forEach(enableFormElement);
      };

      enableFormElement = function(element) {
        var originalText;
        originalText = getData(element, 'ujs:enable-with');
        if (originalText != null) {
          if (matches(element, 'button')) {
            element.innerHTML = originalText;
          } else {
            element.value = originalText;
          }
          setData(element, 'ujs:enable-with', null);
        }
        element.disabled = false;
        return setData(element, 'ujs:disabled', null);
      };

    }).call(this);
    (function() {
      var stopEverything;

      stopEverything = Rails.stopEverything;

      Rails.handleMethod = function(e) {
        var csrfParam, csrfToken, form, formContent, href, link, method;
        link = this;
        method = link.getAttribute('data-method');
        if (!method) {
          return;
        }
        href = Rails.href(link);
        csrfToken = Rails.csrfToken();
        csrfParam = Rails.csrfParam();
        form = document.createElement('form');
        formContent = "<input name='_method' value='" + method + "' type='hidden' />";
        if ((csrfParam != null) && (csrfToken != null) && !Rails.isCrossDomain(href)) {
          formContent += "<input name='" + csrfParam + "' value='" + csrfToken + "' type='hidden' />";
        }
        formContent += '<input type="submit" />';
        form.method = 'post';
        form.action = href;
        form.target = link.target;
        form.innerHTML = formContent;
        form.style.display = 'none';
        document.body.appendChild(form);
        form.querySelector('[type="submit"]').click();
        return stopEverything(e);
      };

    }).call(this);
    (function() {
      var ajax, fire, getData, isCrossDomain, isRemote, matches, serializeElement, setData, stopEverything,
        slice = [].slice;

      matches = Rails.matches, getData = Rails.getData, setData = Rails.setData, fire = Rails.fire, stopEverything = Rails.stopEverything, ajax = Rails.ajax, isCrossDomain = Rails.isCrossDomain, serializeElement = Rails.serializeElement;

      isRemote = function(element) {
        var value;
        value = element.getAttribute('data-remote');
        return (value != null) && value !== 'false';
      };

      Rails.handleRemote = function(e) {
        var button, data, dataType, element, method, url, withCredentials;
        element = this;
        if (!isRemote(element)) {
          return true;
        }
        if (!fire(element, 'ajax:before')) {
          fire(element, 'ajax:stopped');
          return false;
        }
        withCredentials = element.getAttribute('data-with-credentials');
        dataType = element.getAttribute('data-type') || 'script';
        if (matches(element, Rails.formSubmitSelector)) {
          button = getData(element, 'ujs:submit-button');
          method = getData(element, 'ujs:submit-button-formmethod') || element.method;
          url = getData(element, 'ujs:submit-button-formaction') || element.getAttribute('action') || location.href;
          if (method.toUpperCase() === 'GET') {
            url = url.replace(/\?.*$/, '');
          }
          if (element.enctype === 'multipart/form-data') {
            data = new FormData(element);
            if (button != null) {
              data.append(button.name, button.value);
            }
          } else {
            data = serializeElement(element, button);
          }
          setData(element, 'ujs:submit-button', null);
          setData(element, 'ujs:submit-button-formmethod', null);
          setData(element, 'ujs:submit-button-formaction', null);
        } else if (matches(element, Rails.buttonClickSelector) || matches(element, Rails.inputChangeSelector)) {
          method = element.getAttribute('data-method');
          url = element.getAttribute('data-url');
          data = serializeElement(element, element.getAttribute('data-params'));
        } else {
          method = element.getAttribute('data-method');
          url = Rails.href(element);
          data = element.getAttribute('data-params');
        }
        ajax({
          type: method || 'GET',
          url: url,
          data: data,
          dataType: dataType,
          beforeSend: function(xhr, options) {
            if (fire(element, 'ajax:beforeSend', [xhr, options])) {
              return fire(element, 'ajax:send', [xhr]);
            } else {
              fire(element, 'ajax:stopped');
              return false;
            }
          },
          success: function() {
            var args;
            args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
            return fire(element, 'ajax:success', args);
          },
          error: function() {
            var args;
            args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
            return fire(element, 'ajax:error', args);
          },
          complete: function() {
            var args;
            args = 1 <= arguments.length ? slice.call(arguments, 0) : [];
            return fire(element, 'ajax:complete', args);
          },
          crossDomain: isCrossDomain(url),
          withCredentials: (withCredentials != null) && withCredentials !== 'false'
        });
        return stopEverything(e);
      };

      Rails.formSubmitButtonClick = function(e) {
        var button, form;
        button = this;
        form = button.form;
        if (!form) {
          return;
        }
        if (button.name) {
          setData(form, 'ujs:submit-button', {
            name: button.name,
            value: button.value
          });
        }
        setData(form, 'ujs:formnovalidate-button', button.formNoValidate);
        setData(form, 'ujs:submit-button-formaction', button.getAttribute('formaction'));
        return setData(form, 'ujs:submit-button-formmethod', button.getAttribute('formmethod'));
      };

      Rails.preventInsignificantClick = function(e) {
        var data, insignificantMetaClick, link, metaClick, method, nonPrimaryMouseClick;
        link = this;
        method = (link.getAttribute('data-method') || 'GET').toUpperCase();
        data = link.getAttribute('data-params');
        metaClick = e.metaKey || e.ctrlKey;
        insignificantMetaClick = metaClick && method === 'GET' && !data;
        nonPrimaryMouseClick = (e.button != null) && e.button !== 0;
        if (nonPrimaryMouseClick || insignificantMetaClick) {
          return e.stopImmediatePropagation();
        }
      };

    }).call(this);
    (function() {
      var $, CSRFProtection, delegate, disableElement, enableElement, fire, formSubmitButtonClick, getData, handleConfirm, handleDisabledElement, handleMethod, handleRemote, loadCSPNonce, preventInsignificantClick, refreshCSRFTokens;

      fire = Rails.fire, delegate = Rails.delegate, getData = Rails.getData, $ = Rails.$, refreshCSRFTokens = Rails.refreshCSRFTokens, CSRFProtection = Rails.CSRFProtection, loadCSPNonce = Rails.loadCSPNonce, enableElement = Rails.enableElement, disableElement = Rails.disableElement, handleDisabledElement = Rails.handleDisabledElement, handleConfirm = Rails.handleConfirm, preventInsignificantClick = Rails.preventInsignificantClick, handleRemote = Rails.handleRemote, formSubmitButtonClick = Rails.formSubmitButtonClick, handleMethod = Rails.handleMethod;

      if ((typeof jQuery !== "undefined" && jQuery !== null) && (jQuery.ajax != null)) {
        if (jQuery.rails) {
          throw new Error('If you load both jquery_ujs and rails-ujs, use rails-ujs only.');
        }
        jQuery.rails = Rails;
        jQuery.ajaxPrefilter(function(options, originalOptions, xhr) {
          if (!options.crossDomain) {
            return CSRFProtection(xhr);
          }
        });
      }

      Rails.start = function() {
        if (window._rails_loaded) {
          throw new Error('rails-ujs has already been loaded!');
        }
        window.addEventListener('pageshow', function() {
          $(Rails.formEnableSelector).forEach(function(el) {
            if (getData(el, 'ujs:disabled')) {
              return enableElement(el);
            }
          });
          return $(Rails.linkDisableSelector).forEach(function(el) {
            if (getData(el, 'ujs:disabled')) {
              return enableElement(el);
            }
          });
        });
        delegate(document, Rails.linkDisableSelector, 'ajax:complete', enableElement);
        delegate(document, Rails.linkDisableSelector, 'ajax:stopped', enableElement);
        delegate(document, Rails.buttonDisableSelector, 'ajax:complete', enableElement);
        delegate(document, Rails.buttonDisableSelector, 'ajax:stopped', enableElement);
        delegate(document, Rails.linkClickSelector, 'click', preventInsignificantClick);
        delegate(document, Rails.linkClickSelector, 'click', handleDisabledElement);
        delegate(document, Rails.linkClickSelector, 'click', handleConfirm);
        delegate(document, Rails.linkClickSelector, 'click', disableElement);
        delegate(document, Rails.linkClickSelector, 'click', handleRemote);
        delegate(document, Rails.linkClickSelector, 'click', handleMethod);
        delegate(document, Rails.buttonClickSelector, 'click', preventInsignificantClick);
        delegate(document, Rails.buttonClickSelector, 'click', handleDisabledElement);
        delegate(document, Rails.buttonClickSelector, 'click', handleConfirm);
        delegate(document, Rails.buttonClickSelector, 'click', disableElement);
        delegate(document, Rails.buttonClickSelector, 'click', handleRemote);
        delegate(document, Rails.inputChangeSelector, 'change', handleDisabledElement);
        delegate(document, Rails.inputChangeSelector, 'change', handleConfirm);
        delegate(document, Rails.inputChangeSelector, 'change', handleRemote);
        delegate(document, Rails.formSubmitSelector, 'submit', handleDisabledElement);
        delegate(document, Rails.formSubmitSelector, 'submit', handleConfirm);
        delegate(document, Rails.formSubmitSelector, 'submit', handleRemote);
        delegate(document, Rails.formSubmitSelector, 'submit', function(e) {
          return setTimeout((function() {
            return disableElement(e);
          }), 13);
        });
        delegate(document, Rails.formSubmitSelector, 'ajax:send', disableElement);
        delegate(document, Rails.formSubmitSelector, 'ajax:complete', enableElement);
        delegate(document, Rails.formInputClickSelector, 'click', preventInsignificantClick);
        delegate(document, Rails.formInputClickSelector, 'click', handleDisabledElement);
        delegate(document, Rails.formInputClickSelector, 'click', handleConfirm);
        delegate(document, Rails.formInputClickSelector, 'click', formSubmitButtonClick);
        document.addEventListener('DOMContentLoaded', refreshCSRFTokens);
        document.addEventListener('DOMContentLoaded', loadCSPNonce);
        return window._rails_loaded = true;
      };

      if (window.Rails === Rails && fire(document, 'rails:attachBindings')) {
        Rails.start();
      }

    }).call(this);
  }).call(this);

  if (typeof module === "object" && module.exports) {
    module.exports = Rails;
  } else if (typeof define === "function" && define.amd) {
    define(Rails);
  }
}).call(this);
(function(global, factory) {
  typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define([ "exports" ], factory) : factory(global.ActiveStorage = {});
})(this, function(exports) {
  "use strict";
  function createCommonjsModule(fn, module) {
    return module = {
      exports: {}
    }, fn(module, module.exports), module.exports;
  }
  var sparkMd5 = createCommonjsModule(function(module, exports) {
    (function(factory) {
      {
        module.exports = factory();
      }
    })(function(undefined) {
      var hex_chr = [ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" ];
      function md5cycle(x, k) {
        var a = x[0], b = x[1], c = x[2], d = x[3];
        a += (b & c | ~b & d) + k[0] - 680876936 | 0;
        a = (a << 7 | a >>> 25) + b | 0;
        d += (a & b | ~a & c) + k[1] - 389564586 | 0;
        d = (d << 12 | d >>> 20) + a | 0;
        c += (d & a | ~d & b) + k[2] + 606105819 | 0;
        c = (c << 17 | c >>> 15) + d | 0;
        b += (c & d | ~c & a) + k[3] - 1044525330 | 0;
        b = (b << 22 | b >>> 10) + c | 0;
        a += (b & c | ~b & d) + k[4] - 176418897 | 0;
        a = (a << 7 | a >>> 25) + b | 0;
        d += (a & b | ~a & c) + k[5] + 1200080426 | 0;
        d = (d << 12 | d >>> 20) + a | 0;
        c += (d & a | ~d & b) + k[6] - 1473231341 | 0;
        c = (c << 17 | c >>> 15) + d | 0;
        b += (c & d | ~c & a) + k[7] - 45705983 | 0;
        b = (b << 22 | b >>> 10) + c | 0;
        a += (b & c | ~b & d) + k[8] + 1770035416 | 0;
        a = (a << 7 | a >>> 25) + b | 0;
        d += (a & b | ~a & c) + k[9] - 1958414417 | 0;
        d = (d << 12 | d >>> 20) + a | 0;
        c += (d & a | ~d & b) + k[10] - 42063 | 0;
        c = (c << 17 | c >>> 15) + d | 0;
        b += (c & d | ~c & a) + k[11] - 1990404162 | 0;
        b = (b << 22 | b >>> 10) + c | 0;
        a += (b & c | ~b & d) + k[12] + 1804603682 | 0;
        a = (a << 7 | a >>> 25) + b | 0;
        d += (a & b | ~a & c) + k[13] - 40341101 | 0;
        d = (d << 12 | d >>> 20) + a | 0;
        c += (d & a | ~d & b) + k[14] - 1502002290 | 0;
        c = (c << 17 | c >>> 15) + d | 0;
        b += (c & d | ~c & a) + k[15] + 1236535329 | 0;
        b = (b << 22 | b >>> 10) + c | 0;
        a += (b & d | c & ~d) + k[1] - 165796510 | 0;
        a = (a << 5 | a >>> 27) + b | 0;
        d += (a & c | b & ~c) + k[6] - 1069501632 | 0;
        d = (d << 9 | d >>> 23) + a | 0;
        c += (d & b | a & ~b) + k[11] + 643717713 | 0;
        c = (c << 14 | c >>> 18) + d | 0;
        b += (c & a | d & ~a) + k[0] - 373897302 | 0;
        b = (b << 20 | b >>> 12) + c | 0;
        a += (b & d | c & ~d) + k[5] - 701558691 | 0;
        a = (a << 5 | a >>> 27) + b | 0;
        d += (a & c | b & ~c) + k[10] + 38016083 | 0;
        d = (d << 9 | d >>> 23) + a | 0;
        c += (d & b | a & ~b) + k[15] - 660478335 | 0;
        c = (c << 14 | c >>> 18) + d | 0;
        b += (c & a | d & ~a) + k[4] - 405537848 | 0;
        b = (b << 20 | b >>> 12) + c | 0;
        a += (b & d | c & ~d) + k[9] + 568446438 | 0;
        a = (a << 5 | a >>> 27) + b | 0;
        d += (a & c | b & ~c) + k[14] - 1019803690 | 0;
        d = (d << 9 | d >>> 23) + a | 0;
        c += (d & b | a & ~b) + k[3] - 187363961 | 0;
        c = (c << 14 | c >>> 18) + d | 0;
        b += (c & a | d & ~a) + k[8] + 1163531501 | 0;
        b = (b << 20 | b >>> 12) + c | 0;
        a += (b & d | c & ~d) + k[13] - 1444681467 | 0;
        a = (a << 5 | a >>> 27) + b | 0;
        d += (a & c | b & ~c) + k[2] - 51403784 | 0;
        d = (d << 9 | d >>> 23) + a | 0;
        c += (d & b | a & ~b) + k[7] + 1735328473 | 0;
        c = (c << 14 | c >>> 18) + d | 0;
        b += (c & a | d & ~a) + k[12] - 1926607734 | 0;
        b = (b << 20 | b >>> 12) + c | 0;
        a += (b ^ c ^ d) + k[5] - 378558 | 0;
        a = (a << 4 | a >>> 28) + b | 0;
        d += (a ^ b ^ c) + k[8] - 2022574463 | 0;
        d = (d << 11 | d >>> 21) + a | 0;
        c += (d ^ a ^ b) + k[11] + 1839030562 | 0;
        c = (c << 16 | c >>> 16) + d | 0;
        b += (c ^ d ^ a) + k[14] - 35309556 | 0;
        b = (b << 23 | b >>> 9) + c | 0;
        a += (b ^ c ^ d) + k[1] - 1530992060 | 0;
        a = (a << 4 | a >>> 28) + b | 0;
        d += (a ^ b ^ c) + k[4] + 1272893353 | 0;
        d = (d << 11 | d >>> 21) + a | 0;
        c += (d ^ a ^ b) + k[7] - 155497632 | 0;
        c = (c << 16 | c >>> 16) + d | 0;
        b += (c ^ d ^ a) + k[10] - 1094730640 | 0;
        b = (b << 23 | b >>> 9) + c | 0;
        a += (b ^ c ^ d) + k[13] + 681279174 | 0;
        a = (a << 4 | a >>> 28) + b | 0;
        d += (a ^ b ^ c) + k[0] - 358537222 | 0;
        d = (d << 11 | d >>> 21) + a | 0;
        c += (d ^ a ^ b) + k[3] - 722521979 | 0;
        c = (c << 16 | c >>> 16) + d | 0;
        b += (c ^ d ^ a) + k[6] + 76029189 | 0;
        b = (b << 23 | b >>> 9) + c | 0;
        a += (b ^ c ^ d) + k[9] - 640364487 | 0;
        a = (a << 4 | a >>> 28) + b | 0;
        d += (a ^ b ^ c) + k[12] - 421815835 | 0;
        d = (d << 11 | d >>> 21) + a | 0;
        c += (d ^ a ^ b) + k[15] + 530742520 | 0;
        c = (c << 16 | c >>> 16) + d | 0;
        b += (c ^ d ^ a) + k[2] - 995338651 | 0;
        b = (b << 23 | b >>> 9) + c | 0;
        a += (c ^ (b | ~d)) + k[0] - 198630844 | 0;
        a = (a << 6 | a >>> 26) + b | 0;
        d += (b ^ (a | ~c)) + k[7] + 1126891415 | 0;
        d = (d << 10 | d >>> 22) + a | 0;
        c += (a ^ (d | ~b)) + k[14] - 1416354905 | 0;
        c = (c << 15 | c >>> 17) + d | 0;
        b += (d ^ (c | ~a)) + k[5] - 57434055 | 0;
        b = (b << 21 | b >>> 11) + c | 0;
        a += (c ^ (b | ~d)) + k[12] + 1700485571 | 0;
        a = (a << 6 | a >>> 26) + b | 0;
        d += (b ^ (a | ~c)) + k[3] - 1894986606 | 0;
        d = (d << 10 | d >>> 22) + a | 0;
        c += (a ^ (d | ~b)) + k[10] - 1051523 | 0;
        c = (c << 15 | c >>> 17) + d | 0;
        b += (d ^ (c | ~a)) + k[1] - 2054922799 | 0;
        b = (b << 21 | b >>> 11) + c | 0;
        a += (c ^ (b | ~d)) + k[8] + 1873313359 | 0;
        a = (a << 6 | a >>> 26) + b | 0;
        d += (b ^ (a | ~c)) + k[15] - 30611744 | 0;
        d = (d << 10 | d >>> 22) + a | 0;
        c += (a ^ (d | ~b)) + k[6] - 1560198380 | 0;
        c = (c << 15 | c >>> 17) + d | 0;
        b += (d ^ (c | ~a)) + k[13] + 1309151649 | 0;
        b = (b << 21 | b >>> 11) + c | 0;
        a += (c ^ (b | ~d)) + k[4] - 145523070 | 0;
        a = (a << 6 | a >>> 26) + b | 0;
        d += (b ^ (a | ~c)) + k[11] - 1120210379 | 0;
        d = (d << 10 | d >>> 22) + a | 0;
        c += (a ^ (d | ~b)) + k[2] + 718787259 | 0;
        c = (c << 15 | c >>> 17) + d | 0;
        b += (d ^ (c | ~a)) + k[9] - 343485551 | 0;
        b = (b << 21 | b >>> 11) + c | 0;
        x[0] = a + x[0] | 0;
        x[1] = b + x[1] | 0;
        x[2] = c + x[2] | 0;
        x[3] = d + x[3] | 0;
      }
      function md5blk(s) {
        var md5blks = [], i;
        for (i = 0; i < 64; i += 4) {
          md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
        }
        return md5blks;
      }
      function md5blk_array(a) {
        var md5blks = [], i;
        for (i = 0; i < 64; i += 4) {
          md5blks[i >> 2] = a[i] + (a[i + 1] << 8) + (a[i + 2] << 16) + (a[i + 3] << 24);
        }
        return md5blks;
      }
      function md51(s) {
        var n = s.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi;
        for (i = 64; i <= n; i += 64) {
          md5cycle(state, md5blk(s.substring(i - 64, i)));
        }
        s = s.substring(i - 64);
        length = s.length;
        tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
        for (i = 0; i < length; i += 1) {
          tail[i >> 2] |= s.charCodeAt(i) << (i % 4 << 3);
        }
        tail[i >> 2] |= 128 << (i % 4 << 3);
        if (i > 55) {
          md5cycle(state, tail);
          for (i = 0; i < 16; i += 1) {
            tail[i] = 0;
          }
        }
        tmp = n * 8;
        tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
        lo = parseInt(tmp[2], 16);
        hi = parseInt(tmp[1], 16) || 0;
        tail[14] = lo;
        tail[15] = hi;
        md5cycle(state, tail);
        return state;
      }
      function md51_array(a) {
        var n = a.length, state = [ 1732584193, -271733879, -1732584194, 271733878 ], i, length, tail, tmp, lo, hi;
        for (i = 64; i <= n; i += 64) {
          md5cycle(state, md5blk_array(a.subarray(i - 64, i)));
        }
        a = i - 64 < n ? a.subarray(i - 64) : new Uint8Array(0);
        length = a.length;
        tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
        for (i = 0; i < length; i += 1) {
          tail[i >> 2] |= a[i] << (i % 4 << 3);
        }
        tail[i >> 2] |= 128 << (i % 4 << 3);
        if (i > 55) {
          md5cycle(state, tail);
          for (i = 0; i < 16; i += 1) {
            tail[i] = 0;
          }
        }
        tmp = n * 8;
        tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
        lo = parseInt(tmp[2], 16);
        hi = parseInt(tmp[1], 16) || 0;
        tail[14] = lo;
        tail[15] = hi;
        md5cycle(state, tail);
        return state;
      }
      function rhex(n) {
        var s = "", j;
        for (j = 0; j < 4; j += 1) {
          s += hex_chr[n >> j * 8 + 4 & 15] + hex_chr[n >> j * 8 & 15];
        }
        return s;
      }
      function hex(x) {
        var i;
        for (i = 0; i < x.length; i += 1) {
          x[i] = rhex(x[i]);
        }
        return x.join("");
      }
      if (hex(md51("hello")) !== "5d41402abc4b2a76b9719d911017c592") ;
      if (typeof ArrayBuffer !== "undefined" && !ArrayBuffer.prototype.slice) {
        (function() {
          function clamp(val, length) {
            val = val | 0 || 0;
            if (val < 0) {
              return Math.max(val + length, 0);
            }
            return Math.min(val, length);
          }
          ArrayBuffer.prototype.slice = function(from, to) {
            var length = this.byteLength, begin = clamp(from, length), end = length, num, target, targetArray, sourceArray;
            if (to !== undefined) {
              end = clamp(to, length);
            }
            if (begin > end) {
              return new ArrayBuffer(0);
            }
            num = end - begin;
            target = new ArrayBuffer(num);
            targetArray = new Uint8Array(target);
            sourceArray = new Uint8Array(this, begin, num);
            targetArray.set(sourceArray);
            return target;
          };
        })();
      }
      function toUtf8(str) {
        if (/[\u0080-\uFFFF]/.test(str)) {
          str = unescape(encodeURIComponent(str));
        }
        return str;
      }
      function utf8Str2ArrayBuffer(str, returnUInt8Array) {
        var length = str.length, buff = new ArrayBuffer(length), arr = new Uint8Array(buff), i;
        for (i = 0; i < length; i += 1) {
          arr[i] = str.charCodeAt(i);
        }
        return returnUInt8Array ? arr : buff;
      }
      function arrayBuffer2Utf8Str(buff) {
        return String.fromCharCode.apply(null, new Uint8Array(buff));
      }
      function concatenateArrayBuffers(first, second, returnUInt8Array) {
        var result = new Uint8Array(first.byteLength + second.byteLength);
        result.set(new Uint8Array(first));
        result.set(new Uint8Array(second), first.byteLength);
        return returnUInt8Array ? result : result.buffer;
      }
      function hexToBinaryString(hex) {
        var bytes = [], length = hex.length, x;
        for (x = 0; x < length - 1; x += 2) {
          bytes.push(parseInt(hex.substr(x, 2), 16));
        }
        return String.fromCharCode.apply(String, bytes);
      }
      function SparkMD5() {
        this.reset();
      }
      SparkMD5.prototype.append = function(str) {
        this.appendBinary(toUtf8(str));
        return this;
      };
      SparkMD5.prototype.appendBinary = function(contents) {
        this._buff += contents;
        this._length += contents.length;
        var length = this._buff.length, i;
        for (i = 64; i <= length; i += 64) {
          md5cycle(this._hash, md5blk(this._buff.substring(i - 64, i)));
        }
        this._buff = this._buff.substring(i - 64);
        return this;
      };
      SparkMD5.prototype.end = function(raw) {
        var buff = this._buff, length = buff.length, i, tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], ret;
        for (i = 0; i < length; i += 1) {
          tail[i >> 2] |= buff.charCodeAt(i) << (i % 4 << 3);
        }
        this._finish(tail, length);
        ret = hex(this._hash);
        if (raw) {
          ret = hexToBinaryString(ret);
        }
        this.reset();
        return ret;
      };
      SparkMD5.prototype.reset = function() {
        this._buff = "";
        this._length = 0;
        this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ];
        return this;
      };
      SparkMD5.prototype.getState = function() {
        return {
          buff: this._buff,
          length: this._length,
          hash: this._hash
        };
      };
      SparkMD5.prototype.setState = function(state) {
        this._buff = state.buff;
        this._length = state.length;
        this._hash = state.hash;
        return this;
      };
      SparkMD5.prototype.destroy = function() {
        delete this._hash;
        delete this._buff;
        delete this._length;
      };
      SparkMD5.prototype._finish = function(tail, length) {
        var i = length, tmp, lo, hi;
        tail[i >> 2] |= 128 << (i % 4 << 3);
        if (i > 55) {
          md5cycle(this._hash, tail);
          for (i = 0; i < 16; i += 1) {
            tail[i] = 0;
          }
        }
        tmp = this._length * 8;
        tmp = tmp.toString(16).match(/(.*?)(.{0,8})$/);
        lo = parseInt(tmp[2], 16);
        hi = parseInt(tmp[1], 16) || 0;
        tail[14] = lo;
        tail[15] = hi;
        md5cycle(this._hash, tail);
      };
      SparkMD5.hash = function(str, raw) {
        return SparkMD5.hashBinary(toUtf8(str), raw);
      };
      SparkMD5.hashBinary = function(content, raw) {
        var hash = md51(content), ret = hex(hash);
        return raw ? hexToBinaryString(ret) : ret;
      };
      SparkMD5.ArrayBuffer = function() {
        this.reset();
      };
      SparkMD5.ArrayBuffer.prototype.append = function(arr) {
        var buff = concatenateArrayBuffers(this._buff.buffer, arr, true), length = buff.length, i;
        this._length += arr.byteLength;
        for (i = 64; i <= length; i += 64) {
          md5cycle(this._hash, md5blk_array(buff.subarray(i - 64, i)));
        }
        this._buff = i - 64 < length ? new Uint8Array(buff.buffer.slice(i - 64)) : new Uint8Array(0);
        return this;
      };
      SparkMD5.ArrayBuffer.prototype.end = function(raw) {
        var buff = this._buff, length = buff.length, tail = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], i, ret;
        for (i = 0; i < length; i += 1) {
          tail[i >> 2] |= buff[i] << (i % 4 << 3);
        }
        this._finish(tail, length);
        ret = hex(this._hash);
        if (raw) {
          ret = hexToBinaryString(ret);
        }
        this.reset();
        return ret;
      };
      SparkMD5.ArrayBuffer.prototype.reset = function() {
        this._buff = new Uint8Array(0);
        this._length = 0;
        this._hash = [ 1732584193, -271733879, -1732584194, 271733878 ];
        return this;
      };
      SparkMD5.ArrayBuffer.prototype.getState = function() {
        var state = SparkMD5.prototype.getState.call(this);
        state.buff = arrayBuffer2Utf8Str(state.buff);
        return state;
      };
      SparkMD5.ArrayBuffer.prototype.setState = function(state) {
        state.buff = utf8Str2ArrayBuffer(state.buff, true);
        return SparkMD5.prototype.setState.call(this, state);
      };
      SparkMD5.ArrayBuffer.prototype.destroy = SparkMD5.prototype.destroy;
      SparkMD5.ArrayBuffer.prototype._finish = SparkMD5.prototype._finish;
      SparkMD5.ArrayBuffer.hash = function(arr, raw) {
        var hash = md51_array(new Uint8Array(arr)), ret = hex(hash);
        return raw ? hexToBinaryString(ret) : ret;
      };
      return SparkMD5;
    });
  });
  var classCallCheck = function(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  };
  var createClass = function() {
    function defineProperties(target, props) {
      for (var i = 0; i < props.length; i++) {
        var descriptor = props[i];
        descriptor.enumerable = descriptor.enumerable || false;
        descriptor.configurable = true;
        if ("value" in descriptor) descriptor.writable = true;
        Object.defineProperty(target, descriptor.key, descriptor);
      }
    }
    return function(Constructor, protoProps, staticProps) {
      if (protoProps) defineProperties(Constructor.prototype, protoProps);
      if (staticProps) defineProperties(Constructor, staticProps);
      return Constructor;
    };
  }();
  var fileSlice = File.prototype.slice || File.prototype.mozSlice || File.prototype.webkitSlice;
  var FileChecksum = function() {
    createClass(FileChecksum, null, [ {
      key: "create",
      value: function create(file, callback) {
        var instance = new FileChecksum(file);
        instance.create(callback);
      }
    } ]);
    function FileChecksum(file) {
      classCallCheck(this, FileChecksum);
      this.file = file;
      this.chunkSize = 2097152;
      this.chunkCount = Math.ceil(this.file.size / this.chunkSize);
      this.chunkIndex = 0;
    }
    createClass(FileChecksum, [ {
      key: "create",
      value: function create(callback) {
        var _this = this;
        this.callback = callback;
        this.md5Buffer = new sparkMd5.ArrayBuffer();
        this.fileReader = new FileReader();
        this.fileReader.addEventListener("load", function(event) {
          return _this.fileReaderDidLoad(event);
        });
        this.fileReader.addEventListener("error", function(event) {
          return _this.fileReaderDidError(event);
        });
        this.readNextChunk();
      }
    }, {
      key: "fileReaderDidLoad",
      value: function fileReaderDidLoad(event) {
        this.md5Buffer.append(event.target.result);
        if (!this.readNextChunk()) {
          var binaryDigest = this.md5Buffer.end(true);
          var base64digest = btoa(binaryDigest);
          this.callback(null, base64digest);
        }
      }
    }, {
      key: "fileReaderDidError",
      value: function fileReaderDidError(event) {
        this.callback("Error reading " + this.file.name);
      }
    }, {
      key: "readNextChunk",
      value: function readNextChunk() {
        if (this.chunkIndex < this.chunkCount || this.chunkIndex == 0 && this.chunkCount == 0) {
          var start = this.chunkIndex * this.chunkSize;
          var end = Math.min(start + this.chunkSize, this.file.size);
          var bytes = fileSlice.call(this.file, start, end);
          this.fileReader.readAsArrayBuffer(bytes);
          this.chunkIndex++;
          return true;
        } else {
          return false;
        }
      }
    } ]);
    return FileChecksum;
  }();
  function getMetaValue(name) {
    var element = findElement(document.head, 'meta[name="' + name + '"]');
    if (element) {
      return element.getAttribute("content");
    }
  }
  function findElements(root, selector) {
    if (typeof root == "string") {
      selector = root;
      root = document;
    }
    var elements = root.querySelectorAll(selector);
    return toArray$1(elements);
  }
  function findElement(root, selector) {
    if (typeof root == "string") {
      selector = root;
      root = document;
    }
    return root.querySelector(selector);
  }
  function dispatchEvent(element, type) {
    var eventInit = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
    var disabled = element.disabled;
    var bubbles = eventInit.bubbles, cancelable = eventInit.cancelable, detail = eventInit.detail;
    var event = document.createEvent("Event");
    event.initEvent(type, bubbles || true, cancelable || true);
    event.detail = detail || {};
    try {
      element.disabled = false;
      element.dispatchEvent(event);
    } finally {
      element.disabled = disabled;
    }
    return event;
  }
  function toArray$1(value) {
    if (Array.isArray(value)) {
      return value;
    } else if (Array.from) {
      return Array.from(value);
    } else {
      return [].slice.call(value);
    }
  }
  var BlobRecord = function() {
    function BlobRecord(file, checksum, url) {
      var _this = this;
      classCallCheck(this, BlobRecord);
      this.file = file;
      this.attributes = {
        filename: file.name,
        content_type: file.type,
        byte_size: file.size,
        checksum: checksum
      };
      this.xhr = new XMLHttpRequest();
      this.xhr.open("POST", url, true);
      this.xhr.responseType = "json";
      this.xhr.setRequestHeader("Content-Type", "application/json");
      this.xhr.setRequestHeader("Accept", "application/json");
      this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
      this.xhr.setRequestHeader("X-CSRF-Token", getMetaValue("csrf-token"));
      this.xhr.addEventListener("load", function(event) {
        return _this.requestDidLoad(event);
      });
      this.xhr.addEventListener("error", function(event) {
        return _this.requestDidError(event);
      });
    }
    createClass(BlobRecord, [ {
      key: "create",
      value: function create(callback) {
        this.callback = callback;
        this.xhr.send(JSON.stringify({
          blob: this.attributes
        }));
      }
    }, {
      key: "requestDidLoad",
      value: function requestDidLoad(event) {
        if (this.status >= 200 && this.status < 300) {
          var response = this.response;
          var direct_upload = response.direct_upload;
          delete response.direct_upload;
          this.attributes = response;
          this.directUploadData = direct_upload;
          this.callback(null, this.toJSON());
        } else {
          this.requestDidError(event);
        }
      }
    }, {
      key: "requestDidError",
      value: function requestDidError(event) {
        this.callback('Error creating Blob for "' + this.file.name + '". Status: ' + this.status);
      }
    }, {
      key: "toJSON",
      value: function toJSON() {
        var result = {};
        for (var key in this.attributes) {
          result[key] = this.attributes[key];
        }
        return result;
      }
    }, {
      key: "status",
      get: function get$$1() {
        return this.xhr.status;
      }
    }, {
      key: "response",
      get: function get$$1() {
        var _xhr = this.xhr, responseType = _xhr.responseType, response = _xhr.response;
        if (responseType == "json") {
          return response;
        } else {
          return JSON.parse(response);
        }
      }
    } ]);
    return BlobRecord;
  }();
  var BlobUpload = function() {
    function BlobUpload(blob) {
      var _this = this;
      classCallCheck(this, BlobUpload);
      this.blob = blob;
      this.file = blob.file;
      var _blob$directUploadDat = blob.directUploadData, url = _blob$directUploadDat.url, headers = _blob$directUploadDat.headers;
      this.xhr = new XMLHttpRequest();
      this.xhr.open("PUT", url, true);
      this.xhr.responseType = "text";
      for (var key in headers) {
        this.xhr.setRequestHeader(key, headers[key]);
      }
      this.xhr.addEventListener("load", function(event) {
        return _this.requestDidLoad(event);
      });
      this.xhr.addEventListener("error", function(event) {
        return _this.requestDidError(event);
      });
    }
    createClass(BlobUpload, [ {
      key: "create",
      value: function create(callback) {
        this.callback = callback;
        this.xhr.send(this.file.slice());
      }
    }, {
      key: "requestDidLoad",
      value: function requestDidLoad(event) {
        var _xhr = this.xhr, status = _xhr.status, response = _xhr.response;
        if (status >= 200 && status < 300) {
          this.callback(null, response);
        } else {
          this.requestDidError(event);
        }
      }
    }, {
      key: "requestDidError",
      value: function requestDidError(event) {
        this.callback('Error storing "' + this.file.name + '". Status: ' + this.xhr.status);
      }
    } ]);
    return BlobUpload;
  }();
  var id = 0;
  var DirectUpload = function() {
    function DirectUpload(file, url, delegate) {
      classCallCheck(this, DirectUpload);
      this.id = ++id;
      this.file = file;
      this.url = url;
      this.delegate = delegate;
    }
    createClass(DirectUpload, [ {
      key: "create",
      value: function create(callback) {
        var _this = this;
        FileChecksum.create(this.file, function(error, checksum) {
          if (error) {
            callback(error);
            return;
          }
          var blob = new BlobRecord(_this.file, checksum, _this.url);
          notify(_this.delegate, "directUploadWillCreateBlobWithXHR", blob.xhr);
          blob.create(function(error) {
            if (error) {
              callback(error);
            } else {
              var upload = new BlobUpload(blob);
              notify(_this.delegate, "directUploadWillStoreFileWithXHR", upload.xhr);
              upload.create(function(error) {
                if (error) {
                  callback(error);
                } else {
                  callback(null, blob.toJSON());
                }
              });
            }
          });
        });
      }
    } ]);
    return DirectUpload;
  }();
  function notify(object, methodName) {
    if (object && typeof object[methodName] == "function") {
      for (var _len = arguments.length, messages = Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
        messages[_key - 2] = arguments[_key];
      }
      return object[methodName].apply(object, messages);
    }
  }
  var DirectUploadController = function() {
    function DirectUploadController(input, file) {
      classCallCheck(this, DirectUploadController);
      this.input = input;
      this.file = file;
      this.directUpload = new DirectUpload(this.file, this.url, this);
      this.dispatch("initialize");
    }
    createClass(DirectUploadController, [ {
      key: "start",
      value: function start(callback) {
        var _this = this;
        var hiddenInput = document.createElement("input");
        hiddenInput.type = "hidden";
        hiddenInput.name = this.input.name;
        this.input.insertAdjacentElement("beforebegin", hiddenInput);
        this.dispatch("start");
        this.directUpload.create(function(error, attributes) {
          if (error) {
            hiddenInput.parentNode.removeChild(hiddenInput);
            _this.dispatchError(error);
          } else {
            hiddenInput.value = attributes.signed_id;
          }
          _this.dispatch("end");
          callback(error);
        });
      }
    }, {
      key: "uploadRequestDidProgress",
      value: function uploadRequestDidProgress(event) {
        var progress = event.loaded / event.total * 100;
        if (progress) {
          this.dispatch("progress", {
            progress: progress
          });
        }
      }
    }, {
      key: "dispatch",
      value: function dispatch(name) {
        var detail = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        detail.file = this.file;
        detail.id = this.directUpload.id;
        return dispatchEvent(this.input, "direct-upload:" + name, {
          detail: detail
        });
      }
    }, {
      key: "dispatchError",
      value: function dispatchError(error) {
        var event = this.dispatch("error", {
          error: error
        });
        if (!event.defaultPrevented) {
          alert(error);
        }
      }
    }, {
      key: "directUploadWillCreateBlobWithXHR",
      value: function directUploadWillCreateBlobWithXHR(xhr) {
        this.dispatch("before-blob-request", {
          xhr: xhr
        });
      }
    }, {
      key: "directUploadWillStoreFileWithXHR",
      value: function directUploadWillStoreFileWithXHR(xhr) {
        var _this2 = this;
        this.dispatch("before-storage-request", {
          xhr: xhr
        });
        xhr.upload.addEventListener("progress", function(event) {
          return _this2.uploadRequestDidProgress(event);
        });
      }
    }, {
      key: "url",
      get: function get$$1() {
        return this.input.getAttribute("data-direct-upload-url");
      }
    } ]);
    return DirectUploadController;
  }();
  var inputSelector = "input[type=file][data-direct-upload-url]:not([disabled])";
  var DirectUploadsController = function() {
    function DirectUploadsController(form) {
      classCallCheck(this, DirectUploadsController);
      this.form = form;
      this.inputs = findElements(form, inputSelector).filter(function(input) {
        return input.files.length;
      });
    }
    createClass(DirectUploadsController, [ {
      key: "start",
      value: function start(callback) {
        var _this = this;
        var controllers = this.createDirectUploadControllers();
        var startNextController = function startNextController() {
          var controller = controllers.shift();
          if (controller) {
            controller.start(function(error) {
              if (error) {
                callback(error);
                _this.dispatch("end");
              } else {
                startNextController();
              }
            });
          } else {
            callback();
            _this.dispatch("end");
          }
        };
        this.dispatch("start");
        startNextController();
      }
    }, {
      key: "createDirectUploadControllers",
      value: function createDirectUploadControllers() {
        var controllers = [];
        this.inputs.forEach(function(input) {
          toArray$1(input.files).forEach(function(file) {
            var controller = new DirectUploadController(input, file);
            controllers.push(controller);
          });
        });
        return controllers;
      }
    }, {
      key: "dispatch",
      value: function dispatch(name) {
        var detail = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        return dispatchEvent(this.form, "direct-uploads:" + name, {
          detail: detail
        });
      }
    } ]);
    return DirectUploadsController;
  }();
  var processingAttribute = "data-direct-uploads-processing";
  var submitButtonsByForm = new WeakMap();
  var started = false;
  function start() {
    if (!started) {
      started = true;
      document.addEventListener("click", didClick, true);
      document.addEventListener("submit", didSubmitForm);
      document.addEventListener("ajax:before", didSubmitRemoteElement);
    }
  }
  function didClick(event) {
    var target = event.target;
    if ((target.tagName == "INPUT" || target.tagName == "BUTTON") && target.type == "submit" && target.form) {
      submitButtonsByForm.set(target.form, target);
    }
  }
  function didSubmitForm(event) {
    handleFormSubmissionEvent(event);
  }
  function didSubmitRemoteElement(event) {
    if (event.target.tagName == "FORM") {
      handleFormSubmissionEvent(event);
    }
  }
  function handleFormSubmissionEvent(event) {
    var form = event.target;
    if (form.hasAttribute(processingAttribute)) {
      event.preventDefault();
      return;
    }
    var controller = new DirectUploadsController(form);
    var inputs = controller.inputs;
    if (inputs.length) {
      event.preventDefault();
      form.setAttribute(processingAttribute, "");
      inputs.forEach(disable);
      controller.start(function(error) {
        form.removeAttribute(processingAttribute);
        if (error) {
          inputs.forEach(enable);
        } else {
          submitForm(form);
        }
      });
    }
  }
  function submitForm(form) {
    var button = submitButtonsByForm.get(form) || findElement(form, "input[type=submit], button[type=submit]");
    if (button) {
      var _button = button, disabled = _button.disabled;
      button.disabled = false;
      button.focus();
      button.click();
      button.disabled = disabled;
    } else {
      button = document.createElement("input");
      button.type = "submit";
      button.style.display = "none";
      form.appendChild(button);
      button.click();
      form.removeChild(button);
    }
    submitButtonsByForm.delete(form);
  }
  function disable(input) {
    input.disabled = true;
  }
  function enable(input) {
    input.disabled = false;
  }
  function autostart() {
    if (window.ActiveStorage) {
      start();
    }
  }
  setTimeout(autostart, 1);
  exports.start = start;
  exports.DirectUpload = DirectUpload;
  Object.defineProperty(exports, "__esModule", {
    value: true
  });
});
/*!
 * jQuery JavaScript Library v3.7.0
 * https://jquery.com/
 *
 * Copyright OpenJS Foundation and other contributors
 * Released under the MIT license
 * https://jquery.org/license
 *
 * Date: 2023-05-11T18:29Z
 */

( function( global, factory ) {

	"use strict";

	if ( typeof module === "object" && typeof module.exports === "object" ) {

		// For CommonJS and CommonJS-like environments where a proper `window`
		// is present, execute the factory and get jQuery.
		// For environments that do not have a `window` with a `document`
		// (such as Node.js), expose a factory as module.exports.
		// This accentuates the need for the creation of a real `window`.
		// e.g. var jQuery = require("jquery")(window);
		// See ticket trac-14549 for more info.
		module.exports = global.document ?
			factory( global, true ) :
			function( w ) {
				if ( !w.document ) {
					throw new Error( "jQuery requires a window with a document" );
				}
				return factory( w );
			};
	} else {
		factory( global );
	}

// Pass this if window is not defined yet
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {

// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
// enough that all such attempts are guarded in a try block.
"use strict";

var arr = [];

var getProto = Object.getPrototypeOf;

var slice = arr.slice;

var flat = arr.flat ? function( array ) {
	return arr.flat.call( array );
} : function( array ) {
	return arr.concat.apply( [], array );
};


var push = arr.push;

var indexOf = arr.indexOf;

var class2type = {};

var toString = class2type.toString;

var hasOwn = class2type.hasOwnProperty;

var fnToString = hasOwn.toString;

var ObjectFunctionString = fnToString.call( Object );

var support = {};

var isFunction = function isFunction( obj ) {

		// Support: Chrome <=57, Firefox <=52
		// In some browsers, typeof returns "function" for HTML <object> elements
		// (i.e., `typeof document.createElement( "object" ) === "function"`).
		// We don't want to classify *any* DOM node as a function.
		// Support: QtWeb <=3.8.5, WebKit <=534.34, wkhtmltopdf tool <=0.12.5
		// Plus for old WebKit, typeof returns "function" for HTML collections
		// (e.g., `typeof document.getElementsByTagName("div") === "function"`). (gh-4756)
		return typeof obj === "function" && typeof obj.nodeType !== "number" &&
			typeof obj.item !== "function";
	};


var isWindow = function isWindow( obj ) {
		return obj != null && obj === obj.window;
	};


var document = window.document;



	var preservedScriptAttributes = {
		type: true,
		src: true,
		nonce: true,
		noModule: true
	};

	function DOMEval( code, node, doc ) {
		doc = doc || document;

		var i, val,
			script = doc.createElement( "script" );

		script.text = code;
		if ( node ) {
			for ( i in preservedScriptAttributes ) {

				// Support: Firefox 64+, Edge 18+
				// Some browsers don't support the "nonce" property on scripts.
				// On the other hand, just using `getAttribute` is not enough as
				// the `nonce` attribute is reset to an empty string whenever it
				// becomes browsing-context connected.
				// See https://github.com/whatwg/html/issues/2369
				// See https://html.spec.whatwg.org/#nonce-attributes
				// The `node.getAttribute` check was added for the sake of
				// `jQuery.globalEval` so that it can fake a nonce-containing node
				// via an object.
				val = node[ i ] || node.getAttribute && node.getAttribute( i );
				if ( val ) {
					script.setAttribute( i, val );
				}
			}
		}
		doc.head.appendChild( script ).parentNode.removeChild( script );
	}


function toType( obj ) {
	if ( obj == null ) {
		return obj + "";
	}

	// Support: Android <=2.3 only (functionish RegExp)
	return typeof obj === "object" || typeof obj === "function" ?
		class2type[ toString.call( obj ) ] || "object" :
		typeof obj;
}
/* global Symbol */
// Defining this global in .eslintrc.json would create a danger of using the global
// unguarded in another place, it seems safer to define global only for this module



var version = "3.7.0",

	rhtmlSuffix = /HTML$/i,

	// Define a local copy of jQuery
	jQuery = function( selector, context ) {

		// The jQuery object is actually just the init constructor 'enhanced'
		// Need init if jQuery is called (just allow error to be thrown if not included)
		return new jQuery.fn.init( selector, context );
	};

jQuery.fn = jQuery.prototype = {

	// The current version of jQuery being used
	jquery: version,

	constructor: jQuery,

	// The default length of a jQuery object is 0
	length: 0,

	toArray: function() {
		return slice.call( this );
	},

	// Get the Nth element in the matched element set OR
	// Get the whole matched element set as a clean array
	get: function( num ) {

		// Return all the elements in a clean array
		if ( num == null ) {
			return slice.call( this );
		}

		// Return just the one element from the set
		return num < 0 ? this[ num + this.length ] : this[ num ];
	},

	// Take an array of elements and push it onto the stack
	// (returning the new matched element set)
	pushStack: function( elems ) {

		// Build a new jQuery matched element set
		var ret = jQuery.merge( this.constructor(), elems );

		// Add the old object onto the stack (as a reference)
		ret.prevObject = this;

		// Return the newly-formed element set
		return ret;
	},

	// Execute a callback for every element in the matched set.
	each: function( callback ) {
		return jQuery.each( this, callback );
	},

	map: function( callback ) {
		return this.pushStack( jQuery.map( this, function( elem, i ) {
			return callback.call( elem, i, elem );
		} ) );
	},

	slice: function() {
		return this.pushStack( slice.apply( this, arguments ) );
	},

	first: function() {
		return this.eq( 0 );
	},

	last: function() {
		return this.eq( -1 );
	},

	even: function() {
		return this.pushStack( jQuery.grep( this, function( _elem, i ) {
			return ( i + 1 ) % 2;
		} ) );
	},

	odd: function() {
		return this.pushStack( jQuery.grep( this, function( _elem, i ) {
			return i % 2;
		} ) );
	},

	eq: function( i ) {
		var len = this.length,
			j = +i + ( i < 0 ? len : 0 );
		return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
	},

	end: function() {
		return this.prevObject || this.constructor();
	},

	// For internal use only.
	// Behaves like an Array's method, not like a jQuery method.
	push: push,
	sort: arr.sort,
	splice: arr.splice
};

jQuery.extend = jQuery.fn.extend = function() {
	var options, name, src, copy, copyIsArray, clone,
		target = arguments[ 0 ] || {},
		i = 1,
		length = arguments.length,
		deep = false;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;

		// Skip the boolean and the target
		target = arguments[ i ] || {};
		i++;
	}

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && !isFunction( target ) ) {
		target = {};
	}

	// Extend jQuery itself if only one argument is passed
	if ( i === length ) {
		target = this;
		i--;
	}

	for ( ; i < length; i++ ) {

		// Only deal with non-null/undefined values
		if ( ( options = arguments[ i ] ) != null ) {

			// Extend the base object
			for ( name in options ) {
				copy = options[ name ];

				// Prevent Object.prototype pollution
				// Prevent never-ending loop
				if ( name === "__proto__" || target === copy ) {
					continue;
				}

				// Recurse if we're merging plain objects or arrays
				if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
					( copyIsArray = Array.isArray( copy ) ) ) ) {
					src = target[ name ];

					// Ensure proper type for the source value
					if ( copyIsArray && !Array.isArray( src ) ) {
						clone = [];
					} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
						clone = {};
					} else {
						clone = src;
					}
					copyIsArray = false;

					// Never move original objects, clone them
					target[ name ] = jQuery.extend( deep, clone, copy );

				// Don't bring in undefined values
				} else if ( copy !== undefined ) {
					target[ name ] = copy;
				}
			}
		}
	}

	// Return the modified object
	return target;
};

jQuery.extend( {

	// Unique for each copy of jQuery on the page
	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),

	// Assume jQuery is ready without the ready module
	isReady: true,

	error: function( msg ) {
		throw new Error( msg );
	},

	noop: function() {},

	isPlainObject: function( obj ) {
		var proto, Ctor;

		// Detect obvious negatives
		// Use toString instead of jQuery.type to catch host objects
		if ( !obj || toString.call( obj ) !== "[object Object]" ) {
			return false;
		}

		proto = getProto( obj );

		// Objects with no prototype (e.g., `Object.create( null )`) are plain
		if ( !proto ) {
			return true;
		}

		// Objects with prototype are plain iff they were constructed by a global Object function
		Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;
		return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
	},

	isEmptyObject: function( obj ) {
		var name;

		for ( name in obj ) {
			return false;
		}
		return true;
	},

	// Evaluates a script in a provided context; falls back to the global one
	// if not specified.
	globalEval: function( code, options, doc ) {
		DOMEval( code, { nonce: options && options.nonce }, doc );
	},

	each: function( obj, callback ) {
		var length, i = 0;

		if ( isArrayLike( obj ) ) {
			length = obj.length;
			for ( ; i < length; i++ ) {
				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
					break;
				}
			}
		} else {
			for ( i in obj ) {
				if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
					break;
				}
			}
		}

		return obj;
	},


	// Retrieve the text value of an array of DOM nodes
	text: function( elem ) {
		var node,
			ret = "",
			i = 0,
			nodeType = elem.nodeType;

		if ( !nodeType ) {

			// If no nodeType, this is expected to be an array
			while ( ( node = elem[ i++ ] ) ) {

				// Do not traverse comment nodes
				ret += jQuery.text( node );
			}
		} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
			return elem.textContent;
		} else if ( nodeType === 3 || nodeType === 4 ) {
			return elem.nodeValue;
		}

		// Do not include comment or processing instruction nodes

		return ret;
	},

	// results is for internal usage only
	makeArray: function( arr, results ) {
		var ret = results || [];

		if ( arr != null ) {
			if ( isArrayLike( Object( arr ) ) ) {
				jQuery.merge( ret,
					typeof arr === "string" ?
						[ arr ] : arr
				);
			} else {
				push.call( ret, arr );
			}
		}

		return ret;
	},

	inArray: function( elem, arr, i ) {
		return arr == null ? -1 : indexOf.call( arr, elem, i );
	},

	isXMLDoc: function( elem ) {
		var namespace = elem && elem.namespaceURI,
			docElem = elem && ( elem.ownerDocument || elem ).documentElement;

		// Assume HTML when documentElement doesn't yet exist, such as inside
		// document fragments.
		return !rhtmlSuffix.test( namespace || docElem && docElem.nodeName || "HTML" );
	},

	// Support: Android <=4.0 only, PhantomJS 1 only
	// push.apply(_, arraylike) throws on ancient WebKit
	merge: function( first, second ) {
		var len = +second.length,
			j = 0,
			i = first.length;

		for ( ; j < len; j++ ) {
			first[ i++ ] = second[ j ];
		}

		first.length = i;

		return first;
	},

	grep: function( elems, callback, invert ) {
		var callbackInverse,
			matches = [],
			i = 0,
			length = elems.length,
			callbackExpect = !invert;

		// Go through the array, only saving the items
		// that pass the validator function
		for ( ; i < length; i++ ) {
			callbackInverse = !callback( elems[ i ], i );
			if ( callbackInverse !== callbackExpect ) {
				matches.push( elems[ i ] );
			}
		}

		return matches;
	},

	// arg is for internal usage only
	map: function( elems, callback, arg ) {
		var length, value,
			i = 0,
			ret = [];

		// Go through the array, translating each of the items to their new values
		if ( isArrayLike( elems ) ) {
			length = elems.length;
			for ( ; i < length; i++ ) {
				value = callback( elems[ i ], i, arg );

				if ( value != null ) {
					ret.push( value );
				}
			}

		// Go through every key on the object,
		} else {
			for ( i in elems ) {
				value = callback( elems[ i ], i, arg );

				if ( value != null ) {
					ret.push( value );
				}
			}
		}

		// Flatten any nested arrays
		return flat( ret );
	},

	// A global GUID counter for objects
	guid: 1,

	// jQuery.support is not used in Core but other projects attach their
	// properties to it so it needs to exist.
	support: support
} );

if ( typeof Symbol === "function" ) {
	jQuery.fn[ Symbol.iterator ] = arr[ Symbol.iterator ];
}

// Populate the class2type map
jQuery.each( "Boolean Number String Function Array Date RegExp Object Error Symbol".split( " " ),
	function( _i, name ) {
		class2type[ "[object " + name + "]" ] = name.toLowerCase();
	} );

function isArrayLike( obj ) {

	// Support: real iOS 8.2 only (not reproducible in simulator)
	// `in` check used to prevent JIT error (gh-2145)
	// hasOwn isn't used here due to false negatives
	// regarding Nodelist length in IE
	var length = !!obj && "length" in obj && obj.length,
		type = toType( obj );

	if ( isFunction( obj ) || isWindow( obj ) ) {
		return false;
	}

	return type === "array" || length === 0 ||
		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
}


function nodeName( elem, name ) {

	return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();

}
var pop = arr.pop;


var sort = arr.sort;


var splice = arr.splice;


var whitespace = "[\\x20\\t\\r\\n\\f]";


var rtrimCSS = new RegExp(
	"^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$",
	"g"
);




// Note: an element does not contain itself
jQuery.contains = function( a, b ) {
	var bup = b && b.parentNode;

	return a === bup || !!( bup && bup.nodeType === 1 && (

		// Support: IE 9 - 11+
		// IE doesn't have `contains` on SVG.
		a.contains ?
			a.contains( bup ) :
			a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
	) );
};




// CSS string/identifier serialization
// https://drafts.csswg.org/cssom/#common-serializing-idioms
var rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\x80-\uFFFF\w-]/g;

function fcssescape( ch, asCodePoint ) {
	if ( asCodePoint ) {

		// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
		if ( ch === "\0" ) {
			return "\uFFFD";
		}

		// Control characters and (dependent upon position) numbers get escaped as code points
		return ch.slice( 0, -1 ) + "\\" + ch.charCodeAt( ch.length - 1 ).toString( 16 ) + " ";
	}

	// Other potentially-special ASCII characters get backslash-escaped
	return "\\" + ch;
}

jQuery.escapeSelector = function( sel ) {
	return ( sel + "" ).replace( rcssescape, fcssescape );
};




var preferredDoc = document,
	pushNative = push;

( function() {

var i,
	Expr,
	outermostContext,
	sortInput,
	hasDuplicate,
	push = pushNative,

	// Local document vars
	document,
	documentElement,
	documentIsHTML,
	rbuggyQSA,
	matches,

	// Instance-specific data
	expando = jQuery.expando,
	dirruns = 0,
	done = 0,
	classCache = createCache(),
	tokenCache = createCache(),
	compilerCache = createCache(),
	nonnativeSelectorCache = createCache(),
	sortOrder = function( a, b ) {
		if ( a === b ) {
			hasDuplicate = true;
		}
		return 0;
	},

	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|" +
		"loop|multiple|open|readonly|required|scoped",

	// Regular expressions

	// https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
	identifier = "(?:\\\\[\\da-fA-F]{1,6}" + whitespace +
		"?|\\\\[^\\r\\n\\f]|[\\w-]|[^\0-\\x7f])+",

	// Attribute selectors: https://www.w3.org/TR/selectors/#attribute-selectors
	attributes = "\\[" + whitespace + "*(" + identifier + ")(?:" + whitespace +

		// Operator (capture 2)
		"*([*^$|!~]?=)" + whitespace +

		// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
		"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" +
		whitespace + "*\\]",

	pseudos = ":(" + identifier + ")(?:\\((" +

		// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
		// 1. quoted (capture 3; capture 4 or capture 5)
		"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +

		// 2. simple (capture 6)
		"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +

		// 3. anything else (capture 2)
		".*" +
		")\\)|)",

	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
	rwhitespace = new RegExp( whitespace + "+", "g" ),

	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
	rleadingCombinator = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" +
		whitespace + "*" ),
	rdescend = new RegExp( whitespace + "|>" ),

	rpseudo = new RegExp( pseudos ),
	ridentifier = new RegExp( "^" + identifier + "$" ),

	matchExpr = {
		ID: new RegExp( "^#(" + identifier + ")" ),
		CLASS: new RegExp( "^\\.(" + identifier + ")" ),
		TAG: new RegExp( "^(" + identifier + "|[*])" ),
		ATTR: new RegExp( "^" + attributes ),
		PSEUDO: new RegExp( "^" + pseudos ),
		CHILD: new RegExp(
			"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" +
				whitespace + "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" +
				whitespace + "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
		bool: new RegExp( "^(?:" + booleans + ")$", "i" ),

		// For use in libraries implementing .is()
		// We use this for POS matching in `select`
		needsContext: new RegExp( "^" + whitespace +
			"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
			"*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
	},

	rinputs = /^(?:input|select|textarea|button)$/i,
	rheader = /^h\d$/i,

	// Easily-parseable/retrievable ID or TAG or CLASS selectors
	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,

	rsibling = /[+~]/,

	// CSS escapes
	// https://www.w3.org/TR/CSS21/syndata.html#escaped-characters
	runescape = new RegExp( "\\\\[\\da-fA-F]{1,6}" + whitespace +
		"?|\\\\([^\\r\\n\\f])", "g" ),
	funescape = function( escape, nonHex ) {
		var high = "0x" + escape.slice( 1 ) - 0x10000;

		if ( nonHex ) {

			// Strip the backslash prefix from a non-hex escape sequence
			return nonHex;
		}

		// Replace a hexadecimal escape sequence with the encoded Unicode code point
		// Support: IE <=11+
		// For values outside the Basic Multilingual Plane (BMP), manually construct a
		// surrogate pair
		return high < 0 ?
			String.fromCharCode( high + 0x10000 ) :
			String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
	},

	// Used for iframes; see `setDocument`.
	// Support: IE 9 - 11+, Edge 12 - 18+
	// Removing the function wrapper causes a "Permission Denied"
	// error in IE/Edge.
	unloadHandler = function() {
		setDocument();
	},

	inDisabledFieldset = addCombinator(
		function( elem ) {
			return elem.disabled === true && nodeName( elem, "fieldset" );
		},
		{ dir: "parentNode", next: "legend" }
	);

// Support: IE <=9 only
// Accessing document.activeElement can throw unexpectedly
// https://bugs.jquery.com/ticket/13393
function safeActiveElement() {
	try {
		return document.activeElement;
	} catch ( err ) { }
}

// Optimize for push.apply( _, NodeList )
try {
	push.apply(
		( arr = slice.call( preferredDoc.childNodes ) ),
		preferredDoc.childNodes
	);

	// Support: Android <=4.0
	// Detect silently failing push.apply
	// eslint-disable-next-line no-unused-expressions
	arr[ preferredDoc.childNodes.length ].nodeType;
} catch ( e ) {
	push = {
		apply: function( target, els ) {
			pushNative.apply( target, slice.call( els ) );
		},
		call: function( target ) {
			pushNative.apply( target, slice.call( arguments, 1 ) );
		}
	};
}

function find( selector, context, results, seed ) {
	var m, i, elem, nid, match, groups, newSelector,
		newContext = context && context.ownerDocument,

		// nodeType defaults to 9, since context defaults to document
		nodeType = context ? context.nodeType : 9;

	results = results || [];

	// Return early from calls with invalid selector or context
	if ( typeof selector !== "string" || !selector ||
		nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {

		return results;
	}

	// Try to shortcut find operations (as opposed to filters) in HTML documents
	if ( !seed ) {
		setDocument( context );
		context = context || document;

		if ( documentIsHTML ) {

			// If the selector is sufficiently simple, try using a "get*By*" DOM method
			// (excepting DocumentFragment context, where the methods don't exist)
			if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) {

				// ID selector
				if ( ( m = match[ 1 ] ) ) {

					// Document context
					if ( nodeType === 9 ) {
						if ( ( elem = context.getElementById( m ) ) ) {

							// Support: IE 9 only
							// getElementById can match elements by name instead of ID
							if ( elem.id === m ) {
								push.call( results, elem );
								return results;
							}
						} else {
							return results;
						}

					// Element context
					} else {

						// Support: IE 9 only
						// getElementById can match elements by name instead of ID
						if ( newContext && ( elem = newContext.getElementById( m ) ) &&
							find.contains( context, elem ) &&
							elem.id === m ) {

							push.call( results, elem );
							return results;
						}
					}

				// Type selector
				} else if ( match[ 2 ] ) {
					push.apply( results, context.getElementsByTagName( selector ) );
					return results;

				// Class selector
				} else if ( ( m = match[ 3 ] ) && context.getElementsByClassName ) {
					push.apply( results, context.getElementsByClassName( m ) );
					return results;
				}
			}

			// Take advantage of querySelectorAll
			if ( !nonnativeSelectorCache[ selector + " " ] &&
				( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) {

				newSelector = selector;
				newContext = context;

				// qSA considers elements outside a scoping root when evaluating child or
				// descendant combinators, which is not what we want.
				// In such cases, we work around the behavior by prefixing every selector in the
				// list with an ID selector referencing the scope context.
				// The technique has to be used as well when a leading combinator is used
				// as such selectors are not recognized by querySelectorAll.
				// Thanks to Andrew Dupont for this technique.
				if ( nodeType === 1 &&
					( rdescend.test( selector ) || rleadingCombinator.test( selector ) ) ) {

					// Expand context for sibling selectors
					newContext = rsibling.test( selector ) && testContext( context.parentNode ) ||
						context;

					// We can use :scope instead of the ID hack if the browser
					// supports it & if we're not changing the context.
					// Support: IE 11+, Edge 17 - 18+
					// IE/Edge sometimes throw a "Permission denied" error when
					// strict-comparing two documents; shallow comparisons work.
					// eslint-disable-next-line eqeqeq
					if ( newContext != context || !support.scope ) {

						// Capture the context ID, setting it first if necessary
						if ( ( nid = context.getAttribute( "id" ) ) ) {
							nid = jQuery.escapeSelector( nid );
						} else {
							context.setAttribute( "id", ( nid = expando ) );
						}
					}

					// Prefix every selector in the list
					groups = tokenize( selector );
					i = groups.length;
					while ( i-- ) {
						groups[ i ] = ( nid ? "#" + nid : ":scope" ) + " " +
							toSelector( groups[ i ] );
					}
					newSelector = groups.join( "," );
				}

				try {
					push.apply( results,
						newContext.querySelectorAll( newSelector )
					);
					return results;
				} catch ( qsaError ) {
					nonnativeSelectorCache( selector, true );
				} finally {
					if ( nid === expando ) {
						context.removeAttribute( "id" );
					}
				}
			}
		}
	}

	// All others
	return select( selector.replace( rtrimCSS, "$1" ), context, results, seed );
}

/**
 * Create key-value caches of limited size
 * @returns {function(string, object)} Returns the Object data after storing it on itself with
 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
 *	deleting the oldest entry
 */
function createCache() {
	var keys = [];

	function cache( key, value ) {

		// Use (key + " ") to avoid collision with native prototype properties
		// (see https://github.com/jquery/sizzle/issues/157)
		if ( keys.push( key + " " ) > Expr.cacheLength ) {

			// Only keep the most recent entries
			delete cache[ keys.shift() ];
		}
		return ( cache[ key + " " ] = value );
	}
	return cache;
}

/**
 * Mark a function for special use by jQuery selector module
 * @param {Function} fn The function to mark
 */
function markFunction( fn ) {
	fn[ expando ] = true;
	return fn;
}

/**
 * Support testing using an element
 * @param {Function} fn Passed the created element and returns a boolean result
 */
function assert( fn ) {
	var el = document.createElement( "fieldset" );

	try {
		return !!fn( el );
	} catch ( e ) {
		return false;
	} finally {

		// Remove from its parent by default
		if ( el.parentNode ) {
			el.parentNode.removeChild( el );
		}

		// release memory in IE
		el = null;
	}
}

/**
 * Returns a function to use in pseudos for input types
 * @param {String} type
 */
function createInputPseudo( type ) {
	return function( elem ) {
		return nodeName( elem, "input" ) && elem.type === type;
	};
}

/**
 * Returns a function to use in pseudos for buttons
 * @param {String} type
 */
function createButtonPseudo( type ) {
	return function( elem ) {
		return ( nodeName( elem, "input" ) || nodeName( elem, "button" ) ) &&
			elem.type === type;
	};
}

/**
 * Returns a function to use in pseudos for :enabled/:disabled
 * @param {Boolean} disabled true for :disabled; false for :enabled
 */
function createDisabledPseudo( disabled ) {

	// Known :disabled false positives: fieldset[disabled] > legend:nth-of-type(n+2) :can-disable
	return function( elem ) {

		// Only certain elements can match :enabled or :disabled
		// https://html.spec.whatwg.org/multipage/scripting.html#selector-enabled
		// https://html.spec.whatwg.org/multipage/scripting.html#selector-disabled
		if ( "form" in elem ) {

			// Check for inherited disabledness on relevant non-disabled elements:
			// * listed form-associated elements in a disabled fieldset
			//   https://html.spec.whatwg.org/multipage/forms.html#category-listed
			//   https://html.spec.whatwg.org/multipage/forms.html#concept-fe-disabled
			// * option elements in a disabled optgroup
			//   https://html.spec.whatwg.org/multipage/forms.html#concept-option-disabled
			// All such elements have a "form" property.
			if ( elem.parentNode && elem.disabled === false ) {

				// Option elements defer to a parent optgroup if present
				if ( "label" in elem ) {
					if ( "label" in elem.parentNode ) {
						return elem.parentNode.disabled === disabled;
					} else {
						return elem.disabled === disabled;
					}
				}

				// Support: IE 6 - 11+
				// Use the isDisabled shortcut property to check for disabled fieldset ancestors
				return elem.isDisabled === disabled ||

					// Where there is no isDisabled, check manually
					elem.isDisabled !== !disabled &&
						inDisabledFieldset( elem ) === disabled;
			}

			return elem.disabled === disabled;

		// Try to winnow out elements that can't be disabled before trusting the disabled property.
		// Some victims get caught in our net (label, legend, menu, track), but it shouldn't
		// even exist on them, let alone have a boolean value.
		} else if ( "label" in elem ) {
			return elem.disabled === disabled;
		}

		// Remaining elements are neither :enabled nor :disabled
		return false;
	};
}

/**
 * Returns a function to use in pseudos for positionals
 * @param {Function} fn
 */
function createPositionalPseudo( fn ) {
	return markFunction( function( argument ) {
		argument = +argument;
		return markFunction( function( seed, matches ) {
			var j,
				matchIndexes = fn( [], seed.length, argument ),
				i = matchIndexes.length;

			// Match elements found at the specified indexes
			while ( i-- ) {
				if ( seed[ ( j = matchIndexes[ i ] ) ] ) {
					seed[ j ] = !( matches[ j ] = seed[ j ] );
				}
			}
		} );
	} );
}

/**
 * Checks a node for validity as a jQuery selector context
 * @param {Element|Object=} context
 * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
 */
function testContext( context ) {
	return context && typeof context.getElementsByTagName !== "undefined" && context;
}

/**
 * Sets document-related variables once based on the current document
 * @param {Element|Object} [node] An element or document object to use to set the document
 * @returns {Object} Returns the current document
 */
function setDocument( node ) {
	var subWindow,
		doc = node ? node.ownerDocument || node : preferredDoc;

	// Return early if doc is invalid or already selected
	// Support: IE 11+, Edge 17 - 18+
	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
	// two documents; shallow comparisons work.
	// eslint-disable-next-line eqeqeq
	if ( doc == document || doc.nodeType !== 9 || !doc.documentElement ) {
		return document;
	}

	// Update global variables
	document = doc;
	documentElement = document.documentElement;
	documentIsHTML = !jQuery.isXMLDoc( document );

	// Support: iOS 7 only, IE 9 - 11+
	// Older browsers didn't support unprefixed `matches`.
	matches = documentElement.matches ||
		documentElement.webkitMatchesSelector ||
		documentElement.msMatchesSelector;

	// Support: IE 9 - 11+, Edge 12 - 18+
	// Accessing iframe documents after unload throws "permission denied" errors (see trac-13936)
	// Support: IE 11+, Edge 17 - 18+
	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
	// two documents; shallow comparisons work.
	// eslint-disable-next-line eqeqeq
	if ( preferredDoc != document &&
		( subWindow = document.defaultView ) && subWindow.top !== subWindow ) {

		// Support: IE 9 - 11+, Edge 12 - 18+
		subWindow.addEventListener( "unload", unloadHandler );
	}

	// Support: IE <10
	// Check if getElementById returns elements by name
	// The broken getElementById methods don't pick up programmatically-set names,
	// so use a roundabout getElementsByName test
	support.getById = assert( function( el ) {
		documentElement.appendChild( el ).id = jQuery.expando;
		return !document.getElementsByName ||
			!document.getElementsByName( jQuery.expando ).length;
	} );

	// Support: IE 9 only
	// Check to see if it's possible to do matchesSelector
	// on a disconnected node.
	support.disconnectedMatch = assert( function( el ) {
		return matches.call( el, "*" );
	} );

	// Support: IE 9 - 11+, Edge 12 - 18+
	// IE/Edge don't support the :scope pseudo-class.
	support.scope = assert( function() {
		return document.querySelectorAll( ":scope" );
	} );

	// Support: Chrome 105 - 111 only, Safari 15.4 - 16.3 only
	// Make sure the `:has()` argument is parsed unforgivingly.
	// We include `*` in the test to detect buggy implementations that are
	// _selectively_ forgiving (specifically when the list includes at least
	// one valid selector).
	// Note that we treat complete lack of support for `:has()` as if it were
	// spec-compliant support, which is fine because use of `:has()` in such
	// environments will fail in the qSA path and fall back to jQuery traversal
	// anyway.
	support.cssHas = assert( function() {
		try {
			document.querySelector( ":has(*,:jqfake)" );
			return false;
		} catch ( e ) {
			return true;
		}
	} );

	// ID filter and find
	if ( support.getById ) {
		Expr.filter.ID = function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				return elem.getAttribute( "id" ) === attrId;
			};
		};
		Expr.find.ID = function( id, context ) {
			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
				var elem = context.getElementById( id );
				return elem ? [ elem ] : [];
			}
		};
	} else {
		Expr.filter.ID =  function( id ) {
			var attrId = id.replace( runescape, funescape );
			return function( elem ) {
				var node = typeof elem.getAttributeNode !== "undefined" &&
					elem.getAttributeNode( "id" );
				return node && node.value === attrId;
			};
		};

		// Support: IE 6 - 7 only
		// getElementById is not reliable as a find shortcut
		Expr.find.ID = function( id, context ) {
			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
				var node, i, elems,
					elem = context.getElementById( id );

				if ( elem ) {

					// Verify the id attribute
					node = elem.getAttributeNode( "id" );
					if ( node && node.value === id ) {
						return [ elem ];
					}

					// Fall back on getElementsByName
					elems = context.getElementsByName( id );
					i = 0;
					while ( ( elem = elems[ i++ ] ) ) {
						node = elem.getAttributeNode( "id" );
						if ( node && node.value === id ) {
							return [ elem ];
						}
					}
				}

				return [];
			}
		};
	}

	// Tag
	Expr.find.TAG = function( tag, context ) {
		if ( typeof context.getElementsByTagName !== "undefined" ) {
			return context.getElementsByTagName( tag );

		// DocumentFragment nodes don't have gEBTN
		} else {
			return context.querySelectorAll( tag );
		}
	};

	// Class
	Expr.find.CLASS = function( className, context ) {
		if ( typeof context.getElementsByClassName !== "undefined" && documentIsHTML ) {
			return context.getElementsByClassName( className );
		}
	};

	/* QSA/matchesSelector
	---------------------------------------------------------------------- */

	// QSA and matchesSelector support

	rbuggyQSA = [];

	// Build QSA regex
	// Regex strategy adopted from Diego Perini
	assert( function( el ) {

		var input;

		documentElement.appendChild( el ).innerHTML =
			"<a id='" + expando + "' href='' disabled='disabled'></a>" +
			"<select id='" + expando + "-\r\\' disabled='disabled'>" +
			"<option selected=''></option></select>";

		// Support: iOS <=7 - 8 only
		// Boolean attributes and "value" are not treated correctly in some XML documents
		if ( !el.querySelectorAll( "[selected]" ).length ) {
			rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
		}

		// Support: iOS <=7 - 8 only
		if ( !el.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
			rbuggyQSA.push( "~=" );
		}

		// Support: iOS 8 only
		// https://bugs.webkit.org/show_bug.cgi?id=136851
		// In-page `selector#id sibling-combinator selector` fails
		if ( !el.querySelectorAll( "a#" + expando + "+*" ).length ) {
			rbuggyQSA.push( ".#.+[+~]" );
		}

		// Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+
		// In some of the document kinds, these selectors wouldn't work natively.
		// This is probably OK but for backwards compatibility we want to maintain
		// handling them through jQuery traversal in jQuery 3.x.
		if ( !el.querySelectorAll( ":checked" ).length ) {
			rbuggyQSA.push( ":checked" );
		}

		// Support: Windows 8 Native Apps
		// The type and name attributes are restricted during .innerHTML assignment
		input = document.createElement( "input" );
		input.setAttribute( "type", "hidden" );
		el.appendChild( input ).setAttribute( "name", "D" );

		// Support: IE 9 - 11+
		// IE's :disabled selector does not pick up the children of disabled fieldsets
		// Support: Chrome <=105+, Firefox <=104+, Safari <=15.4+
		// In some of the document kinds, these selectors wouldn't work natively.
		// This is probably OK but for backwards compatibility we want to maintain
		// handling them through jQuery traversal in jQuery 3.x.
		documentElement.appendChild( el ).disabled = true;
		if ( el.querySelectorAll( ":disabled" ).length !== 2 ) {
			rbuggyQSA.push( ":enabled", ":disabled" );
		}

		// Support: IE 11+, Edge 15 - 18+
		// IE 11/Edge don't find elements on a `[name='']` query in some cases.
		// Adding a temporary attribute to the document before the selection works
		// around the issue.
		// Interestingly, IE 10 & older don't seem to have the issue.
		input = document.createElement( "input" );
		input.setAttribute( "name", "" );
		el.appendChild( input );
		if ( !el.querySelectorAll( "[name='']" ).length ) {
			rbuggyQSA.push( "\\[" + whitespace + "*name" + whitespace + "*=" +
				whitespace + "*(?:''|\"\")" );
		}
	} );

	if ( !support.cssHas ) {

		// Support: Chrome 105 - 110+, Safari 15.4 - 16.3+
		// Our regular `try-catch` mechanism fails to detect natively-unsupported
		// pseudo-classes inside `:has()` (such as `:has(:contains("Foo"))`)
		// in browsers that parse the `:has()` argument as a forgiving selector list.
		// https://drafts.csswg.org/selectors/#relational now requires the argument
		// to be parsed unforgivingly, but browsers have not yet fully adjusted.
		rbuggyQSA.push( ":has" );
	}

	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join( "|" ) );

	/* Sorting
	---------------------------------------------------------------------- */

	// Document order sorting
	sortOrder = function( a, b ) {

		// Flag for duplicate removal
		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		// Sort on method existence if only one input has compareDocumentPosition
		var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
		if ( compare ) {
			return compare;
		}

		// Calculate position if both inputs belong to the same document
		// Support: IE 11+, Edge 17 - 18+
		// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
		// two documents; shallow comparisons work.
		// eslint-disable-next-line eqeqeq
		compare = ( a.ownerDocument || a ) == ( b.ownerDocument || b ) ?
			a.compareDocumentPosition( b ) :

			// Otherwise we know they are disconnected
			1;

		// Disconnected nodes
		if ( compare & 1 ||
			( !support.sortDetached && b.compareDocumentPosition( a ) === compare ) ) {

			// Choose the first element that is related to our preferred document
			// Support: IE 11+, Edge 17 - 18+
			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
			// two documents; shallow comparisons work.
			// eslint-disable-next-line eqeqeq
			if ( a === document || a.ownerDocument == preferredDoc &&
				find.contains( preferredDoc, a ) ) {
				return -1;
			}

			// Support: IE 11+, Edge 17 - 18+
			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
			// two documents; shallow comparisons work.
			// eslint-disable-next-line eqeqeq
			if ( b === document || b.ownerDocument == preferredDoc &&
				find.contains( preferredDoc, b ) ) {
				return 1;
			}

			// Maintain original order
			return sortInput ?
				( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
				0;
		}

		return compare & 4 ? -1 : 1;
	};

	return document;
}

find.matches = function( expr, elements ) {
	return find( expr, null, null, elements );
};

find.matchesSelector = function( elem, expr ) {
	setDocument( elem );

	if ( documentIsHTML &&
		!nonnativeSelectorCache[ expr + " " ] &&
		( !rbuggyQSA || !rbuggyQSA.test( expr ) ) ) {

		try {
			var ret = matches.call( elem, expr );

			// IE 9's matchesSelector returns false on disconnected nodes
			if ( ret || support.disconnectedMatch ||

					// As well, disconnected nodes are said to be in a document
					// fragment in IE 9
					elem.document && elem.document.nodeType !== 11 ) {
				return ret;
			}
		} catch ( e ) {
			nonnativeSelectorCache( expr, true );
		}
	}

	return find( expr, document, null, [ elem ] ).length > 0;
};

find.contains = function( context, elem ) {

	// Set document vars if needed
	// Support: IE 11+, Edge 17 - 18+
	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
	// two documents; shallow comparisons work.
	// eslint-disable-next-line eqeqeq
	if ( ( context.ownerDocument || context ) != document ) {
		setDocument( context );
	}
	return jQuery.contains( context, elem );
};


find.attr = function( elem, name ) {

	// Set document vars if needed
	// Support: IE 11+, Edge 17 - 18+
	// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
	// two documents; shallow comparisons work.
	// eslint-disable-next-line eqeqeq
	if ( ( elem.ownerDocument || elem ) != document ) {
		setDocument( elem );
	}

	var fn = Expr.attrHandle[ name.toLowerCase() ],

		// Don't get fooled by Object.prototype properties (see trac-13807)
		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
			fn( elem, name, !documentIsHTML ) :
			undefined;

	if ( val !== undefined ) {
		return val;
	}

	return elem.getAttribute( name );
};

find.error = function( msg ) {
	throw new Error( "Syntax error, unrecognized expression: " + msg );
};

/**
 * Document sorting and removing duplicates
 * @param {ArrayLike} results
 */
jQuery.uniqueSort = function( results ) {
	var elem,
		duplicates = [],
		j = 0,
		i = 0;

	// Unless we *know* we can detect duplicates, assume their presence
	//
	// Support: Android <=4.0+
	// Testing for detecting duplicates is unpredictable so instead assume we can't
	// depend on duplicate detection in all browsers without a stable sort.
	hasDuplicate = !support.sortStable;
	sortInput = !support.sortStable && slice.call( results, 0 );
	sort.call( results, sortOrder );

	if ( hasDuplicate ) {
		while ( ( elem = results[ i++ ] ) ) {
			if ( elem === results[ i ] ) {
				j = duplicates.push( i );
			}
		}
		while ( j-- ) {
			splice.call( results, duplicates[ j ], 1 );
		}
	}

	// Clear input after sorting to release objects
	// See https://github.com/jquery/sizzle/pull/225
	sortInput = null;

	return results;
};

jQuery.fn.uniqueSort = function() {
	return this.pushStack( jQuery.uniqueSort( slice.apply( this ) ) );
};

Expr = jQuery.expr = {

	// Can be adjusted by the user
	cacheLength: 50,

	createPseudo: markFunction,

	match: matchExpr,

	attrHandle: {},

	find: {},

	relative: {
		">": { dir: "parentNode", first: true },
		" ": { dir: "parentNode" },
		"+": { dir: "previousSibling", first: true },
		"~": { dir: "previousSibling" }
	},

	preFilter: {
		ATTR: function( match ) {
			match[ 1 ] = match[ 1 ].replace( runescape, funescape );

			// Move the given value to match[3] whether quoted or unquoted
			match[ 3 ] = ( match[ 3 ] || match[ 4 ] || match[ 5 ] || "" )
				.replace( runescape, funescape );

			if ( match[ 2 ] === "~=" ) {
				match[ 3 ] = " " + match[ 3 ] + " ";
			}

			return match.slice( 0, 4 );
		},

		CHILD: function( match ) {

			/* matches from matchExpr["CHILD"]
				1 type (only|nth|...)
				2 what (child|of-type)
				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
				4 xn-component of xn+y argument ([+-]?\d*n|)
				5 sign of xn-component
				6 x of xn-component
				7 sign of y-component
				8 y of y-component
			*/
			match[ 1 ] = match[ 1 ].toLowerCase();

			if ( match[ 1 ].slice( 0, 3 ) === "nth" ) {

				// nth-* requires argument
				if ( !match[ 3 ] ) {
					find.error( match[ 0 ] );
				}

				// numeric x and y parameters for Expr.filter.CHILD
				// remember that false/true cast respectively to 0/1
				match[ 4 ] = +( match[ 4 ] ?
					match[ 5 ] + ( match[ 6 ] || 1 ) :
					2 * ( match[ 3 ] === "even" || match[ 3 ] === "odd" )
				);
				match[ 5 ] = +( ( match[ 7 ] + match[ 8 ] ) || match[ 3 ] === "odd" );

			// other types prohibit arguments
			} else if ( match[ 3 ] ) {
				find.error( match[ 0 ] );
			}

			return match;
		},

		PSEUDO: function( match ) {
			var excess,
				unquoted = !match[ 6 ] && match[ 2 ];

			if ( matchExpr.CHILD.test( match[ 0 ] ) ) {
				return null;
			}

			// Accept quoted arguments as-is
			if ( match[ 3 ] ) {
				match[ 2 ] = match[ 4 ] || match[ 5 ] || "";

			// Strip excess characters from unquoted arguments
			} else if ( unquoted && rpseudo.test( unquoted ) &&

				// Get excess from tokenize (recursively)
				( excess = tokenize( unquoted, true ) ) &&

				// advance to the next closing parenthesis
				( excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length ) ) {

				// excess is a negative index
				match[ 0 ] = match[ 0 ].slice( 0, excess );
				match[ 2 ] = unquoted.slice( 0, excess );
			}

			// Return only captures needed by the pseudo filter method (type and argument)
			return match.slice( 0, 3 );
		}
	},

	filter: {

		TAG: function( nodeNameSelector ) {
			var expectedNodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
			return nodeNameSelector === "*" ?
				function() {
					return true;
				} :
				function( elem ) {
					return nodeName( elem, expectedNodeName );
				};
		},

		CLASS: function( className ) {
			var pattern = classCache[ className + " " ];

			return pattern ||
				( pattern = new RegExp( "(^|" + whitespace + ")" + className +
					"(" + whitespace + "|$)" ) ) &&
				classCache( className, function( elem ) {
					return pattern.test(
						typeof elem.className === "string" && elem.className ||
							typeof elem.getAttribute !== "undefined" &&
								elem.getAttribute( "class" ) ||
							""
					);
				} );
		},

		ATTR: function( name, operator, check ) {
			return function( elem ) {
				var result = find.attr( elem, name );

				if ( result == null ) {
					return operator === "!=";
				}
				if ( !operator ) {
					return true;
				}

				result += "";

				if ( operator === "=" ) {
					return result === check;
				}
				if ( operator === "!=" ) {
					return result !== check;
				}
				if ( operator === "^=" ) {
					return check && result.indexOf( check ) === 0;
				}
				if ( operator === "*=" ) {
					return check && result.indexOf( check ) > -1;
				}
				if ( operator === "$=" ) {
					return check && result.slice( -check.length ) === check;
				}
				if ( operator === "~=" ) {
					return ( " " + result.replace( rwhitespace, " " ) + " " )
						.indexOf( check ) > -1;
				}
				if ( operator === "|=" ) {
					return result === check || result.slice( 0, check.length + 1 ) === check + "-";
				}

				return false;
			};
		},

		CHILD: function( type, what, _argument, first, last ) {
			var simple = type.slice( 0, 3 ) !== "nth",
				forward = type.slice( -4 ) !== "last",
				ofType = what === "of-type";

			return first === 1 && last === 0 ?

				// Shortcut for :nth-*(n)
				function( elem ) {
					return !!elem.parentNode;
				} :

				function( elem, _context, xml ) {
					var cache, outerCache, node, nodeIndex, start,
						dir = simple !== forward ? "nextSibling" : "previousSibling",
						parent = elem.parentNode,
						name = ofType && elem.nodeName.toLowerCase(),
						useCache = !xml && !ofType,
						diff = false;

					if ( parent ) {

						// :(first|last|only)-(child|of-type)
						if ( simple ) {
							while ( dir ) {
								node = elem;
								while ( ( node = node[ dir ] ) ) {
									if ( ofType ?
										nodeName( node, name ) :
										node.nodeType === 1 ) {

										return false;
									}
								}

								// Reverse direction for :only-* (if we haven't yet done so)
								start = dir = type === "only" && !start && "nextSibling";
							}
							return true;
						}

						start = [ forward ? parent.firstChild : parent.lastChild ];

						// non-xml :nth-child(...) stores cache data on `parent`
						if ( forward && useCache ) {

							// Seek `elem` from a previously-cached index
							outerCache = parent[ expando ] || ( parent[ expando ] = {} );
							cache = outerCache[ type ] || [];
							nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
							diff = nodeIndex && cache[ 2 ];
							node = nodeIndex && parent.childNodes[ nodeIndex ];

							while ( ( node = ++nodeIndex && node && node[ dir ] ||

								// Fallback to seeking `elem` from the start
								( diff = nodeIndex = 0 ) || start.pop() ) ) {

								// When found, cache indexes on `parent` and break
								if ( node.nodeType === 1 && ++diff && node === elem ) {
									outerCache[ type ] = [ dirruns, nodeIndex, diff ];
									break;
								}
							}

						} else {

							// Use previously-cached element index if available
							if ( useCache ) {
								outerCache = elem[ expando ] || ( elem[ expando ] = {} );
								cache = outerCache[ type ] || [];
								nodeIndex = cache[ 0 ] === dirruns && cache[ 1 ];
								diff = nodeIndex;
							}

							// xml :nth-child(...)
							// or :nth-last-child(...) or :nth(-last)?-of-type(...)
							if ( diff === false ) {

								// Use the same loop as above to seek `elem` from the start
								while ( ( node = ++nodeIndex && node && node[ dir ] ||
									( diff = nodeIndex = 0 ) || start.pop() ) ) {

									if ( ( ofType ?
										nodeName( node, name ) :
										node.nodeType === 1 ) &&
										++diff ) {

										// Cache the index of each encountered element
										if ( useCache ) {
											outerCache = node[ expando ] ||
												( node[ expando ] = {} );
											outerCache[ type ] = [ dirruns, diff ];
										}

										if ( node === elem ) {
											break;
										}
									}
								}
							}
						}

						// Incorporate the offset, then check against cycle size
						diff -= last;
						return diff === first || ( diff % first === 0 && diff / first >= 0 );
					}
				};
		},

		PSEUDO: function( pseudo, argument ) {

			// pseudo-class names are case-insensitive
			// https://www.w3.org/TR/selectors/#pseudo-classes
			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
			// Remember that setFilters inherits from pseudos
			var args,
				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
					find.error( "unsupported pseudo: " + pseudo );

			// The user may use createPseudo to indicate that
			// arguments are needed to create the filter function
			// just as jQuery does
			if ( fn[ expando ] ) {
				return fn( argument );
			}

			// But maintain support for old signatures
			if ( fn.length > 1 ) {
				args = [ pseudo, pseudo, "", argument ];
				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
					markFunction( function( seed, matches ) {
						var idx,
							matched = fn( seed, argument ),
							i = matched.length;
						while ( i-- ) {
							idx = indexOf.call( seed, matched[ i ] );
							seed[ idx ] = !( matches[ idx ] = matched[ i ] );
						}
					} ) :
					function( elem ) {
						return fn( elem, 0, args );
					};
			}

			return fn;
		}
	},

	pseudos: {

		// Potentially complex pseudos
		not: markFunction( function( selector ) {

			// Trim the selector passed to compile
			// to avoid treating leading and trailing
			// spaces as combinators
			var input = [],
				results = [],
				matcher = compile( selector.replace( rtrimCSS, "$1" ) );

			return matcher[ expando ] ?
				markFunction( function( seed, matches, _context, xml ) {
					var elem,
						unmatched = matcher( seed, null, xml, [] ),
						i = seed.length;

					// Match elements unmatched by `matcher`
					while ( i-- ) {
						if ( ( elem = unmatched[ i ] ) ) {
							seed[ i ] = !( matches[ i ] = elem );
						}
					}
				} ) :
				function( elem, _context, xml ) {
					input[ 0 ] = elem;
					matcher( input, null, xml, results );

					// Don't keep the element
					// (see https://github.com/jquery/sizzle/issues/299)
					input[ 0 ] = null;
					return !results.pop();
				};
		} ),

		has: markFunction( function( selector ) {
			return function( elem ) {
				return find( selector, elem ).length > 0;
			};
		} ),

		contains: markFunction( function( text ) {
			text = text.replace( runescape, funescape );
			return function( elem ) {
				return ( elem.textContent || jQuery.text( elem ) ).indexOf( text ) > -1;
			};
		} ),

		// "Whether an element is represented by a :lang() selector
		// is based solely on the element's language value
		// being equal to the identifier C,
		// or beginning with the identifier C immediately followed by "-".
		// The matching of C against the element's language value is performed case-insensitively.
		// The identifier C does not have to be a valid language name."
		// https://www.w3.org/TR/selectors/#lang-pseudo
		lang: markFunction( function( lang ) {

			// lang value must be a valid identifier
			if ( !ridentifier.test( lang || "" ) ) {
				find.error( "unsupported lang: " + lang );
			}
			lang = lang.replace( runescape, funescape ).toLowerCase();
			return function( elem ) {
				var elemLang;
				do {
					if ( ( elemLang = documentIsHTML ?
						elem.lang :
						elem.getAttribute( "xml:lang" ) || elem.getAttribute( "lang" ) ) ) {

						elemLang = elemLang.toLowerCase();
						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
					}
				} while ( ( elem = elem.parentNode ) && elem.nodeType === 1 );
				return false;
			};
		} ),

		// Miscellaneous
		target: function( elem ) {
			var hash = window.location && window.location.hash;
			return hash && hash.slice( 1 ) === elem.id;
		},

		root: function( elem ) {
			return elem === documentElement;
		},

		focus: function( elem ) {
			return elem === safeActiveElement() &&
				document.hasFocus() &&
				!!( elem.type || elem.href || ~elem.tabIndex );
		},

		// Boolean properties
		enabled: createDisabledPseudo( false ),
		disabled: createDisabledPseudo( true ),

		checked: function( elem ) {

			// In CSS3, :checked should return both checked and selected elements
			// https://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
			return ( nodeName( elem, "input" ) && !!elem.checked ) ||
				( nodeName( elem, "option" ) && !!elem.selected );
		},

		selected: function( elem ) {

			// Support: IE <=11+
			// Accessing the selectedIndex property
			// forces the browser to treat the default option as
			// selected when in an optgroup.
			if ( elem.parentNode ) {
				// eslint-disable-next-line no-unused-expressions
				elem.parentNode.selectedIndex;
			}

			return elem.selected === true;
		},

		// Contents
		empty: function( elem ) {

			// https://www.w3.org/TR/selectors/#empty-pseudo
			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
			//   but not by others (comment: 8; processing instruction: 7; etc.)
			// nodeType < 6 works because attributes (2) do not appear as children
			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
				if ( elem.nodeType < 6 ) {
					return false;
				}
			}
			return true;
		},

		parent: function( elem ) {
			return !Expr.pseudos.empty( elem );
		},

		// Element/input types
		header: function( elem ) {
			return rheader.test( elem.nodeName );
		},

		input: function( elem ) {
			return rinputs.test( elem.nodeName );
		},

		button: function( elem ) {
			return nodeName( elem, "input" ) && elem.type === "button" ||
				nodeName( elem, "button" );
		},

		text: function( elem ) {
			var attr;
			return nodeName( elem, "input" ) && elem.type === "text" &&

				// Support: IE <10 only
				// New HTML5 attribute values (e.g., "search") appear
				// with elem.type === "text"
				( ( attr = elem.getAttribute( "type" ) ) == null ||
					attr.toLowerCase() === "text" );
		},

		// Position-in-collection
		first: createPositionalPseudo( function() {
			return [ 0 ];
		} ),

		last: createPositionalPseudo( function( _matchIndexes, length ) {
			return [ length - 1 ];
		} ),

		eq: createPositionalPseudo( function( _matchIndexes, length, argument ) {
			return [ argument < 0 ? argument + length : argument ];
		} ),

		even: createPositionalPseudo( function( matchIndexes, length ) {
			var i = 0;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		} ),

		odd: createPositionalPseudo( function( matchIndexes, length ) {
			var i = 1;
			for ( ; i < length; i += 2 ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		} ),

		lt: createPositionalPseudo( function( matchIndexes, length, argument ) {
			var i;

			if ( argument < 0 ) {
				i = argument + length;
			} else if ( argument > length ) {
				i = length;
			} else {
				i = argument;
			}

			for ( ; --i >= 0; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		} ),

		gt: createPositionalPseudo( function( matchIndexes, length, argument ) {
			var i = argument < 0 ? argument + length : argument;
			for ( ; ++i < length; ) {
				matchIndexes.push( i );
			}
			return matchIndexes;
		} )
	}
};

Expr.pseudos.nth = Expr.pseudos.eq;

// Add button/input type pseudos
for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
	Expr.pseudos[ i ] = createInputPseudo( i );
}
for ( i in { submit: true, reset: true } ) {
	Expr.pseudos[ i ] = createButtonPseudo( i );
}

// Easy API for creating new setFilters
function setFilters() {}
setFilters.prototype = Expr.filters = Expr.pseudos;
Expr.setFilters = new setFilters();

function tokenize( selector, parseOnly ) {
	var matched, match, tokens, type,
		soFar, groups, preFilters,
		cached = tokenCache[ selector + " " ];

	if ( cached ) {
		return parseOnly ? 0 : cached.slice( 0 );
	}

	soFar = selector;
	groups = [];
	preFilters = Expr.preFilter;

	while ( soFar ) {

		// Comma and first run
		if ( !matched || ( match = rcomma.exec( soFar ) ) ) {
			if ( match ) {

				// Don't consume trailing commas as valid
				soFar = soFar.slice( match[ 0 ].length ) || soFar;
			}
			groups.push( ( tokens = [] ) );
		}

		matched = false;

		// Combinators
		if ( ( match = rleadingCombinator.exec( soFar ) ) ) {
			matched = match.shift();
			tokens.push( {
				value: matched,

				// Cast descendant combinators to space
				type: match[ 0 ].replace( rtrimCSS, " " )
			} );
			soFar = soFar.slice( matched.length );
		}

		// Filters
		for ( type in Expr.filter ) {
			if ( ( match = matchExpr[ type ].exec( soFar ) ) && ( !preFilters[ type ] ||
				( match = preFilters[ type ]( match ) ) ) ) {
				matched = match.shift();
				tokens.push( {
					value: matched,
					type: type,
					matches: match
				} );
				soFar = soFar.slice( matched.length );
			}
		}

		if ( !matched ) {
			break;
		}
	}

	// Return the length of the invalid excess
	// if we're just parsing
	// Otherwise, throw an error or return tokens
	if ( parseOnly ) {
		return soFar.length;
	}

	return soFar ?
		find.error( selector ) :

		// Cache the tokens
		tokenCache( selector, groups ).slice( 0 );
}

function toSelector( tokens ) {
	var i = 0,
		len = tokens.length,
		selector = "";
	for ( ; i < len; i++ ) {
		selector += tokens[ i ].value;
	}
	return selector;
}

function addCombinator( matcher, combinator, base ) {
	var dir = combinator.dir,
		skip = combinator.next,
		key = skip || dir,
		checkNonElements = base && key === "parentNode",
		doneName = done++;

	return combinator.first ?

		// Check against closest ancestor/preceding element
		function( elem, context, xml ) {
			while ( ( elem = elem[ dir ] ) ) {
				if ( elem.nodeType === 1 || checkNonElements ) {
					return matcher( elem, context, xml );
				}
			}
			return false;
		} :

		// Check against all ancestor/preceding elements
		function( elem, context, xml ) {
			var oldCache, outerCache,
				newCache = [ dirruns, doneName ];

			// We can't set arbitrary data on XML nodes, so they don't benefit from combinator caching
			if ( xml ) {
				while ( ( elem = elem[ dir ] ) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						if ( matcher( elem, context, xml ) ) {
							return true;
						}
					}
				}
			} else {
				while ( ( elem = elem[ dir ] ) ) {
					if ( elem.nodeType === 1 || checkNonElements ) {
						outerCache = elem[ expando ] || ( elem[ expando ] = {} );

						if ( skip && nodeName( elem, skip ) ) {
							elem = elem[ dir ] || elem;
						} else if ( ( oldCache = outerCache[ key ] ) &&
							oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {

							// Assign to newCache so results back-propagate to previous elements
							return ( newCache[ 2 ] = oldCache[ 2 ] );
						} else {

							// Reuse newcache so results back-propagate to previous elements
							outerCache[ key ] = newCache;

							// A match means we're done; a fail means we have to keep checking
							if ( ( newCache[ 2 ] = matcher( elem, context, xml ) ) ) {
								return true;
							}
						}
					}
				}
			}
			return false;
		};
}

function elementMatcher( matchers ) {
	return matchers.length > 1 ?
		function( elem, context, xml ) {
			var i = matchers.length;
			while ( i-- ) {
				if ( !matchers[ i ]( elem, context, xml ) ) {
					return false;
				}
			}
			return true;
		} :
		matchers[ 0 ];
}

function multipleContexts( selector, contexts, results ) {
	var i = 0,
		len = contexts.length;
	for ( ; i < len; i++ ) {
		find( selector, contexts[ i ], results );
	}
	return results;
}

function condense( unmatched, map, filter, context, xml ) {
	var elem,
		newUnmatched = [],
		i = 0,
		len = unmatched.length,
		mapped = map != null;

	for ( ; i < len; i++ ) {
		if ( ( elem = unmatched[ i ] ) ) {
			if ( !filter || filter( elem, context, xml ) ) {
				newUnmatched.push( elem );
				if ( mapped ) {
					map.push( i );
				}
			}
		}
	}

	return newUnmatched;
}

function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
	if ( postFilter && !postFilter[ expando ] ) {
		postFilter = setMatcher( postFilter );
	}
	if ( postFinder && !postFinder[ expando ] ) {
		postFinder = setMatcher( postFinder, postSelector );
	}
	return markFunction( function( seed, results, context, xml ) {
		var temp, i, elem, matcherOut,
			preMap = [],
			postMap = [],
			preexisting = results.length,

			// Get initial elements from seed or context
			elems = seed ||
				multipleContexts( selector || "*",
					context.nodeType ? [ context ] : context, [] ),

			// Prefilter to get matcher input, preserving a map for seed-results synchronization
			matcherIn = preFilter && ( seed || !selector ) ?
				condense( elems, preMap, preFilter, context, xml ) :
				elems;

		if ( matcher ) {

			// If we have a postFinder, or filtered seed, or non-seed postFilter
			// or preexisting results,
			matcherOut = postFinder || ( seed ? preFilter : preexisting || postFilter ) ?

				// ...intermediate processing is necessary
				[] :

				// ...otherwise use results directly
				results;

			// Find primary matches
			matcher( matcherIn, matcherOut, context, xml );
		} else {
			matcherOut = matcherIn;
		}

		// Apply postFilter
		if ( postFilter ) {
			temp = condense( matcherOut, postMap );
			postFilter( temp, [], context, xml );

			// Un-match failing elements by moving them back to matcherIn
			i = temp.length;
			while ( i-- ) {
				if ( ( elem = temp[ i ] ) ) {
					matcherOut[ postMap[ i ] ] = !( matcherIn[ postMap[ i ] ] = elem );
				}
			}
		}

		if ( seed ) {
			if ( postFinder || preFilter ) {
				if ( postFinder ) {

					// Get the final matcherOut by condensing this intermediate into postFinder contexts
					temp = [];
					i = matcherOut.length;
					while ( i-- ) {
						if ( ( elem = matcherOut[ i ] ) ) {

							// Restore matcherIn since elem is not yet a final match
							temp.push( ( matcherIn[ i ] = elem ) );
						}
					}
					postFinder( null, ( matcherOut = [] ), temp, xml );
				}

				// Move matched elements from seed to results to keep them synchronized
				i = matcherOut.length;
				while ( i-- ) {
					if ( ( elem = matcherOut[ i ] ) &&
						( temp = postFinder ? indexOf.call( seed, elem ) : preMap[ i ] ) > -1 ) {

						seed[ temp ] = !( results[ temp ] = elem );
					}
				}
			}

		// Add elements to results, through postFinder if defined
		} else {
			matcherOut = condense(
				matcherOut === results ?
					matcherOut.splice( preexisting, matcherOut.length ) :
					matcherOut
			);
			if ( postFinder ) {
				postFinder( null, results, matcherOut, xml );
			} else {
				push.apply( results, matcherOut );
			}
		}
	} );
}

function matcherFromTokens( tokens ) {
	var checkContext, matcher, j,
		len = tokens.length,
		leadingRelative = Expr.relative[ tokens[ 0 ].type ],
		implicitRelative = leadingRelative || Expr.relative[ " " ],
		i = leadingRelative ? 1 : 0,

		// The foundational matcher ensures that elements are reachable from top-level context(s)
		matchContext = addCombinator( function( elem ) {
			return elem === checkContext;
		}, implicitRelative, true ),
		matchAnyContext = addCombinator( function( elem ) {
			return indexOf.call( checkContext, elem ) > -1;
		}, implicitRelative, true ),
		matchers = [ function( elem, context, xml ) {

			// Support: IE 11+, Edge 17 - 18+
			// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
			// two documents; shallow comparisons work.
			// eslint-disable-next-line eqeqeq
			var ret = ( !leadingRelative && ( xml || context != outermostContext ) ) || (
				( checkContext = context ).nodeType ?
					matchContext( elem, context, xml ) :
					matchAnyContext( elem, context, xml ) );

			// Avoid hanging onto element
			// (see https://github.com/jquery/sizzle/issues/299)
			checkContext = null;
			return ret;
		} ];

	for ( ; i < len; i++ ) {
		if ( ( matcher = Expr.relative[ tokens[ i ].type ] ) ) {
			matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
		} else {
			matcher = Expr.filter[ tokens[ i ].type ].apply( null, tokens[ i ].matches );

			// Return special upon seeing a positional matcher
			if ( matcher[ expando ] ) {

				// Find the next relative operator (if any) for proper handling
				j = ++i;
				for ( ; j < len; j++ ) {
					if ( Expr.relative[ tokens[ j ].type ] ) {
						break;
					}
				}
				return setMatcher(
					i > 1 && elementMatcher( matchers ),
					i > 1 && toSelector(

						// If the preceding token was a descendant combinator, insert an implicit any-element `*`
						tokens.slice( 0, i - 1 )
							.concat( { value: tokens[ i - 2 ].type === " " ? "*" : "" } )
					).replace( rtrimCSS, "$1" ),
					matcher,
					i < j && matcherFromTokens( tokens.slice( i, j ) ),
					j < len && matcherFromTokens( ( tokens = tokens.slice( j ) ) ),
					j < len && toSelector( tokens )
				);
			}
			matchers.push( matcher );
		}
	}

	return elementMatcher( matchers );
}

function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
	var bySet = setMatchers.length > 0,
		byElement = elementMatchers.length > 0,
		superMatcher = function( seed, context, xml, results, outermost ) {
			var elem, j, matcher,
				matchedCount = 0,
				i = "0",
				unmatched = seed && [],
				setMatched = [],
				contextBackup = outermostContext,

				// We must always have either seed elements or outermost context
				elems = seed || byElement && Expr.find.TAG( "*", outermost ),

				// Use integer dirruns iff this is the outermost matcher
				dirrunsUnique = ( dirruns += contextBackup == null ? 1 : Math.random() || 0.1 ),
				len = elems.length;

			if ( outermost ) {

				// Support: IE 11+, Edge 17 - 18+
				// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
				// two documents; shallow comparisons work.
				// eslint-disable-next-line eqeqeq
				outermostContext = context == document || context || outermost;
			}

			// Add elements passing elementMatchers directly to results
			// Support: iOS <=7 - 9 only
			// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching
			// elements by id. (see trac-14142)
			for ( ; i !== len && ( elem = elems[ i ] ) != null; i++ ) {
				if ( byElement && elem ) {
					j = 0;

					// Support: IE 11+, Edge 17 - 18+
					// IE/Edge sometimes throw a "Permission denied" error when strict-comparing
					// two documents; shallow comparisons work.
					// eslint-disable-next-line eqeqeq
					if ( !context && elem.ownerDocument != document ) {
						setDocument( elem );
						xml = !documentIsHTML;
					}
					while ( ( matcher = elementMatchers[ j++ ] ) ) {
						if ( matcher( elem, context || document, xml ) ) {
							push.call( results, elem );
							break;
						}
					}
					if ( outermost ) {
						dirruns = dirrunsUnique;
					}
				}

				// Track unmatched elements for set filters
				if ( bySet ) {

					// They will have gone through all possible matchers
					if ( ( elem = !matcher && elem ) ) {
						matchedCount--;
					}

					// Lengthen the array for every element, matched or not
					if ( seed ) {
						unmatched.push( elem );
					}
				}
			}

			// `i` is now the count of elements visited above, and adding it to `matchedCount`
			// makes the latter nonnegative.
			matchedCount += i;

			// Apply set filters to unmatched elements
			// NOTE: This can be skipped if there are no unmatched elements (i.e., `matchedCount`
			// equals `i`), unless we didn't visit _any_ elements in the above loop because we have
			// no element matchers and no seed.
			// Incrementing an initially-string "0" `i` allows `i` to remain a string only in that
			// case, which will result in a "00" `matchedCount` that differs from `i` but is also
			// numerically zero.
			if ( bySet && i !== matchedCount ) {
				j = 0;
				while ( ( matcher = setMatchers[ j++ ] ) ) {
					matcher( unmatched, setMatched, context, xml );
				}

				if ( seed ) {

					// Reintegrate element matches to eliminate the need for sorting
					if ( matchedCount > 0 ) {
						while ( i-- ) {
							if ( !( unmatched[ i ] || setMatched[ i ] ) ) {
								setMatched[ i ] = pop.call( results );
							}
						}
					}

					// Discard index placeholder values to get only actual matches
					setMatched = condense( setMatched );
				}

				// Add matches to results
				push.apply( results, setMatched );

				// Seedless set matches succeeding multiple successful matchers stipulate sorting
				if ( outermost && !seed && setMatched.length > 0 &&
					( matchedCount + setMatchers.length ) > 1 ) {

					jQuery.uniqueSort( results );
				}
			}

			// Override manipulation of globals by nested matchers
			if ( outermost ) {
				dirruns = dirrunsUnique;
				outermostContext = contextBackup;
			}

			return unmatched;
		};

	return bySet ?
		markFunction( superMatcher ) :
		superMatcher;
}

function compile( selector, match /* Internal Use Only */ ) {
	var i,
		setMatchers = [],
		elementMatchers = [],
		cached = compilerCache[ selector + " " ];

	if ( !cached ) {

		// Generate a function of recursive functions that can be used to check each element
		if ( !match ) {
			match = tokenize( selector );
		}
		i = match.length;
		while ( i-- ) {
			cached = matcherFromTokens( match[ i ] );
			if ( cached[ expando ] ) {
				setMatchers.push( cached );
			} else {
				elementMatchers.push( cached );
			}
		}

		// Cache the compiled function
		cached = compilerCache( selector,
			matcherFromGroupMatchers( elementMatchers, setMatchers ) );

		// Save selector and tokenization
		cached.selector = selector;
	}
	return cached;
}

/**
 * A low-level selection function that works with jQuery's compiled
 *  selector functions
 * @param {String|Function} selector A selector or a pre-compiled
 *  selector function built with jQuery selector compile
 * @param {Element} context
 * @param {Array} [results]
 * @param {Array} [seed] A set of elements to match against
 */
function select( selector, context, results, seed ) {
	var i, tokens, token, type, find,
		compiled = typeof selector === "function" && selector,
		match = !seed && tokenize( ( selector = compiled.selector || selector ) );

	results = results || [];

	// Try to minimize operations if there is only one selector in the list and no seed
	// (the latter of which guarantees us context)
	if ( match.length === 1 ) {

		// Reduce context if the leading compound selector is an ID
		tokens = match[ 0 ] = match[ 0 ].slice( 0 );
		if ( tokens.length > 2 && ( token = tokens[ 0 ] ).type === "ID" &&
				context.nodeType === 9 && documentIsHTML && Expr.relative[ tokens[ 1 ].type ] ) {

			context = ( Expr.find.ID(
				token.matches[ 0 ].replace( runescape, funescape ),
				context
			) || [] )[ 0 ];
			if ( !context ) {
				return results;

			// Precompiled matchers will still verify ancestry, so step up a level
			} else if ( compiled ) {
				context = context.parentNode;
			}

			selector = selector.slice( tokens.shift().value.length );
		}

		// Fetch a seed set for right-to-left matching
		i = matchExpr.needsContext.test( selector ) ? 0 : tokens.length;
		while ( i-- ) {
			token = tokens[ i ];

			// Abort if we hit a combinator
			if ( Expr.relative[ ( type = token.type ) ] ) {
				break;
			}
			if ( ( find = Expr.find[ type ] ) ) {

				// Search, expanding context for leading sibling combinators
				if ( ( seed = find(
					token.matches[ 0 ].replace( runescape, funescape ),
					rsibling.test( tokens[ 0 ].type ) &&
						testContext( context.parentNode ) || context
				) ) ) {

					// If seed is empty or no tokens remain, we can return early
					tokens.splice( i, 1 );
					selector = seed.length && toSelector( tokens );
					if ( !selector ) {
						push.apply( results, seed );
						return results;
					}

					break;
				}
			}
		}
	}

	// Compile and execute a filtering function if one is not provided
	// Provide `match` to avoid retokenization if we modified the selector above
	( compiled || compile( selector, match ) )(
		seed,
		context,
		!documentIsHTML,
		results,
		!context || rsibling.test( selector ) && testContext( context.parentNode ) || context
	);
	return results;
}

// One-time assignments

// Support: Android <=4.0 - 4.1+
// Sort stability
support.sortStable = expando.split( "" ).sort( sortOrder ).join( "" ) === expando;

// Initialize against the default document
setDocument();

// Support: Android <=4.0 - 4.1+
// Detached nodes confoundingly follow *each other*
support.sortDetached = assert( function( el ) {

	// Should return 1, but returns 4 (following)
	return el.compareDocumentPosition( document.createElement( "fieldset" ) ) & 1;
} );

jQuery.find = find;

// Deprecated
jQuery.expr[ ":" ] = jQuery.expr.pseudos;
jQuery.unique = jQuery.uniqueSort;

// These have always been private, but they used to be documented
// as part of Sizzle so let's maintain them in the 3.x line
// for backwards compatibility purposes.
find.compile = compile;
find.select = select;
find.setDocument = setDocument;

find.escape = jQuery.escapeSelector;
find.getText = jQuery.text;
find.isXML = jQuery.isXMLDoc;
find.selectors = jQuery.expr;
find.support = jQuery.support;
find.uniqueSort = jQuery.uniqueSort;

	/* eslint-enable */

} )();


var dir = function( elem, dir, until ) {
	var matched = [],
		truncate = until !== undefined;

	while ( ( elem = elem[ dir ] ) && elem.nodeType !== 9 ) {
		if ( elem.nodeType === 1 ) {
			if ( truncate && jQuery( elem ).is( until ) ) {
				break;
			}
			matched.push( elem );
		}
	}
	return matched;
};


var siblings = function( n, elem ) {
	var matched = [];

	for ( ; n; n = n.nextSibling ) {
		if ( n.nodeType === 1 && n !== elem ) {
			matched.push( n );
		}
	}

	return matched;
};


var rneedsContext = jQuery.expr.match.needsContext;

var rsingleTag = ( /^<([a-z][^\/\0>:\x20\t\r\n\f]*)[\x20\t\r\n\f]*\/?>(?:<\/\1>|)$/i );



// Implement the identical functionality for filter and not
function winnow( elements, qualifier, not ) {
	if ( isFunction( qualifier ) ) {
		return jQuery.grep( elements, function( elem, i ) {
			return !!qualifier.call( elem, i, elem ) !== not;
		} );
	}

	// Single element
	if ( qualifier.nodeType ) {
		return jQuery.grep( elements, function( elem ) {
			return ( elem === qualifier ) !== not;
		} );
	}

	// Arraylike of elements (jQuery, arguments, Array)
	if ( typeof qualifier !== "string" ) {
		return jQuery.grep( elements, function( elem ) {
			return ( indexOf.call( qualifier, elem ) > -1 ) !== not;
		} );
	}

	// Filtered directly for both simple and complex selectors
	return jQuery.filter( qualifier, elements, not );
}

jQuery.filter = function( expr, elems, not ) {
	var elem = elems[ 0 ];

	if ( not ) {
		expr = ":not(" + expr + ")";
	}

	if ( elems.length === 1 && elem.nodeType === 1 ) {
		return jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [];
	}

	return jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
		return elem.nodeType === 1;
	} ) );
};

jQuery.fn.extend( {
	find: function( selector ) {
		var i, ret,
			len = this.length,
			self = this;

		if ( typeof selector !== "string" ) {
			return this.pushStack( jQuery( selector ).filter( function() {
				for ( i = 0; i < len; i++ ) {
					if ( jQuery.contains( self[ i ], this ) ) {
						return true;
					}
				}
			} ) );
		}

		ret = this.pushStack( [] );

		for ( i = 0; i < len; i++ ) {
			jQuery.find( selector, self[ i ], ret );
		}

		return len > 1 ? jQuery.uniqueSort( ret ) : ret;
	},
	filter: function( selector ) {
		return this.pushStack( winnow( this, selector || [], false ) );
	},
	not: function( selector ) {
		return this.pushStack( winnow( this, selector || [], true ) );
	},
	is: function( selector ) {
		return !!winnow(
			this,

			// If this is a positional/relative selector, check membership in the returned set
			// so $("p:first").is("p:last") won't return true for a doc with two "p".
			typeof selector === "string" && rneedsContext.test( selector ) ?
				jQuery( selector ) :
				selector || [],
			false
		).length;
	}
} );


// Initialize a jQuery object


// A central reference to the root jQuery(document)
var rootjQuery,

	// A simple way to check for HTML strings
	// Prioritize #id over <tag> to avoid XSS via location.hash (trac-9521)
	// Strict HTML recognition (trac-11290: must start with <)
	// Shortcut simple #id case for speed
	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/,

	init = jQuery.fn.init = function( selector, context, root ) {
		var match, elem;

		// HANDLE: $(""), $(null), $(undefined), $(false)
		if ( !selector ) {
			return this;
		}

		// Method init() accepts an alternate rootjQuery
		// so migrate can support jQuery.sub (gh-2101)
		root = root || rootjQuery;

		// Handle HTML strings
		if ( typeof selector === "string" ) {
			if ( selector[ 0 ] === "<" &&
				selector[ selector.length - 1 ] === ">" &&
				selector.length >= 3 ) {

				// Assume that strings that start and end with <> are HTML and skip the regex check
				match = [ null, selector, null ];

			} else {
				match = rquickExpr.exec( selector );
			}

			// Match html or make sure no context is specified for #id
			if ( match && ( match[ 1 ] || !context ) ) {

				// HANDLE: $(html) -> $(array)
				if ( match[ 1 ] ) {
					context = context instanceof jQuery ? context[ 0 ] : context;

					// Option to run scripts is true for back-compat
					// Intentionally let the error be thrown if parseHTML is not present
					jQuery.merge( this, jQuery.parseHTML(
						match[ 1 ],
						context && context.nodeType ? context.ownerDocument || context : document,
						true
					) );

					// HANDLE: $(html, props)
					if ( rsingleTag.test( match[ 1 ] ) && jQuery.isPlainObject( context ) ) {
						for ( match in context ) {

							// Properties of context are called as methods if possible
							if ( isFunction( this[ match ] ) ) {
								this[ match ]( context[ match ] );

							// ...and otherwise set as attributes
							} else {
								this.attr( match, context[ match ] );
							}
						}
					}

					return this;

				// HANDLE: $(#id)
				} else {
					elem = document.getElementById( match[ 2 ] );

					if ( elem ) {

						// Inject the element directly into the jQuery object
						this[ 0 ] = elem;
						this.length = 1;
					}
					return this;
				}

			// HANDLE: $(expr, $(...))
			} else if ( !context || context.jquery ) {
				return ( context || root ).find( selector );

			// HANDLE: $(expr, context)
			// (which is just equivalent to: $(context).find(expr)
			} else {
				return this.constructor( context ).find( selector );
			}

		// HANDLE: $(DOMElement)
		} else if ( selector.nodeType ) {
			this[ 0 ] = selector;
			this.length = 1;
			return this;

		// HANDLE: $(function)
		// Shortcut for document ready
		} else if ( isFunction( selector ) ) {
			return root.ready !== undefined ?
				root.ready( selector ) :

				// Execute immediately if ready is not present
				selector( jQuery );
		}

		return jQuery.makeArray( selector, this );
	};

// Give the init function the jQuery prototype for later instantiation
init.prototype = jQuery.fn;

// Initialize central reference
rootjQuery = jQuery( document );


var rparentsprev = /^(?:parents|prev(?:Until|All))/,

	// Methods guaranteed to produce a unique set when starting from a unique set
	guaranteedUnique = {
		children: true,
		contents: true,
		next: true,
		prev: true
	};

jQuery.fn.extend( {
	has: function( target ) {
		var targets = jQuery( target, this ),
			l = targets.length;

		return this.filter( function() {
			var i = 0;
			for ( ; i < l; i++ ) {
				if ( jQuery.contains( this, targets[ i ] ) ) {
					return true;
				}
			}
		} );
	},

	closest: function( selectors, context ) {
		var cur,
			i = 0,
			l = this.length,
			matched = [],
			targets = typeof selectors !== "string" && jQuery( selectors );

		// Positional selectors never match, since there's no _selection_ context
		if ( !rneedsContext.test( selectors ) ) {
			for ( ; i < l; i++ ) {
				for ( cur = this[ i ]; cur && cur !== context; cur = cur.parentNode ) {

					// Always skip document fragments
					if ( cur.nodeType < 11 && ( targets ?
						targets.index( cur ) > -1 :

						// Don't pass non-elements to jQuery#find
						cur.nodeType === 1 &&
							jQuery.find.matchesSelector( cur, selectors ) ) ) {

						matched.push( cur );
						break;
					}
				}
			}
		}

		return this.pushStack( matched.length > 1 ? jQuery.uniqueSort( matched ) : matched );
	},

	// Determine the position of an element within the set
	index: function( elem ) {

		// No argument, return index in parent
		if ( !elem ) {
			return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
		}

		// Index in selector
		if ( typeof elem === "string" ) {
			return indexOf.call( jQuery( elem ), this[ 0 ] );
		}

		// Locate the position of the desired element
		return indexOf.call( this,

			// If it receives a jQuery object, the first element is used
			elem.jquery ? elem[ 0 ] : elem
		);
	},

	add: function( selector, context ) {
		return this.pushStack(
			jQuery.uniqueSort(
				jQuery.merge( this.get(), jQuery( selector, context ) )
			)
		);
	},

	addBack: function( selector ) {
		return this.add( selector == null ?
			this.prevObject : this.prevObject.filter( selector )
		);
	}
} );

function sibling( cur, dir ) {
	while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
	return cur;
}

jQuery.each( {
	parent: function( elem ) {
		var parent = elem.parentNode;
		return parent && parent.nodeType !== 11 ? parent : null;
	},
	parents: function( elem ) {
		return dir( elem, "parentNode" );
	},
	parentsUntil: function( elem, _i, until ) {
		return dir( elem, "parentNode", until );
	},
	next: function( elem ) {
		return sibling( elem, "nextSibling" );
	},
	prev: function( elem ) {
		return sibling( elem, "previousSibling" );
	},
	nextAll: function( elem ) {
		return dir( elem, "nextSibling" );
	},
	prevAll: function( elem ) {
		return dir( elem, "previousSibling" );
	},
	nextUntil: function( elem, _i, until ) {
		return dir( elem, "nextSibling", until );
	},
	prevUntil: function( elem, _i, until ) {
		return dir( elem, "previousSibling", until );
	},
	siblings: function( elem ) {
		return siblings( ( elem.parentNode || {} ).firstChild, elem );
	},
	children: function( elem ) {
		return siblings( elem.firstChild );
	},
	contents: function( elem ) {
		if ( elem.contentDocument != null &&

			// Support: IE 11+
			// <object> elements with no `data` attribute has an object
			// `contentDocument` with a `null` prototype.
			getProto( elem.contentDocument ) ) {

			return elem.contentDocument;
		}

		// Support: IE 9 - 11 only, iOS 7 only, Android Browser <=4.3 only
		// Treat the template element as a regular one in browsers that
		// don't support it.
		if ( nodeName( elem, "template" ) ) {
			elem = elem.content || elem;
		}

		return jQuery.merge( [], elem.childNodes );
	}
}, function( name, fn ) {
	jQuery.fn[ name ] = function( until, selector ) {
		var matched = jQuery.map( this, fn, until );

		if ( name.slice( -5 ) !== "Until" ) {
			selector = until;
		}

		if ( selector && typeof selector === "string" ) {
			matched = jQuery.filter( selector, matched );
		}

		if ( this.length > 1 ) {

			// Remove duplicates
			if ( !guaranteedUnique[ name ] ) {
				jQuery.uniqueSort( matched );
			}

			// Reverse order for parents* and prev-derivatives
			if ( rparentsprev.test( name ) ) {
				matched.reverse();
			}
		}

		return this.pushStack( matched );
	};
} );
var rnothtmlwhite = ( /[^\x20\t\r\n\f]+/g );



// Convert String-formatted options into Object-formatted ones
function createOptions( options ) {
	var object = {};
	jQuery.each( options.match( rnothtmlwhite ) || [], function( _, flag ) {
		object[ flag ] = true;
	} );
	return object;
}

/*
 * Create a callback list using the following parameters:
 *
 *	options: an optional list of space-separated options that will change how
 *			the callback list behaves or a more traditional option object
 *
 * By default a callback list will act like an event callback list and can be
 * "fired" multiple times.
 *
 * Possible options:
 *
 *	once:			will ensure the callback list can only be fired once (like a Deferred)
 *
 *	memory:			will keep track of previous values and will call any callback added
 *					after the list has been fired right away with the latest "memorized"
 *					values (like a Deferred)
 *
 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
 *
 *	stopOnFalse:	interrupt callings when a callback returns false
 *
 */
jQuery.Callbacks = function( options ) {

	// Convert options from String-formatted to Object-formatted if needed
	// (we check in cache first)
	options = typeof options === "string" ?
		createOptions( options ) :
		jQuery.extend( {}, options );

	var // Flag to know if list is currently firing
		firing,

		// Last fire value for non-forgettable lists
		memory,

		// Flag to know if list was already fired
		fired,

		// Flag to prevent firing
		locked,

		// Actual callback list
		list = [],

		// Queue of execution data for repeatable lists
		queue = [],

		// Index of currently firing callback (modified by add/remove as needed)
		firingIndex = -1,

		// Fire callbacks
		fire = function() {

			// Enforce single-firing
			locked = locked || options.once;

			// Execute callbacks for all pending executions,
			// respecting firingIndex overrides and runtime changes
			fired = firing = true;
			for ( ; queue.length; firingIndex = -1 ) {
				memory = queue.shift();
				while ( ++firingIndex < list.length ) {

					// Run callback and check for early termination
					if ( list[ firingIndex ].apply( memory[ 0 ], memory[ 1 ] ) === false &&
						options.stopOnFalse ) {

						// Jump to end and forget the data so .add doesn't re-fire
						firingIndex = list.length;
						memory = false;
					}
				}
			}

			// Forget the data if we're done with it
			if ( !options.memory ) {
				memory = false;
			}

			firing = false;

			// Clean up if we're done firing for good
			if ( locked ) {

				// Keep an empty list if we have data for future add calls
				if ( memory ) {
					list = [];

				// Otherwise, this object is spent
				} else {
					list = "";
				}
			}
		},

		// Actual Callbacks object
		self = {

			// Add a callback or a collection of callbacks to the list
			add: function() {
				if ( list ) {

					// If we have memory from a past run, we should fire after adding
					if ( memory && !firing ) {
						firingIndex = list.length - 1;
						queue.push( memory );
					}

					( function add( args ) {
						jQuery.each( args, function( _, arg ) {
							if ( isFunction( arg ) ) {
								if ( !options.unique || !self.has( arg ) ) {
									list.push( arg );
								}
							} else if ( arg && arg.length && toType( arg ) !== "string" ) {

								// Inspect recursively
								add( arg );
							}
						} );
					} )( arguments );

					if ( memory && !firing ) {
						fire();
					}
				}
				return this;
			},

			// Remove a callback from the list
			remove: function() {
				jQuery.each( arguments, function( _, arg ) {
					var index;
					while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
						list.splice( index, 1 );

						// Handle firing indexes
						if ( index <= firingIndex ) {
							firingIndex--;
						}
					}
				} );
				return this;
			},

			// Check if a given callback is in the list.
			// If no argument is given, return whether or not list has callbacks attached.
			has: function( fn ) {
				return fn ?
					jQuery.inArray( fn, list ) > -1 :
					list.length > 0;
			},

			// Remove all callbacks from the list
			empty: function() {
				if ( list ) {
					list = [];
				}
				return this;
			},

			// Disable .fire and .add
			// Abort any current/pending executions
			// Clear all callbacks and values
			disable: function() {
				locked = queue = [];
				list = memory = "";
				return this;
			},
			disabled: function() {
				return !list;
			},

			// Disable .fire
			// Also disable .add unless we have memory (since it would have no effect)
			// Abort any pending executions
			lock: function() {
				locked = queue = [];
				if ( !memory && !firing ) {
					list = memory = "";
				}
				return this;
			},
			locked: function() {
				return !!locked;
			},

			// Call all callbacks with the given context and arguments
			fireWith: function( context, args ) {
				if ( !locked ) {
					args = args || [];
					args = [ context, args.slice ? args.slice() : args ];
					queue.push( args );
					if ( !firing ) {
						fire();
					}
				}
				return this;
			},

			// Call all the callbacks with the given arguments
			fire: function() {
				self.fireWith( this, arguments );
				return this;
			},

			// To know if the callbacks have already been called at least once
			fired: function() {
				return !!fired;
			}
		};

	return self;
};


function Identity( v ) {
	return v;
}
function Thrower( ex ) {
	throw ex;
}

function adoptValue( value, resolve, reject, noValue ) {
	var method;

	try {

		// Check for promise aspect first to privilege synchronous behavior
		if ( value && isFunction( ( method = value.promise ) ) ) {
			method.call( value ).done( resolve ).fail( reject );

		// Other thenables
		} else if ( value && isFunction( ( method = value.then ) ) ) {
			method.call( value, resolve, reject );

		// Other non-thenables
		} else {

			// Control `resolve` arguments by letting Array#slice cast boolean `noValue` to integer:
			// * false: [ value ].slice( 0 ) => resolve( value )
			// * true: [ value ].slice( 1 ) => resolve()
			resolve.apply( undefined, [ value ].slice( noValue ) );
		}

	// For Promises/A+, convert exceptions into rejections
	// Since jQuery.when doesn't unwrap thenables, we can skip the extra checks appearing in
	// Deferred#then to conditionally suppress rejection.
	} catch ( value ) {

		// Support: Android 4.0 only
		// Strict mode functions invoked without .call/.apply get global-object context
		reject.apply( undefined, [ value ] );
	}
}

jQuery.extend( {

	Deferred: function( func ) {
		var tuples = [

				// action, add listener, callbacks,
				// ... .then handlers, argument index, [final state]
				[ "notify", "progress", jQuery.Callbacks( "memory" ),
					jQuery.Callbacks( "memory" ), 2 ],
				[ "resolve", "done", jQuery.Callbacks( "once memory" ),
					jQuery.Callbacks( "once memory" ), 0, "resolved" ],
				[ "reject", "fail", jQuery.Callbacks( "once memory" ),
					jQuery.Callbacks( "once memory" ), 1, "rejected" ]
			],
			state = "pending",
			promise = {
				state: function() {
					return state;
				},
				always: function() {
					deferred.done( arguments ).fail( arguments );
					return this;
				},
				"catch": function( fn ) {
					return promise.then( null, fn );
				},

				// Keep pipe for back-compat
				pipe: function( /* fnDone, fnFail, fnProgress */ ) {
					var fns = arguments;

					return jQuery.Deferred( function( newDefer ) {
						jQuery.each( tuples, function( _i, tuple ) {

							// Map tuples (progress, done, fail) to arguments (done, fail, progress)
							var fn = isFunction( fns[ tuple[ 4 ] ] ) && fns[ tuple[ 4 ] ];

							// deferred.progress(function() { bind to newDefer or newDefer.notify })
							// deferred.done(function() { bind to newDefer or newDefer.resolve })
							// deferred.fail(function() { bind to newDefer or newDefer.reject })
							deferred[ tuple[ 1 ] ]( function() {
								var returned = fn && fn.apply( this, arguments );
								if ( returned && isFunction( returned.promise ) ) {
									returned.promise()
										.progress( newDefer.notify )
										.done( newDefer.resolve )
										.fail( newDefer.reject );
								} else {
									newDefer[ tuple[ 0 ] + "With" ](
										this,
										fn ? [ returned ] : arguments
									);
								}
							} );
						} );
						fns = null;
					} ).promise();
				},
				then: function( onFulfilled, onRejected, onProgress ) {
					var maxDepth = 0;
					function resolve( depth, deferred, handler, special ) {
						return function() {
							var that = this,
								args = arguments,
								mightThrow = function() {
									var returned, then;

									// Support: Promises/A+ section 2.3.3.3.3
									// https://promisesaplus.com/#point-59
									// Ignore double-resolution attempts
									if ( depth < maxDepth ) {
										return;
									}

									returned = handler.apply( that, args );

									// Support: Promises/A+ section 2.3.1
									// https://promisesaplus.com/#point-48
									if ( returned === deferred.promise() ) {
										throw new TypeError( "Thenable self-resolution" );
									}

									// Support: Promises/A+ sections 2.3.3.1, 3.5
									// https://promisesaplus.com/#point-54
									// https://promisesaplus.com/#point-75
									// Retrieve `then` only once
									then = returned &&

										// Support: Promises/A+ section 2.3.4
										// https://promisesaplus.com/#point-64
										// Only check objects and functions for thenability
										( typeof returned === "object" ||
											typeof returned === "function" ) &&
										returned.then;

									// Handle a returned thenable
									if ( isFunction( then ) ) {

										// Special processors (notify) just wait for resolution
										if ( special ) {
											then.call(
												returned,
												resolve( maxDepth, deferred, Identity, special ),
												resolve( maxDepth, deferred, Thrower, special )
											);

										// Normal processors (resolve) also hook into progress
										} else {

											// ...and disregard older resolution values
											maxDepth++;

											then.call(
												returned,
												resolve( maxDepth, deferred, Identity, special ),
												resolve( maxDepth, deferred, Thrower, special ),
												resolve( maxDepth, deferred, Identity,
													deferred.notifyWith )
											);
										}

									// Handle all other returned values
									} else {

										// Only substitute handlers pass on context
										// and multiple values (non-spec behavior)
										if ( handler !== Identity ) {
											that = undefined;
											args = [ returned ];
										}

										// Process the value(s)
										// Default process is resolve
										( special || deferred.resolveWith )( that, args );
									}
								},

								// Only normal processors (resolve) catch and reject exceptions
								process = special ?
									mightThrow :
									function() {
										try {
											mightThrow();
										} catch ( e ) {

											if ( jQuery.Deferred.exceptionHook ) {
												jQuery.Deferred.exceptionHook( e,
													process.error );
											}

											// Support: Promises/A+ section 2.3.3.3.4.1
											// https://promisesaplus.com/#point-61
											// Ignore post-resolution exceptions
											if ( depth + 1 >= maxDepth ) {

												// Only substitute handlers pass on context
												// and multiple values (non-spec behavior)
												if ( handler !== Thrower ) {
													that = undefined;
													args = [ e ];
												}

												deferred.rejectWith( that, args );
											}
										}
									};

							// Support: Promises/A+ section 2.3.3.3.1
							// https://promisesaplus.com/#point-57
							// Re-resolve promises immediately to dodge false rejection from
							// subsequent errors
							if ( depth ) {
								process();
							} else {

								// Call an optional hook to record the error, in case of exception
								// since it's otherwise lost when execution goes async
								if ( jQuery.Deferred.getErrorHook ) {
									process.error = jQuery.Deferred.getErrorHook();

								// The deprecated alias of the above. While the name suggests
								// returning the stack, not an error instance, jQuery just passes
								// it directly to `console.warn` so both will work; an instance
								// just better cooperates with source maps.
								} else if ( jQuery.Deferred.getStackHook ) {
									process.error = jQuery.Deferred.getStackHook();
								}
								window.setTimeout( process );
							}
						};
					}

					return jQuery.Deferred( function( newDefer ) {

						// progress_handlers.add( ... )
						tuples[ 0 ][ 3 ].add(
							resolve(
								0,
								newDefer,
								isFunction( onProgress ) ?
									onProgress :
									Identity,
								newDefer.notifyWith
							)
						);

						// fulfilled_handlers.add( ... )
						tuples[ 1 ][ 3 ].add(
							resolve(
								0,
								newDefer,
								isFunction( onFulfilled ) ?
									onFulfilled :
									Identity
							)
						);

						// rejected_handlers.add( ... )
						tuples[ 2 ][ 3 ].add(
							resolve(
								0,
								newDefer,
								isFunction( onRejected ) ?
									onRejected :
									Thrower
							)
						);
					} ).promise();
				},

				// Get a promise for this deferred
				// If obj is provided, the promise aspect is added to the object
				promise: function( obj ) {
					return obj != null ? jQuery.extend( obj, promise ) : promise;
				}
			},
			deferred = {};

		// Add list-specific methods
		jQuery.each( tuples, function( i, tuple ) {
			var list = tuple[ 2 ],
				stateString = tuple[ 5 ];

			// promise.progress = list.add
			// promise.done = list.add
			// promise.fail = list.add
			promise[ tuple[ 1 ] ] = list.add;

			// Handle state
			if ( stateString ) {
				list.add(
					function() {

						// state = "resolved" (i.e., fulfilled)
						// state = "rejected"
						state = stateString;
					},

					// rejected_callbacks.disable
					// fulfilled_callbacks.disable
					tuples[ 3 - i ][ 2 ].disable,

					// rejected_handlers.disable
					// fulfilled_handlers.disable
					tuples[ 3 - i ][ 3 ].disable,

					// progress_callbacks.lock
					tuples[ 0 ][ 2 ].lock,

					// progress_handlers.lock
					tuples[ 0 ][ 3 ].lock
				);
			}

			// progress_handlers.fire
			// fulfilled_handlers.fire
			// rejected_handlers.fire
			list.add( tuple[ 3 ].fire );

			// deferred.notify = function() { deferred.notifyWith(...) }
			// deferred.resolve = function() { deferred.resolveWith(...) }
			// deferred.reject = function() { deferred.rejectWith(...) }
			deferred[ tuple[ 0 ] ] = function() {
				deferred[ tuple[ 0 ] + "With" ]( this === deferred ? undefined : this, arguments );
				return this;
			};

			// deferred.notifyWith = list.fireWith
			// deferred.resolveWith = list.fireWith
			// deferred.rejectWith = list.fireWith
			deferred[ tuple[ 0 ] + "With" ] = list.fireWith;
		} );

		// Make the deferred a promise
		promise.promise( deferred );

		// Call given func if any
		if ( func ) {
			func.call( deferred, deferred );
		}

		// All done!
		return deferred;
	},

	// Deferred helper
	when: function( singleValue ) {
		var

			// count of uncompleted subordinates
			remaining = arguments.length,

			// count of unprocessed arguments
			i = remaining,

			// subordinate fulfillment data
			resolveContexts = Array( i ),
			resolveValues = slice.call( arguments ),

			// the primary Deferred
			primary = jQuery.Deferred(),

			// subordinate callback factory
			updateFunc = function( i ) {
				return function( value ) {
					resolveContexts[ i ] = this;
					resolveValues[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
					if ( !( --remaining ) ) {
						primary.resolveWith( resolveContexts, resolveValues );
					}
				};
			};

		// Single- and empty arguments are adopted like Promise.resolve
		if ( remaining <= 1 ) {
			adoptValue( singleValue, primary.done( updateFunc( i ) ).resolve, primary.reject,
				!remaining );

			// Use .then() to unwrap secondary thenables (cf. gh-3000)
			if ( primary.state() === "pending" ||
				isFunction( resolveValues[ i ] && resolveValues[ i ].then ) ) {

				return primary.then();
			}
		}

		// Multiple arguments are aggregated like Promise.all array elements
		while ( i-- ) {
			adoptValue( resolveValues[ i ], updateFunc( i ), primary.reject );
		}

		return primary.promise();
	}
} );


// These usually indicate a programmer mistake during development,
// warn about them ASAP rather than swallowing them by default.
var rerrorNames = /^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;

// If `jQuery.Deferred.getErrorHook` is defined, `asyncError` is an error
// captured before the async barrier to get the original error cause
// which may otherwise be hidden.
jQuery.Deferred.exceptionHook = function( error, asyncError ) {

	// Support: IE 8 - 9 only
	// Console exists when dev tools are open, which can happen at any time
	if ( window.console && window.console.warn && error && rerrorNames.test( error.name ) ) {
		window.console.warn( "jQuery.Deferred exception: " + error.message,
			error.stack, asyncError );
	}
};




jQuery.readyException = function( error ) {
	window.setTimeout( function() {
		throw error;
	} );
};




// The deferred used on DOM ready
var readyList = jQuery.Deferred();

jQuery.fn.ready = function( fn ) {

	readyList
		.then( fn )

		// Wrap jQuery.readyException in a function so that the lookup
		// happens at the time of error handling instead of callback
		// registration.
		.catch( function( error ) {
			jQuery.readyException( error );
		} );

	return this;
};

jQuery.extend( {

	// Is the DOM ready to be used? Set to true once it occurs.
	isReady: false,

	// A counter to track how many items to wait for before
	// the ready event fires. See trac-6781
	readyWait: 1,

	// Handle when the DOM is ready
	ready: function( wait ) {

		// Abort if there are pending holds or we're already ready
		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
			return;
		}

		// Remember that the DOM is ready
		jQuery.isReady = true;

		// If a normal DOM Ready event fired, decrement, and wait if need be
		if ( wait !== true && --jQuery.readyWait > 0 ) {
			return;
		}

		// If there are functions bound, to execute
		readyList.resolveWith( document, [ jQuery ] );
	}
} );

jQuery.ready.then = readyList.then;

// The ready event handler and self cleanup method
function completed() {
	document.removeEventListener( "DOMContentLoaded", completed );
	window.removeEventListener( "load", completed );
	jQuery.ready();
}

// Catch cases where $(document).ready() is called
// after the browser event has already occurred.
// Support: IE <=9 - 10 only
// Older IE sometimes signals "interactive" too soon
if ( document.readyState === "complete" ||
	( document.readyState !== "loading" && !document.documentElement.doScroll ) ) {

	// Handle it asynchronously to allow scripts the opportunity to delay ready
	window.setTimeout( jQuery.ready );

} else {

	// Use the handy event callback
	document.addEventListener( "DOMContentLoaded", completed );

	// A fallback to window.onload, that will always work
	window.addEventListener( "load", completed );
}




// Multifunctional method to get and set values of a collection
// The value/s can optionally be executed if it's a function
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
	var i = 0,
		len = elems.length,
		bulk = key == null;

	// Sets many values
	if ( toType( key ) === "object" ) {
		chainable = true;
		for ( i in key ) {
			access( elems, fn, i, key[ i ], true, emptyGet, raw );
		}

	// Sets one value
	} else if ( value !== undefined ) {
		chainable = true;

		if ( !isFunction( value ) ) {
			raw = true;
		}

		if ( bulk ) {

			// Bulk operations run against the entire set
			if ( raw ) {
				fn.call( elems, value );
				fn = null;

			// ...except when executing function values
			} else {
				bulk = fn;
				fn = function( elem, _key, value ) {
					return bulk.call( jQuery( elem ), value );
				};
			}
		}

		if ( fn ) {
			for ( ; i < len; i++ ) {
				fn(
					elems[ i ], key, raw ?
						value :
						value.call( elems[ i ], i, fn( elems[ i ], key ) )
				);
			}
		}
	}

	if ( chainable ) {
		return elems;
	}

	// Gets
	if ( bulk ) {
		return fn.call( elems );
	}

	return len ? fn( elems[ 0 ], key ) : emptyGet;
};


// Matches dashed string for camelizing
var rmsPrefix = /^-ms-/,
	rdashAlpha = /-([a-z])/g;

// Used by camelCase as callback to replace()
function fcamelCase( _all, letter ) {
	return letter.toUpperCase();
}

// Convert dashed to camelCase; used by the css and data modules
// Support: IE <=9 - 11, Edge 12 - 15
// Microsoft forgot to hump their vendor prefix (trac-9572)
function camelCase( string ) {
	return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
}
var acceptData = function( owner ) {

	// Accepts only:
	//  - Node
	//    - Node.ELEMENT_NODE
	//    - Node.DOCUMENT_NODE
	//  - Object
	//    - Any
	return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
};




function Data() {
	this.expando = jQuery.expando + Data.uid++;
}

Data.uid = 1;

Data.prototype = {

	cache: function( owner ) {

		// Check if the owner object already has a cache
		var value = owner[ this.expando ];

		// If not, create one
		if ( !value ) {
			value = {};

			// We can accept data for non-element nodes in modern browsers,
			// but we should not, see trac-8335.
			// Always return an empty object.
			if ( acceptData( owner ) ) {

				// If it is a node unlikely to be stringify-ed or looped over
				// use plain assignment
				if ( owner.nodeType ) {
					owner[ this.expando ] = value;

				// Otherwise secure it in a non-enumerable property
				// configurable must be true to allow the property to be
				// deleted when data is removed
				} else {
					Object.defineProperty( owner, this.expando, {
						value: value,
						configurable: true
					} );
				}
			}
		}

		return value;
	},
	set: function( owner, data, value ) {
		var prop,
			cache = this.cache( owner );

		// Handle: [ owner, key, value ] args
		// Always use camelCase key (gh-2257)
		if ( typeof data === "string" ) {
			cache[ camelCase( data ) ] = value;

		// Handle: [ owner, { properties } ] args
		} else {

			// Copy the properties one-by-one to the cache object
			for ( prop in data ) {
				cache[ camelCase( prop ) ] = data[ prop ];
			}
		}
		return cache;
	},
	get: function( owner, key ) {
		return key === undefined ?
			this.cache( owner ) :

			// Always use camelCase key (gh-2257)
			owner[ this.expando ] && owner[ this.expando ][ camelCase( key ) ];
	},
	access: function( owner, key, value ) {

		// In cases where either:
		//
		//   1. No key was specified
		//   2. A string key was specified, but no value provided
		//
		// Take the "read" path and allow the get method to determine
		// which value to return, respectively either:
		//
		//   1. The entire cache object
		//   2. The data stored at the key
		//
		if ( key === undefined ||
				( ( key && typeof key === "string" ) && value === undefined ) ) {

			return this.get( owner, key );
		}

		// When the key is not a string, or both a key and value
		// are specified, set or extend (existing objects) with either:
		//
		//   1. An object of properties
		//   2. A key and value
		//
		this.set( owner, key, value );

		// Since the "set" path can have two possible entry points
		// return the expected data based on which path was taken[*]
		return value !== undefined ? value : key;
	},
	remove: function( owner, key ) {
		var i,
			cache = owner[ this.expando ];

		if ( cache === undefined ) {
			return;
		}

		if ( key !== undefined ) {

			// Support array or space separated string of keys
			if ( Array.isArray( key ) ) {

				// If key is an array of keys...
				// We always set camelCase keys, so remove that.
				key = key.map( camelCase );
			} else {
				key = camelCase( key );

				// If a key with the spaces exists, use it.
				// Otherwise, create an array by matching non-whitespace
				key = key in cache ?
					[ key ] :
					( key.match( rnothtmlwhite ) || [] );
			}

			i = key.length;

			while ( i-- ) {
				delete cache[ key[ i ] ];
			}
		}

		// Remove the expando if there's no more data
		if ( key === undefined || jQuery.isEmptyObject( cache ) ) {

			// Support: Chrome <=35 - 45
			// Webkit & Blink performance suffers when deleting properties
			// from DOM nodes, so set to undefined instead
			// https://bugs.chromium.org/p/chromium/issues/detail?id=378607 (bug restricted)
			if ( owner.nodeType ) {
				owner[ this.expando ] = undefined;
			} else {
				delete owner[ this.expando ];
			}
		}
	},
	hasData: function( owner ) {
		var cache = owner[ this.expando ];
		return cache !== undefined && !jQuery.isEmptyObject( cache );
	}
};
var dataPriv = new Data();

var dataUser = new Data();



//	Implementation Summary
//
//	1. Enforce API surface and semantic compatibility with 1.9.x branch
//	2. Improve the module's maintainability by reducing the storage
//		paths to a single mechanism.
//	3. Use the same single mechanism to support "private" and "user" data.
//	4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
//	5. Avoid exposing implementation details on user objects (eg. expando properties)
//	6. Provide a clear path for implementation upgrade to WeakMap in 2014

var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
	rmultiDash = /[A-Z]/g;

function getData( data ) {
	if ( data === "true" ) {
		return true;
	}

	if ( data === "false" ) {
		return false;
	}

	if ( data === "null" ) {
		return null;
	}

	// Only convert to a number if it doesn't change the string
	if ( data === +data + "" ) {
		return +data;
	}

	if ( rbrace.test( data ) ) {
		return JSON.parse( data );
	}

	return data;
}

function dataAttr( elem, key, data ) {
	var name;

	// If nothing was found internally, try to fetch any
	// data from the HTML5 data-* attribute
	if ( data === undefined && elem.nodeType === 1 ) {
		name = "data-" + key.replace( rmultiDash, "-$&" ).toLowerCase();
		data = elem.getAttribute( name );

		if ( typeof data === "string" ) {
			try {
				data = getData( data );
			} catch ( e ) {}

			// Make sure we set the data so it isn't changed later
			dataUser.set( elem, key, data );
		} else {
			data = undefined;
		}
	}
	return data;
}

jQuery.extend( {
	hasData: function( elem ) {
		return dataUser.hasData( elem ) || dataPriv.hasData( elem );
	},

	data: function( elem, name, data ) {
		return dataUser.access( elem, name, data );
	},

	removeData: function( elem, name ) {
		dataUser.remove( elem, name );
	},

	// TODO: Now that all calls to _data and _removeData have been replaced
	// with direct calls to dataPriv methods, these can be deprecated.
	_data: function( elem, name, data ) {
		return dataPriv.access( elem, name, data );
	},

	_removeData: function( elem, name ) {
		dataPriv.remove( elem, name );
	}
} );

jQuery.fn.extend( {
	data: function( key, value ) {
		var i, name, data,
			elem = this[ 0 ],
			attrs = elem && elem.attributes;

		// Gets all values
		if ( key === undefined ) {
			if ( this.length ) {
				data = dataUser.get( elem );

				if ( elem.nodeType === 1 && !dataPriv.get( elem, "hasDataAttrs" ) ) {
					i = attrs.length;
					while ( i-- ) {

						// Support: IE 11 only
						// The attrs elements can be null (trac-14894)
						if ( attrs[ i ] ) {
							name = attrs[ i ].name;
							if ( name.indexOf( "data-" ) === 0 ) {
								name = camelCase( name.slice( 5 ) );
								dataAttr( elem, name, data[ name ] );
							}
						}
					}
					dataPriv.set( elem, "hasDataAttrs", true );
				}
			}

			return data;
		}

		// Sets multiple values
		if ( typeof key === "object" ) {
			return this.each( function() {
				dataUser.set( this, key );
			} );
		}

		return access( this, function( value ) {
			var data;

			// The calling jQuery object (element matches) is not empty
			// (and therefore has an element appears at this[ 0 ]) and the
			// `value` parameter was not undefined. An empty jQuery object
			// will result in `undefined` for elem = this[ 0 ] which will
			// throw an exception if an attempt to read a data cache is made.
			if ( elem && value === undefined ) {

				// Attempt to get data from the cache
				// The key will always be camelCased in Data
				data = dataUser.get( elem, key );
				if ( data !== undefined ) {
					return data;
				}

				// Attempt to "discover" the data in
				// HTML5 custom data-* attrs
				data = dataAttr( elem, key );
				if ( data !== undefined ) {
					return data;
				}

				// We tried really hard, but the data doesn't exist.
				return;
			}

			// Set the data...
			this.each( function() {

				// We always store the camelCased key
				dataUser.set( this, key, value );
			} );
		}, null, value, arguments.length > 1, null, true );
	},

	removeData: function( key ) {
		return this.each( function() {
			dataUser.remove( this, key );
		} );
	}
} );


jQuery.extend( {
	queue: function( elem, type, data ) {
		var queue;

		if ( elem ) {
			type = ( type || "fx" ) + "queue";
			queue = dataPriv.get( elem, type );

			// Speed up dequeue by getting out quickly if this is just a lookup
			if ( data ) {
				if ( !queue || Array.isArray( data ) ) {
					queue = dataPriv.access( elem, type, jQuery.makeArray( data ) );
				} else {
					queue.push( data );
				}
			}
			return queue || [];
		}
	},

	dequeue: function( elem, type ) {
		type = type || "fx";

		var queue = jQuery.queue( elem, type ),
			startLength = queue.length,
			fn = queue.shift(),
			hooks = jQuery._queueHooks( elem, type ),
			next = function() {
				jQuery.dequeue( elem, type );
			};

		// If the fx queue is dequeued, always remove the progress sentinel
		if ( fn === "inprogress" ) {
			fn = queue.shift();
			startLength--;
		}

		if ( fn ) {

			// Add a progress sentinel to prevent the fx queue from being
			// automatically dequeued
			if ( type === "fx" ) {
				queue.unshift( "inprogress" );
			}

			// Clear up the last queue stop function
			delete hooks.stop;
			fn.call( elem, next, hooks );
		}

		if ( !startLength && hooks ) {
			hooks.empty.fire();
		}
	},

	// Not public - generate a queueHooks object, or return the current one
	_queueHooks: function( elem, type ) {
		var key = type + "queueHooks";
		return dataPriv.get( elem, key ) || dataPriv.access( elem, key, {
			empty: jQuery.Callbacks( "once memory" ).add( function() {
				dataPriv.remove( elem, [ type + "queue", key ] );
			} )
		} );
	}
} );

jQuery.fn.extend( {
	queue: function( type, data ) {
		var setter = 2;

		if ( typeof type !== "string" ) {
			data = type;
			type = "fx";
			setter--;
		}

		if ( arguments.length < setter ) {
			return jQuery.queue( this[ 0 ], type );
		}

		return data === undefined ?
			this :
			this.each( function() {
				var queue = jQuery.queue( this, type, data );

				// Ensure a hooks for this queue
				jQuery._queueHooks( this, type );

				if ( type === "fx" && queue[ 0 ] !== "inprogress" ) {
					jQuery.dequeue( this, type );
				}
			} );
	},
	dequeue: function( type ) {
		return this.each( function() {
			jQuery.dequeue( this, type );
		} );
	},
	clearQueue: function( type ) {
		return this.queue( type || "fx", [] );
	},

	// Get a promise resolved when queues of a certain type
	// are emptied (fx is the type by default)
	promise: function( type, obj ) {
		var tmp,
			count = 1,
			defer = jQuery.Deferred(),
			elements = this,
			i = this.length,
			resolve = function() {
				if ( !( --count ) ) {
					defer.resolveWith( elements, [ elements ] );
				}
			};

		if ( typeof type !== "string" ) {
			obj = type;
			type = undefined;
		}
		type = type || "fx";

		while ( i-- ) {
			tmp = dataPriv.get( elements[ i ], type + "queueHooks" );
			if ( tmp && tmp.empty ) {
				count++;
				tmp.empty.add( resolve );
			}
		}
		resolve();
		return defer.promise( obj );
	}
} );
var pnum = ( /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/ ).source;

var rcssNum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" );


var cssExpand = [ "Top", "Right", "Bottom", "Left" ];

var documentElement = document.documentElement;



	var isAttached = function( elem ) {
			return jQuery.contains( elem.ownerDocument, elem );
		},
		composed = { composed: true };

	// Support: IE 9 - 11+, Edge 12 - 18+, iOS 10.0 - 10.2 only
	// Check attachment across shadow DOM boundaries when possible (gh-3504)
	// Support: iOS 10.0-10.2 only
	// Early iOS 10 versions support `attachShadow` but not `getRootNode`,
	// leading to errors. We need to check for `getRootNode`.
	if ( documentElement.getRootNode ) {
		isAttached = function( elem ) {
			return jQuery.contains( elem.ownerDocument, elem ) ||
				elem.getRootNode( composed ) === elem.ownerDocument;
		};
	}
var isHiddenWithinTree = function( elem, el ) {

		// isHiddenWithinTree might be called from jQuery#filter function;
		// in that case, element will be second argument
		elem = el || elem;

		// Inline style trumps all
		return elem.style.display === "none" ||
			elem.style.display === "" &&

			// Otherwise, check computed style
			// Support: Firefox <=43 - 45
			// Disconnected elements can have computed display: none, so first confirm that elem is
			// in the document.
			isAttached( elem ) &&

			jQuery.css( elem, "display" ) === "none";
	};



function adjustCSS( elem, prop, valueParts, tween ) {
	var adjusted, scale,
		maxIterations = 20,
		currentValue = tween ?
			function() {
				return tween.cur();
			} :
			function() {
				return jQuery.css( elem, prop, "" );
			},
		initial = currentValue(),
		unit = valueParts && valueParts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),

		// Starting value computation is required for potential unit mismatches
		initialInUnit = elem.nodeType &&
			( jQuery.cssNumber[ prop ] || unit !== "px" && +initial ) &&
			rcssNum.exec( jQuery.css( elem, prop ) );

	if ( initialInUnit && initialInUnit[ 3 ] !== unit ) {

		// Support: Firefox <=54
		// Halve the iteration target value to prevent interference from CSS upper bounds (gh-2144)
		initial = initial / 2;

		// Trust units reported by jQuery.css
		unit = unit || initialInUnit[ 3 ];

		// Iteratively approximate from a nonzero starting point
		initialInUnit = +initial || 1;

		while ( maxIterations-- ) {

			// Evaluate and update our best guess (doubling guesses that zero out).
			// Finish if the scale equals or crosses 1 (making the old*new product non-positive).
			jQuery.style( elem, prop, initialInUnit + unit );
			if ( ( 1 - scale ) * ( 1 - ( scale = currentValue() / initial || 0.5 ) ) <= 0 ) {
				maxIterations = 0;
			}
			initialInUnit = initialInUnit / scale;

		}

		initialInUnit = initialInUnit * 2;
		jQuery.style( elem, prop, initialInUnit + unit );

		// Make sure we update the tween properties later on
		valueParts = valueParts || [];
	}

	if ( valueParts ) {
		initialInUnit = +initialInUnit || +initial || 0;

		// Apply relative offset (+=/-=) if specified
		adjusted = valueParts[ 1 ] ?
			initialInUnit + ( valueParts[ 1 ] + 1 ) * valueParts[ 2 ] :
			+valueParts[ 2 ];
		if ( tween ) {
			tween.unit = unit;
			tween.start = initialInUnit;
			tween.end = adjusted;
		}
	}
	return adjusted;
}


var defaultDisplayMap = {};

function getDefaultDisplay( elem ) {
	var temp,
		doc = elem.ownerDocument,
		nodeName = elem.nodeName,
		display = defaultDisplayMap[ nodeName ];

	if ( display ) {
		return display;
	}

	temp = doc.body.appendChild( doc.createElement( nodeName ) );
	display = jQuery.css( temp, "display" );

	temp.parentNode.removeChild( temp );

	if ( display === "none" ) {
		display = "block";
	}
	defaultDisplayMap[ nodeName ] = display;

	return display;
}

function showHide( elements, show ) {
	var display, elem,
		values = [],
		index = 0,
		length = elements.length;

	// Determine new display value for elements that need to change
	for ( ; index < length; index++ ) {
		elem = elements[ index ];
		if ( !elem.style ) {
			continue;
		}

		display = elem.style.display;
		if ( show ) {

			// Since we force visibility upon cascade-hidden elements, an immediate (and slow)
			// check is required in this first loop unless we have a nonempty display value (either
			// inline or about-to-be-restored)
			if ( display === "none" ) {
				values[ index ] = dataPriv.get( elem, "display" ) || null;
				if ( !values[ index ] ) {
					elem.style.display = "";
				}
			}
			if ( elem.style.display === "" && isHiddenWithinTree( elem ) ) {
				values[ index ] = getDefaultDisplay( elem );
			}
		} else {
			if ( display !== "none" ) {
				values[ index ] = "none";

				// Remember what we're overwriting
				dataPriv.set( elem, "display", display );
			}
		}
	}

	// Set the display of the elements in a second loop to avoid constant reflow
	for ( index = 0; index < length; index++ ) {
		if ( values[ index ] != null ) {
			elements[ index ].style.display = values[ index ];
		}
	}

	return elements;
}

jQuery.fn.extend( {
	show: function() {
		return showHide( this, true );
	},
	hide: function() {
		return showHide( this );
	},
	toggle: function( state ) {
		if ( typeof state === "boolean" ) {
			return state ? this.show() : this.hide();
		}

		return this.each( function() {
			if ( isHiddenWithinTree( this ) ) {
				jQuery( this ).show();
			} else {
				jQuery( this ).hide();
			}
		} );
	}
} );
var rcheckableType = ( /^(?:checkbox|radio)$/i );

var rtagName = ( /<([a-z][^\/\0>\x20\t\r\n\f]*)/i );

var rscriptType = ( /^$|^module$|\/(?:java|ecma)script/i );



( function() {
	var fragment = document.createDocumentFragment(),
		div = fragment.appendChild( document.createElement( "div" ) ),
		input = document.createElement( "input" );

	// Support: Android 4.0 - 4.3 only
	// Check state lost if the name is set (trac-11217)
	// Support: Windows Web Apps (WWA)
	// `name` and `type` must use .setAttribute for WWA (trac-14901)
	input.setAttribute( "type", "radio" );
	input.setAttribute( "checked", "checked" );
	input.setAttribute( "name", "t" );

	div.appendChild( input );

	// Support: Android <=4.1 only
	// Older WebKit doesn't clone checked state correctly in fragments
	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;

	// Support: IE <=11 only
	// Make sure textarea (and checkbox) defaultValue is properly cloned
	div.innerHTML = "<textarea>x</textarea>";
	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;

	// Support: IE <=9 only
	// IE <=9 replaces <option> tags with their contents when inserted outside of
	// the select element.
	div.innerHTML = "<option></option>";
	support.option = !!div.lastChild;
} )();


// We have to close these tags to support XHTML (trac-13200)
var wrapMap = {

	// XHTML parsers do not magically insert elements in the
	// same way that tag soup parsers do. So we cannot shorten
	// this by omitting <tbody> or other required elements.
	thead: [ 1, "<table>", "</table>" ],
	col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
	tr: [ 2, "<table><tbody>", "</tbody></table>" ],
	td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],

	_default: [ 0, "", "" ]
};

wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
wrapMap.th = wrapMap.td;

// Support: IE <=9 only
if ( !support.option ) {
	wrapMap.optgroup = wrapMap.option = [ 1, "<select multiple='multiple'>", "</select>" ];
}


function getAll( context, tag ) {

	// Support: IE <=9 - 11 only
	// Use typeof to avoid zero-argument method invocation on host objects (trac-15151)
	var ret;

	if ( typeof context.getElementsByTagName !== "undefined" ) {
		ret = context.getElementsByTagName( tag || "*" );

	} else if ( typeof context.querySelectorAll !== "undefined" ) {
		ret = context.querySelectorAll( tag || "*" );

	} else {
		ret = [];
	}

	if ( tag === undefined || tag && nodeName( context, tag ) ) {
		return jQuery.merge( [ context ], ret );
	}

	return ret;
}


// Mark scripts as having already been evaluated
function setGlobalEval( elems, refElements ) {
	var i = 0,
		l = elems.length;

	for ( ; i < l; i++ ) {
		dataPriv.set(
			elems[ i ],
			"globalEval",
			!refElements || dataPriv.get( refElements[ i ], "globalEval" )
		);
	}
}


var rhtml = /<|&#?\w+;/;

function buildFragment( elems, context, scripts, selection, ignored ) {
	var elem, tmp, tag, wrap, attached, j,
		fragment = context.createDocumentFragment(),
		nodes = [],
		i = 0,
		l = elems.length;

	for ( ; i < l; i++ ) {
		elem = elems[ i ];

		if ( elem || elem === 0 ) {

			// Add nodes directly
			if ( toType( elem ) === "object" ) {

				// Support: Android <=4.0 only, PhantomJS 1 only
				// push.apply(_, arraylike) throws on ancient WebKit
				jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );

			// Convert non-html into a text node
			} else if ( !rhtml.test( elem ) ) {
				nodes.push( context.createTextNode( elem ) );

			// Convert html into DOM nodes
			} else {
				tmp = tmp || fragment.appendChild( context.createElement( "div" ) );

				// Deserialize a standard representation
				tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
				wrap = wrapMap[ tag ] || wrapMap._default;
				tmp.innerHTML = wrap[ 1 ] + jQuery.htmlPrefilter( elem ) + wrap[ 2 ];

				// Descend through wrappers to the right content
				j = wrap[ 0 ];
				while ( j-- ) {
					tmp = tmp.lastChild;
				}

				// Support: Android <=4.0 only, PhantomJS 1 only
				// push.apply(_, arraylike) throws on ancient WebKit
				jQuery.merge( nodes, tmp.childNodes );

				// Remember the top-level container
				tmp = fragment.firstChild;

				// Ensure the created nodes are orphaned (trac-12392)
				tmp.textContent = "";
			}
		}
	}

	// Remove wrapper from fragment
	fragment.textContent = "";

	i = 0;
	while ( ( elem = nodes[ i++ ] ) ) {

		// Skip elements already in the context collection (trac-4087)
		if ( selection && jQuery.inArray( elem, selection ) > -1 ) {
			if ( ignored ) {
				ignored.push( elem );
			}
			continue;
		}

		attached = isAttached( elem );

		// Append to fragment
		tmp = getAll( fragment.appendChild( elem ), "script" );

		// Preserve script evaluation history
		if ( attached ) {
			setGlobalEval( tmp );
		}

		// Capture executables
		if ( scripts ) {
			j = 0;
			while ( ( elem = tmp[ j++ ] ) ) {
				if ( rscriptType.test( elem.type || "" ) ) {
					scripts.push( elem );
				}
			}
		}
	}

	return fragment;
}


var rtypenamespace = /^([^.]*)(?:\.(.+)|)/;

function returnTrue() {
	return true;
}

function returnFalse() {
	return false;
}

function on( elem, types, selector, data, fn, one ) {
	var origFn, type;

	// Types can be a map of types/handlers
	if ( typeof types === "object" ) {

		// ( types-Object, selector, data )
		if ( typeof selector !== "string" ) {

			// ( types-Object, data )
			data = data || selector;
			selector = undefined;
		}
		for ( type in types ) {
			on( elem, type, selector, data, types[ type ], one );
		}
		return elem;
	}

	if ( data == null && fn == null ) {

		// ( types, fn )
		fn = selector;
		data = selector = undefined;
	} else if ( fn == null ) {
		if ( typeof selector === "string" ) {

			// ( types, selector, fn )
			fn = data;
			data = undefined;
		} else {

			// ( types, data, fn )
			fn = data;
			data = selector;
			selector = undefined;
		}
	}
	if ( fn === false ) {
		fn = returnFalse;
	} else if ( !fn ) {
		return elem;
	}

	if ( one === 1 ) {
		origFn = fn;
		fn = function( event ) {

			// Can use an empty set, since event contains the info
			jQuery().off( event );
			return origFn.apply( this, arguments );
		};

		// Use same guid so caller can remove using origFn
		fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
	}
	return elem.each( function() {
		jQuery.event.add( this, types, fn, data, selector );
	} );
}

/*
 * Helper functions for managing events -- not part of the public interface.
 * Props to Dean Edwards' addEvent library for many of the ideas.
 */
jQuery.event = {

	global: {},

	add: function( elem, types, handler, data, selector ) {

		var handleObjIn, eventHandle, tmp,
			events, t, handleObj,
			special, handlers, type, namespaces, origType,
			elemData = dataPriv.get( elem );

		// Only attach events to objects that accept data
		if ( !acceptData( elem ) ) {
			return;
		}

		// Caller can pass in an object of custom data in lieu of the handler
		if ( handler.handler ) {
			handleObjIn = handler;
			handler = handleObjIn.handler;
			selector = handleObjIn.selector;
		}

		// Ensure that invalid selectors throw exceptions at attach time
		// Evaluate against documentElement in case elem is a non-element node (e.g., document)
		if ( selector ) {
			jQuery.find.matchesSelector( documentElement, selector );
		}

		// Make sure that the handler has a unique ID, used to find/remove it later
		if ( !handler.guid ) {
			handler.guid = jQuery.guid++;
		}

		// Init the element's event structure and main handler, if this is the first
		if ( !( events = elemData.events ) ) {
			events = elemData.events = Object.create( null );
		}
		if ( !( eventHandle = elemData.handle ) ) {
			eventHandle = elemData.handle = function( e ) {

				// Discard the second event of a jQuery.event.trigger() and
				// when an event is called after a page has unloaded
				return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
					jQuery.event.dispatch.apply( elem, arguments ) : undefined;
			};
		}

		// Handle multiple events separated by a space
		types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[ t ] ) || [];
			type = origType = tmp[ 1 ];
			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();

			// There *must* be a type, no attaching namespace-only handlers
			if ( !type ) {
				continue;
			}

			// If event changes its type, use the special event handlers for the changed type
			special = jQuery.event.special[ type ] || {};

			// If selector defined, determine special event api type, otherwise given type
			type = ( selector ? special.delegateType : special.bindType ) || type;

			// Update special based on newly reset type
			special = jQuery.event.special[ type ] || {};

			// handleObj is passed to all event handlers
			handleObj = jQuery.extend( {
				type: type,
				origType: origType,
				data: data,
				handler: handler,
				guid: handler.guid,
				selector: selector,
				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
				namespace: namespaces.join( "." )
			}, handleObjIn );

			// Init the event handler queue if we're the first
			if ( !( handlers = events[ type ] ) ) {
				handlers = events[ type ] = [];
				handlers.delegateCount = 0;

				// Only use addEventListener if the special events handler returns false
				if ( !special.setup ||
					special.setup.call( elem, data, namespaces, eventHandle ) === false ) {

					if ( elem.addEventListener ) {
						elem.addEventListener( type, eventHandle );
					}
				}
			}

			if ( special.add ) {
				special.add.call( elem, handleObj );

				if ( !handleObj.handler.guid ) {
					handleObj.handler.guid = handler.guid;
				}
			}

			// Add to the element's handler list, delegates in front
			if ( selector ) {
				handlers.splice( handlers.delegateCount++, 0, handleObj );
			} else {
				handlers.push( handleObj );
			}

			// Keep track of which events have ever been used, for event optimization
			jQuery.event.global[ type ] = true;
		}

	},

	// Detach an event or set of events from an element
	remove: function( elem, types, handler, selector, mappedTypes ) {

		var j, origCount, tmp,
			events, t, handleObj,
			special, handlers, type, namespaces, origType,
			elemData = dataPriv.hasData( elem ) && dataPriv.get( elem );

		if ( !elemData || !( events = elemData.events ) ) {
			return;
		}

		// Once for each type.namespace in types; type may be omitted
		types = ( types || "" ).match( rnothtmlwhite ) || [ "" ];
		t = types.length;
		while ( t-- ) {
			tmp = rtypenamespace.exec( types[ t ] ) || [];
			type = origType = tmp[ 1 ];
			namespaces = ( tmp[ 2 ] || "" ).split( "." ).sort();

			// Unbind all events (on this namespace, if provided) for the element
			if ( !type ) {
				for ( type in events ) {
					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
				}
				continue;
			}

			special = jQuery.event.special[ type ] || {};
			type = ( selector ? special.delegateType : special.bindType ) || type;
			handlers = events[ type ] || [];
			tmp = tmp[ 2 ] &&
				new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" );

			// Remove matching events
			origCount = j = handlers.length;
			while ( j-- ) {
				handleObj = handlers[ j ];

				if ( ( mappedTypes || origType === handleObj.origType ) &&
					( !handler || handler.guid === handleObj.guid ) &&
					( !tmp || tmp.test( handleObj.namespace ) ) &&
					( !selector || selector === handleObj.selector ||
						selector === "**" && handleObj.selector ) ) {
					handlers.splice( j, 1 );

					if ( handleObj.selector ) {
						handlers.delegateCount--;
					}
					if ( special.remove ) {
						special.remove.call( elem, handleObj );
					}
				}
			}

			// Remove generic event handler if we removed something and no more handlers exist
			// (avoids potential for endless recursion during removal of special event handlers)
			if ( origCount && !handlers.length ) {
				if ( !special.teardown ||
					special.teardown.call( elem, namespaces, elemData.handle ) === false ) {

					jQuery.removeEvent( elem, type, elemData.handle );
				}

				delete events[ type ];
			}
		}

		// Remove data and the expando if it's no longer used
		if ( jQuery.isEmptyObject( events ) ) {
			dataPriv.remove( elem, "handle events" );
		}
	},

	dispatch: function( nativeEvent ) {

		var i, j, ret, matched, handleObj, handlerQueue,
			args = new Array( arguments.length ),

			// Make a writable jQuery.Event from the native event object
			event = jQuery.event.fix( nativeEvent ),

			handlers = (
				dataPriv.get( this, "events" ) || Object.create( null )
			)[ event.type ] || [],
			special = jQuery.event.special[ event.type ] || {};

		// Use the fix-ed jQuery.Event rather than the (read-only) native event
		args[ 0 ] = event;

		for ( i = 1; i < arguments.length; i++ ) {
			args[ i ] = arguments[ i ];
		}

		event.delegateTarget = this;

		// Call the preDispatch hook for the mapped type, and let it bail if desired
		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
			return;
		}

		// Determine handlers
		handlerQueue = jQuery.event.handlers.call( this, event, handlers );

		// Run delegates first; they may want to stop propagation beneath us
		i = 0;
		while ( ( matched = handlerQueue[ i++ ] ) && !event.isPropagationStopped() ) {
			event.currentTarget = matched.elem;

			j = 0;
			while ( ( handleObj = matched.handlers[ j++ ] ) &&
				!event.isImmediatePropagationStopped() ) {

				// If the event is namespaced, then each handler is only invoked if it is
				// specially universal or its namespaces are a superset of the event's.
				if ( !event.rnamespace || handleObj.namespace === false ||
					event.rnamespace.test( handleObj.namespace ) ) {

					event.handleObj = handleObj;
					event.data = handleObj.data;

					ret = ( ( jQuery.event.special[ handleObj.origType ] || {} ).handle ||
						handleObj.handler ).apply( matched.elem, args );

					if ( ret !== undefined ) {
						if ( ( event.result = ret ) === false ) {
							event.preventDefault();
							event.stopPropagation();
						}
					}
				}
			}
		}

		// Call the postDispatch hook for the mapped type
		if ( special.postDispatch ) {
			special.postDispatch.call( this, event );
		}

		return event.result;
	},

	handlers: function( event, handlers ) {
		var i, handleObj, sel, matchedHandlers, matchedSelectors,
			handlerQueue = [],
			delegateCount = handlers.delegateCount,
			cur = event.target;

		// Find delegate handlers
		if ( delegateCount &&

			// Support: IE <=9
			// Black-hole SVG <use> instance trees (trac-13180)
			cur.nodeType &&

			// Support: Firefox <=42
			// Suppress spec-violating clicks indicating a non-primary pointer button (trac-3861)
			// https://www.w3.org/TR/DOM-Level-3-Events/#event-type-click
			// Support: IE 11 only
			// ...but not arrow key "clicks" of radio inputs, which can have `button` -1 (gh-2343)
			!( event.type === "click" && event.button >= 1 ) ) {

			for ( ; cur !== this; cur = cur.parentNode || this ) {

				// Don't check non-elements (trac-13208)
				// Don't process clicks on disabled elements (trac-6911, trac-8165, trac-11382, trac-11764)
				if ( cur.nodeType === 1 && !( event.type === "click" && cur.disabled === true ) ) {
					matchedHandlers = [];
					matchedSelectors = {};
					for ( i = 0; i < delegateCount; i++ ) {
						handleObj = handlers[ i ];

						// Don't conflict with Object.prototype properties (trac-13203)
						sel = handleObj.selector + " ";

						if ( matchedSelectors[ sel ] === undefined ) {
							matchedSelectors[ sel ] = handleObj.needsContext ?
								jQuery( sel, this ).index( cur ) > -1 :
								jQuery.find( sel, this, null, [ cur ] ).length;
						}
						if ( matchedSelectors[ sel ] ) {
							matchedHandlers.push( handleObj );
						}
					}
					if ( matchedHandlers.length ) {
						handlerQueue.push( { elem: cur, handlers: matchedHandlers } );
					}
				}
			}
		}

		// Add the remaining (directly-bound) handlers
		cur = this;
		if ( delegateCount < handlers.length ) {
			handlerQueue.push( { elem: cur, handlers: handlers.slice( delegateCount ) } );
		}

		return handlerQueue;
	},

	addProp: function( name, hook ) {
		Object.defineProperty( jQuery.Event.prototype, name, {
			enumerable: true,
			configurable: true,

			get: isFunction( hook ) ?
				function() {
					if ( this.originalEvent ) {
						return hook( this.originalEvent );
					}
				} :
				function() {
					if ( this.originalEvent ) {
						return this.originalEvent[ name ];
					}
				},

			set: function( value ) {
				Object.defineProperty( this, name, {
					enumerable: true,
					configurable: true,
					writable: true,
					value: value
				} );
			}
		} );
	},

	fix: function( originalEvent ) {
		return originalEvent[ jQuery.expando ] ?
			originalEvent :
			new jQuery.Event( originalEvent );
	},

	special: {
		load: {

			// Prevent triggered image.load events from bubbling to window.load
			noBubble: true
		},
		click: {

			// Utilize native event to ensure correct state for checkable inputs
			setup: function( data ) {

				// For mutual compressibility with _default, replace `this` access with a local var.
				// `|| data` is dead code meant only to preserve the variable through minification.
				var el = this || data;

				// Claim the first handler
				if ( rcheckableType.test( el.type ) &&
					el.click && nodeName( el, "input" ) ) {

					// dataPriv.set( el, "click", ... )
					leverageNative( el, "click", true );
				}

				// Return false to allow normal processing in the caller
				return false;
			},
			trigger: function( data ) {

				// For mutual compressibility with _default, replace `this` access with a local var.
				// `|| data` is dead code meant only to preserve the variable through minification.
				var el = this || data;

				// Force setup before triggering a click
				if ( rcheckableType.test( el.type ) &&
					el.click && nodeName( el, "input" ) ) {

					leverageNative( el, "click" );
				}

				// Return non-false to allow normal event-path propagation
				return true;
			},

			// For cross-browser consistency, suppress native .click() on links
			// Also prevent it if we're currently inside a leveraged native-event stack
			_default: function( event ) {
				var target = event.target;
				return rcheckableType.test( target.type ) &&
					target.click && nodeName( target, "input" ) &&
					dataPriv.get( target, "click" ) ||
					nodeName( target, "a" );
			}
		},

		beforeunload: {
			postDispatch: function( event ) {

				// Support: Firefox 20+
				// Firefox doesn't alert if the returnValue field is not set.
				if ( event.result !== undefined && event.originalEvent ) {
					event.originalEvent.returnValue = event.result;
				}
			}
		}
	}
};

// Ensure the presence of an event listener that handles manually-triggered
// synthetic events by interrupting progress until reinvoked in response to
// *native* events that it fires directly, ensuring that state changes have
// already occurred before other listeners are invoked.
function leverageNative( el, type, isSetup ) {

	// Missing `isSetup` indicates a trigger call, which must force setup through jQuery.event.add
	if ( !isSetup ) {
		if ( dataPriv.get( el, type ) === undefined ) {
			jQuery.event.add( el, type, returnTrue );
		}
		return;
	}

	// Register the controller as a special universal handler for all event namespaces
	dataPriv.set( el, type, false );
	jQuery.event.add( el, type, {
		namespace: false,
		handler: function( event ) {
			var result,
				saved = dataPriv.get( this, type );

			if ( ( event.isTrigger & 1 ) && this[ type ] ) {

				// Interrupt processing of the outer synthetic .trigger()ed event
				if ( !saved ) {

					// Store arguments for use when handling the inner native event
					// There will always be at least one argument (an event object), so this array
					// will not be confused with a leftover capture object.
					saved = slice.call( arguments );
					dataPriv.set( this, type, saved );

					// Trigger the native event and capture its result
					this[ type ]();
					result = dataPriv.get( this, type );
					dataPriv.set( this, type, false );

					if ( saved !== result ) {

						// Cancel the outer synthetic event
						event.stopImmediatePropagation();
						event.preventDefault();

						return result;
					}

				// If this is an inner synthetic event for an event with a bubbling surrogate
				// (focus or blur), assume that the surrogate already propagated from triggering
				// the native event and prevent that from happening again here.
				// This technically gets the ordering wrong w.r.t. to `.trigger()` (in which the
				// bubbling surrogate propagates *after* the non-bubbling base), but that seems
				// less bad than duplication.
				} else if ( ( jQuery.event.special[ type ] || {} ).delegateType ) {
					event.stopPropagation();
				}

			// If this is a native event triggered above, everything is now in order
			// Fire an inner synthetic event with the original arguments
			} else if ( saved ) {

				// ...and capture the result
				dataPriv.set( this, type, jQuery.event.trigger(
					saved[ 0 ],
					saved.slice( 1 ),
					this
				) );

				// Abort handling of the native event by all jQuery handlers while allowing
				// native handlers on the same element to run. On target, this is achieved
				// by stopping immediate propagation just on the jQuery event. However,
				// the native event is re-wrapped by a jQuery one on each level of the
				// propagation so the only way to stop it for jQuery is to stop it for
				// everyone via native `stopPropagation()`. This is not a problem for
				// focus/blur which don't bubble, but it does also stop click on checkboxes
				// and radios. We accept this limitation.
				event.stopPropagation();
				event.isImmediatePropagationStopped = returnTrue;
			}
		}
	} );
}

jQuery.removeEvent = function( elem, type, handle ) {

	// This "if" is needed for plain objects
	if ( elem.removeEventListener ) {
		elem.removeEventListener( type, handle );
	}
};

jQuery.Event = function( src, props ) {

	// Allow instantiation without the 'new' keyword
	if ( !( this instanceof jQuery.Event ) ) {
		return new jQuery.Event( src, props );
	}

	// Event object
	if ( src && src.type ) {
		this.originalEvent = src;
		this.type = src.type;

		// Events bubbling up the document may have been marked as prevented
		// by a handler lower down the tree; reflect the correct value.
		this.isDefaultPrevented = src.defaultPrevented ||
				src.defaultPrevented === undefined &&

				// Support: Android <=2.3 only
				src.returnValue === false ?
			returnTrue :
			returnFalse;

		// Create target properties
		// Support: Safari <=6 - 7 only
		// Target should not be a text node (trac-504, trac-13143)
		this.target = ( src.target && src.target.nodeType === 3 ) ?
			src.target.parentNode :
			src.target;

		this.currentTarget = src.currentTarget;
		this.relatedTarget = src.relatedTarget;

	// Event type
	} else {
		this.type = src;
	}

	// Put explicitly provided properties onto the event object
	if ( props ) {
		jQuery.extend( this, props );
	}

	// Create a timestamp if incoming event doesn't have one
	this.timeStamp = src && src.timeStamp || Date.now();

	// Mark it as fixed
	this[ jQuery.expando ] = true;
};

// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// https://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
	constructor: jQuery.Event,
	isDefaultPrevented: returnFalse,
	isPropagationStopped: returnFalse,
	isImmediatePropagationStopped: returnFalse,
	isSimulated: false,

	preventDefault: function() {
		var e = this.originalEvent;

		this.isDefaultPrevented = returnTrue;

		if ( e && !this.isSimulated ) {
			e.preventDefault();
		}
	},
	stopPropagation: function() {
		var e = this.originalEvent;

		this.isPropagationStopped = returnTrue;

		if ( e && !this.isSimulated ) {
			e.stopPropagation();
		}
	},
	stopImmediatePropagation: function() {
		var e = this.originalEvent;

		this.isImmediatePropagationStopped = returnTrue;

		if ( e && !this.isSimulated ) {
			e.stopImmediatePropagation();
		}

		this.stopPropagation();
	}
};

// Includes all common event props including KeyEvent and MouseEvent specific props
jQuery.each( {
	altKey: true,
	bubbles: true,
	cancelable: true,
	changedTouches: true,
	ctrlKey: true,
	detail: true,
	eventPhase: true,
	metaKey: true,
	pageX: true,
	pageY: true,
	shiftKey: true,
	view: true,
	"char": true,
	code: true,
	charCode: true,
	key: true,
	keyCode: true,
	button: true,
	buttons: true,
	clientX: true,
	clientY: true,
	offsetX: true,
	offsetY: true,
	pointerId: true,
	pointerType: true,
	screenX: true,
	screenY: true,
	targetTouches: true,
	toElement: true,
	touches: true,
	which: true
}, jQuery.event.addProp );

jQuery.each( { focus: "focusin", blur: "focusout" }, function( type, delegateType ) {

	function focusMappedHandler( nativeEvent ) {
		if ( document.documentMode ) {

			// Support: IE 11+
			// Attach a single focusin/focusout handler on the document while someone wants
			// focus/blur. This is because the former are synchronous in IE while the latter
			// are async. In other browsers, all those handlers are invoked synchronously.

			// `handle` from private data would already wrap the event, but we need
			// to change the `type` here.
			var handle = dataPriv.get( this, "handle" ),
				event = jQuery.event.fix( nativeEvent );
			event.type = nativeEvent.type === "focusin" ? "focus" : "blur";
			event.isSimulated = true;

			// First, handle focusin/focusout
			handle( nativeEvent );

			// ...then, handle focus/blur
			//
			// focus/blur don't bubble while focusin/focusout do; simulate the former by only
			// invoking the handler at the lower level.
			if ( event.target === event.currentTarget ) {

				// The setup part calls `leverageNative`, which, in turn, calls
				// `jQuery.event.add`, so event handle will already have been set
				// by this point.
				handle( event );
			}
		} else {

			// For non-IE browsers, attach a single capturing handler on the document
			// while someone wants focusin/focusout.
			jQuery.event.simulate( delegateType, nativeEvent.target,
				jQuery.event.fix( nativeEvent ) );
		}
	}

	jQuery.event.special[ type ] = {

		// Utilize native event if possible so blur/focus sequence is correct
		setup: function() {

			var attaches;

			// Claim the first handler
			// dataPriv.set( this, "focus", ... )
			// dataPriv.set( this, "blur", ... )
			leverageNative( this, type, true );

			if ( document.documentMode ) {

				// Support: IE 9 - 11+
				// We use the same native handler for focusin & focus (and focusout & blur)
				// so we need to coordinate setup & teardown parts between those events.
				// Use `delegateType` as the key as `type` is already used by `leverageNative`.
				attaches = dataPriv.get( this, delegateType );
				if ( !attaches ) {
					this.addEventListener( delegateType, focusMappedHandler );
				}
				dataPriv.set( this, delegateType, ( attaches || 0 ) + 1 );
			} else {

				// Return false to allow normal processing in the caller
				return false;
			}
		},
		trigger: function() {

			// Force setup before trigger
			leverageNative( this, type );

			// Return non-false to allow normal event-path propagation
			return true;
		},

		teardown: function() {
			var attaches;

			if ( document.documentMode ) {
				attaches = dataPriv.get( this, delegateType ) - 1;
				if ( !attaches ) {
					this.removeEventListener( delegateType, focusMappedHandler );
					dataPriv.remove( this, delegateType );
				} else {
					dataPriv.set( this, delegateType, attaches );
				}
			} else {

				// Return false to indicate standard teardown should be applied
				return false;
			}
		},

		// Suppress native focus or blur if we're currently inside
		// a leveraged native-event stack
		_default: function( event ) {
			return dataPriv.get( event.target, type );
		},

		delegateType: delegateType
	};

	// Support: Firefox <=44
	// Firefox doesn't have focus(in | out) events
	// Related ticket - https://bugzilla.mozilla.org/show_bug.cgi?id=687787
	//
	// Support: Chrome <=48 - 49, Safari <=9.0 - 9.1
	// focus(in | out) events fire after focus & blur events,
	// which is spec violation - http://www.w3.org/TR/DOM-Level-3-Events/#events-focusevent-event-order
	// Related ticket - https://bugs.chromium.org/p/chromium/issues/detail?id=449857
	//
	// Support: IE 9 - 11+
	// To preserve relative focusin/focus & focusout/blur event order guaranteed on the 3.x branch,
	// attach a single handler for both events in IE.
	jQuery.event.special[ delegateType ] = {
		setup: function() {

			// Handle: regular nodes (via `this.ownerDocument`), window
			// (via `this.document`) & document (via `this`).
			var doc = this.ownerDocument || this.document || this,
				dataHolder = document.documentMode ? this : doc,
				attaches = dataPriv.get( dataHolder, delegateType );

			// Support: IE 9 - 11+
			// We use the same native handler for focusin & focus (and focusout & blur)
			// so we need to coordinate setup & teardown parts between those events.
			// Use `delegateType` as the key as `type` is already used by `leverageNative`.
			if ( !attaches ) {
				if ( document.documentMode ) {
					this.addEventListener( delegateType, focusMappedHandler );
				} else {
					doc.addEventListener( type, focusMappedHandler, true );
				}
			}
			dataPriv.set( dataHolder, delegateType, ( attaches || 0 ) + 1 );
		},
		teardown: function() {
			var doc = this.ownerDocument || this.document || this,
				dataHolder = document.documentMode ? this : doc,
				attaches = dataPriv.get( dataHolder, delegateType ) - 1;

			if ( !attaches ) {
				if ( document.documentMode ) {
					this.removeEventListener( delegateType, focusMappedHandler );
				} else {
					doc.removeEventListener( type, focusMappedHandler, true );
				}
				dataPriv.remove( dataHolder, delegateType );
			} else {
				dataPriv.set( dataHolder, delegateType, attaches );
			}
		}
	};
} );

// Create mouseenter/leave events using mouseover/out and event-time checks
// so that event delegation works in jQuery.
// Do the same for pointerenter/pointerleave and pointerover/pointerout
//
// Support: Safari 7 only
// Safari sends mouseenter too often; see:
// https://bugs.chromium.org/p/chromium/issues/detail?id=470258
// for the description of the bug (it existed in older Chrome versions as well).
jQuery.each( {
	mouseenter: "mouseover",
	mouseleave: "mouseout",
	pointerenter: "pointerover",
	pointerleave: "pointerout"
}, function( orig, fix ) {
	jQuery.event.special[ orig ] = {
		delegateType: fix,
		bindType: fix,

		handle: function( event ) {
			var ret,
				target = this,
				related = event.relatedTarget,
				handleObj = event.handleObj;

			// For mouseenter/leave call the handler if related is outside the target.
			// NB: No relatedTarget if the mouse left/entered the browser window
			if ( !related || ( related !== target && !jQuery.contains( target, related ) ) ) {
				event.type = handleObj.origType;
				ret = handleObj.handler.apply( this, arguments );
				event.type = fix;
			}
			return ret;
		}
	};
} );

jQuery.fn.extend( {

	on: function( types, selector, data, fn ) {
		return on( this, types, selector, data, fn );
	},
	one: function( types, selector, data, fn ) {
		return on( this, types, selector, data, fn, 1 );
	},
	off: function( types, selector, fn ) {
		var handleObj, type;
		if ( types && types.preventDefault && types.handleObj ) {

			// ( event )  dispatched jQuery.Event
			handleObj = types.handleObj;
			jQuery( types.delegateTarget ).off(
				handleObj.namespace ?
					handleObj.origType + "." + handleObj.namespace :
					handleObj.origType,
				handleObj.selector,
				handleObj.handler
			);
			return this;
		}
		if ( typeof types === "object" ) {

			// ( types-object [, selector] )
			for ( type in types ) {
				this.off( type, selector, types[ type ] );
			}
			return this;
		}
		if ( selector === false || typeof selector === "function" ) {

			// ( types [, fn] )
			fn = selector;
			selector = undefined;
		}
		if ( fn === false ) {
			fn = returnFalse;
		}
		return this.each( function() {
			jQuery.event.remove( this, types, fn, selector );
		} );
	}
} );


var

	// Support: IE <=10 - 11, Edge 12 - 13 only
	// In IE/Edge using regex groups here causes severe slowdowns.
	// See https://connect.microsoft.com/IE/feedback/details/1736512/
	rnoInnerhtml = /<script|<style|<link/i,

	// checked="checked" or checked
	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,

	rcleanScript = /^\s*<!\[CDATA\[|\]\]>\s*$/g;

// Prefer a tbody over its parent table for containing new rows
function manipulationTarget( elem, content ) {
	if ( nodeName( elem, "table" ) &&
		nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ) {

		return jQuery( elem ).children( "tbody" )[ 0 ] || elem;
	}

	return elem;
}

// Replace/restore the type attribute of script elements for safe DOM manipulation
function disableScript( elem ) {
	elem.type = ( elem.getAttribute( "type" ) !== null ) + "/" + elem.type;
	return elem;
}
function restoreScript( elem ) {
	if ( ( elem.type || "" ).slice( 0, 5 ) === "true/" ) {
		elem.type = elem.type.slice( 5 );
	} else {
		elem.removeAttribute( "type" );
	}

	return elem;
}

function cloneCopyEvent( src, dest ) {
	var i, l, type, pdataOld, udataOld, udataCur, events;

	if ( dest.nodeType !== 1 ) {
		return;
	}

	// 1. Copy private data: events, handlers, etc.
	if ( dataPriv.hasData( src ) ) {
		pdataOld = dataPriv.get( src );
		events = pdataOld.events;

		if ( events ) {
			dataPriv.remove( dest, "handle events" );

			for ( type in events ) {
				for ( i = 0, l = events[ type ].length; i < l; i++ ) {
					jQuery.event.add( dest, type, events[ type ][ i ] );
				}
			}
		}
	}

	// 2. Copy user data
	if ( dataUser.hasData( src ) ) {
		udataOld = dataUser.access( src );
		udataCur = jQuery.extend( {}, udataOld );

		dataUser.set( dest, udataCur );
	}
}

// Fix IE bugs, see support tests
function fixInput( src, dest ) {
	var nodeName = dest.nodeName.toLowerCase();

	// Fails to persist the checked state of a cloned checkbox or radio button.
	if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
		dest.checked = src.checked;

	// Fails to return the selected option to the default selected state when cloning options
	} else if ( nodeName === "input" || nodeName === "textarea" ) {
		dest.defaultValue = src.defaultValue;
	}
}

function domManip( collection, args, callback, ignored ) {

	// Flatten any nested arrays
	args = flat( args );

	var fragment, first, scripts, hasScripts, node, doc,
		i = 0,
		l = collection.length,
		iNoClone = l - 1,
		value = args[ 0 ],
		valueIsFunction = isFunction( value );

	// We can't cloneNode fragments that contain checked, in WebKit
	if ( valueIsFunction ||
			( l > 1 && typeof value === "string" &&
				!support.checkClone && rchecked.test( value ) ) ) {
		return collection.each( function( index ) {
			var self = collection.eq( index );
			if ( valueIsFunction ) {
				args[ 0 ] = value.call( this, index, self.html() );
			}
			domManip( self, args, callback, ignored );
		} );
	}

	if ( l ) {
		fragment = buildFragment( args, collection[ 0 ].ownerDocument, false, collection, ignored );
		first = fragment.firstChild;

		if ( fragment.childNodes.length === 1 ) {
			fragment = first;
		}

		// Require either new content or an interest in ignored elements to invoke the callback
		if ( first || ignored ) {
			scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
			hasScripts = scripts.length;

			// Use the original fragment for the last item
			// instead of the first because it can end up
			// being emptied incorrectly in certain situations (trac-8070).
			for ( ; i < l; i++ ) {
				node = fragment;

				if ( i !== iNoClone ) {
					node = jQuery.clone( node, true, true );

					// Keep references to cloned scripts for later restoration
					if ( hasScripts ) {

						// Support: Android <=4.0 only, PhantomJS 1 only
						// push.apply(_, arraylike) throws on ancient WebKit
						jQuery.merge( scripts, getAll( node, "script" ) );
					}
				}

				callback.call( collection[ i ], node, i );
			}

			if ( hasScripts ) {
				doc = scripts[ scripts.length - 1 ].ownerDocument;

				// Reenable scripts
				jQuery.map( scripts, restoreScript );

				// Evaluate executable scripts on first document insertion
				for ( i = 0; i < hasScripts; i++ ) {
					node = scripts[ i ];
					if ( rscriptType.test( node.type || "" ) &&
						!dataPriv.access( node, "globalEval" ) &&
						jQuery.contains( doc, node ) ) {

						if ( node.src && ( node.type || "" ).toLowerCase()  !== "module" ) {

							// Optional AJAX dependency, but won't run scripts if not present
							if ( jQuery._evalUrl && !node.noModule ) {
								jQuery._evalUrl( node.src, {
									nonce: node.nonce || node.getAttribute( "nonce" )
								}, doc );
							}
						} else {

							// Unwrap a CDATA section containing script contents. This shouldn't be
							// needed as in XML documents they're already not visible when
							// inspecting element contents and in HTML documents they have no
							// meaning but we're preserving that logic for backwards compatibility.
							// This will be removed completely in 4.0. See gh-4904.
							DOMEval( node.textContent.replace( rcleanScript, "" ), node, doc );
						}
					}
				}
			}
		}
	}

	return collection;
}

function remove( elem, selector, keepData ) {
	var node,
		nodes = selector ? jQuery.filter( selector, elem ) : elem,
		i = 0;

	for ( ; ( node = nodes[ i ] ) != null; i++ ) {
		if ( !keepData && node.nodeType === 1 ) {
			jQuery.cleanData( getAll( node ) );
		}

		if ( node.parentNode ) {
			if ( keepData && isAttached( node ) ) {
				setGlobalEval( getAll( node, "script" ) );
			}
			node.parentNode.removeChild( node );
		}
	}

	return elem;
}

jQuery.extend( {
	htmlPrefilter: function( html ) {
		return html;
	},

	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
		var i, l, srcElements, destElements,
			clone = elem.cloneNode( true ),
			inPage = isAttached( elem );

		// Fix IE cloning issues
		if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
				!jQuery.isXMLDoc( elem ) ) {

			// We eschew jQuery#find here for performance reasons:
			// https://jsperf.com/getall-vs-sizzle/2
			destElements = getAll( clone );
			srcElements = getAll( elem );

			for ( i = 0, l = srcElements.length; i < l; i++ ) {
				fixInput( srcElements[ i ], destElements[ i ] );
			}
		}

		// Copy the events from the original to the clone
		if ( dataAndEvents ) {
			if ( deepDataAndEvents ) {
				srcElements = srcElements || getAll( elem );
				destElements = destElements || getAll( clone );

				for ( i = 0, l = srcElements.length; i < l; i++ ) {
					cloneCopyEvent( srcElements[ i ], destElements[ i ] );
				}
			} else {
				cloneCopyEvent( elem, clone );
			}
		}

		// Preserve script evaluation history
		destElements = getAll( clone, "script" );
		if ( destElements.length > 0 ) {
			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
		}

		// Return the cloned set
		return clone;
	},

	cleanData: function( elems ) {
		var data, elem, type,
			special = jQuery.event.special,
			i = 0;

		for ( ; ( elem = elems[ i ] ) !== undefined; i++ ) {
			if ( acceptData( elem ) ) {
				if ( ( data = elem[ dataPriv.expando ] ) ) {
					if ( data.events ) {
						for ( type in data.events ) {
							if ( special[ type ] ) {
								jQuery.event.remove( elem, type );

							// This is a shortcut to avoid jQuery.event.remove's overhead
							} else {
								jQuery.removeEvent( elem, type, data.handle );
							}
						}
					}

					// Support: Chrome <=35 - 45+
					// Assign undefined instead of using delete, see Data#remove
					elem[ dataPriv.expando ] = undefined;
				}
				if ( elem[ dataUser.expando ] ) {

					// Support: Chrome <=35 - 45+
					// Assign undefined instead of using delete, see Data#remove
					elem[ dataUser.expando ] = undefined;
				}
			}
		}
	}
} );

jQuery.fn.extend( {
	detach: function( selector ) {
		return remove( this, selector, true );
	},

	remove: function( selector ) {
		return remove( this, selector );
	},

	text: function( value ) {
		return access( this, function( value ) {
			return value === undefined ?
				jQuery.text( this ) :
				this.empty().each( function() {
					if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
						this.textContent = value;
					}
				} );
		}, null, value, arguments.length );
	},

	append: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				var target = manipulationTarget( this, elem );
				target.appendChild( elem );
			}
		} );
	},

	prepend: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
				var target = manipulationTarget( this, elem );
				target.insertBefore( elem, target.firstChild );
			}
		} );
	},

	before: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.parentNode ) {
				this.parentNode.insertBefore( elem, this );
			}
		} );
	},

	after: function() {
		return domManip( this, arguments, function( elem ) {
			if ( this.parentNode ) {
				this.parentNode.insertBefore( elem, this.nextSibling );
			}
		} );
	},

	empty: function() {
		var elem,
			i = 0;

		for ( ; ( elem = this[ i ] ) != null; i++ ) {
			if ( elem.nodeType === 1 ) {

				// Prevent memory leaks
				jQuery.cleanData( getAll( elem, false ) );

				// Remove any remaining nodes
				elem.textContent = "";
			}
		}

		return this;
	},

	clone: function( dataAndEvents, deepDataAndEvents ) {
		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;

		return this.map( function() {
			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
		} );
	},

	html: function( value ) {
		return access( this, function( value ) {
			var elem = this[ 0 ] || {},
				i = 0,
				l = this.length;

			if ( value === undefined && elem.nodeType === 1 ) {
				return elem.innerHTML;
			}

			// See if we can take a shortcut and just use innerHTML
			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
				!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {

				value = jQuery.htmlPrefilter( value );

				try {
					for ( ; i < l; i++ ) {
						elem = this[ i ] || {};

						// Remove element nodes and prevent memory leaks
						if ( elem.nodeType === 1 ) {
							jQuery.cleanData( getAll( elem, false ) );
							elem.innerHTML = value;
						}
					}

					elem = 0;

				// If using innerHTML throws an exception, use the fallback method
				} catch ( e ) {}
			}

			if ( elem ) {
				this.empty().append( value );
			}
		}, null, value, arguments.length );
	},

	replaceWith: function() {
		var ignored = [];

		// Make the changes, replacing each non-ignored context element with the new content
		return domManip( this, arguments, function( elem ) {
			var parent = this.parentNode;

			if ( jQuery.inArray( this, ignored ) < 0 ) {
				jQuery.cleanData( getAll( this ) );
				if ( parent ) {
					parent.replaceChild( elem, this );
				}
			}

		// Force callback invocation
		}, ignored );
	}
} );

jQuery.each( {
	appendTo: "append",
	prependTo: "prepend",
	insertBefore: "before",
	insertAfter: "after",
	replaceAll: "replaceWith"
}, function( name, original ) {
	jQuery.fn[ name ] = function( selector ) {
		var elems,
			ret = [],
			insert = jQuery( selector ),
			last = insert.length - 1,
			i = 0;

		for ( ; i <= last; i++ ) {
			elems = i === last ? this : this.clone( true );
			jQuery( insert[ i ] )[ original ]( elems );

			// Support: Android <=4.0 only, PhantomJS 1 only
			// .get() because push.apply(_, arraylike) throws on ancient WebKit
			push.apply( ret, elems.get() );
		}

		return this.pushStack( ret );
	};
} );
var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );

var rcustomProp = /^--/;


var getStyles = function( elem ) {

		// Support: IE <=11 only, Firefox <=30 (trac-15098, trac-14150)
		// IE throws on elements created in popups
		// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
		var view = elem.ownerDocument.defaultView;

		if ( !view || !view.opener ) {
			view = window;
		}

		return view.getComputedStyle( elem );
	};

var swap = function( elem, options, callback ) {
	var ret, name,
		old = {};

	// Remember the old values, and insert the new ones
	for ( name in options ) {
		old[ name ] = elem.style[ name ];
		elem.style[ name ] = options[ name ];
	}

	ret = callback.call( elem );

	// Revert the old values
	for ( name in options ) {
		elem.style[ name ] = old[ name ];
	}

	return ret;
};


var rboxStyle = new RegExp( cssExpand.join( "|" ), "i" );



( function() {

	// Executing both pixelPosition & boxSizingReliable tests require only one layout
	// so they're executed at the same time to save the second computation.
	function computeStyleTests() {

		// This is a singleton, we need to execute it only once
		if ( !div ) {
			return;
		}

		container.style.cssText = "position:absolute;left:-11111px;width:60px;" +
			"margin-top:1px;padding:0;border:0";
		div.style.cssText =
			"position:relative;display:block;box-sizing:border-box;overflow:scroll;" +
			"margin:auto;border:1px;padding:1px;" +
			"width:60%;top:1%";
		documentElement.appendChild( container ).appendChild( div );

		var divStyle = window.getComputedStyle( div );
		pixelPositionVal = divStyle.top !== "1%";

		// Support: Android 4.0 - 4.3 only, Firefox <=3 - 44
		reliableMarginLeftVal = roundPixelMeasures( divStyle.marginLeft ) === 12;

		// Support: Android 4.0 - 4.3 only, Safari <=9.1 - 10.1, iOS <=7.0 - 9.3
		// Some styles come back with percentage values, even though they shouldn't
		div.style.right = "60%";
		pixelBoxStylesVal = roundPixelMeasures( divStyle.right ) === 36;

		// Support: IE 9 - 11 only
		// Detect misreporting of content dimensions for box-sizing:border-box elements
		boxSizingReliableVal = roundPixelMeasures( divStyle.width ) === 36;

		// Support: IE 9 only
		// Detect overflow:scroll screwiness (gh-3699)
		// Support: Chrome <=64
		// Don't get tricked when zoom affects offsetWidth (gh-4029)
		div.style.position = "absolute";
		scrollboxSizeVal = roundPixelMeasures( div.offsetWidth / 3 ) === 12;

		documentElement.removeChild( container );

		// Nullify the div so it wouldn't be stored in the memory and
		// it will also be a sign that checks already performed
		div = null;
	}

	function roundPixelMeasures( measure ) {
		return Math.round( parseFloat( measure ) );
	}

	var pixelPositionVal, boxSizingReliableVal, scrollboxSizeVal, pixelBoxStylesVal,
		reliableTrDimensionsVal, reliableMarginLeftVal,
		container = document.createElement( "div" ),
		div = document.createElement( "div" );

	// Finish early in limited (non-browser) environments
	if ( !div.style ) {
		return;
	}

	// Support: IE <=9 - 11 only
	// Style of cloned element affects source element cloned (trac-8908)
	div.style.backgroundClip = "content-box";
	div.cloneNode( true ).style.backgroundClip = "";
	support.clearCloneStyle = div.style.backgroundClip === "content-box";

	jQuery.extend( support, {
		boxSizingReliable: function() {
			computeStyleTests();
			return boxSizingReliableVal;
		},
		pixelBoxStyles: function() {
			computeStyleTests();
			return pixelBoxStylesVal;
		},
		pixelPosition: function() {
			computeStyleTests();
			return pixelPositionVal;
		},
		reliableMarginLeft: function() {
			computeStyleTests();
			return reliableMarginLeftVal;
		},
		scrollboxSize: function() {
			computeStyleTests();
			return scrollboxSizeVal;
		},

		// Support: IE 9 - 11+, Edge 15 - 18+
		// IE/Edge misreport `getComputedStyle` of table rows with width/height
		// set in CSS while `offset*` properties report correct values.
		// Behavior in IE 9 is more subtle than in newer versions & it passes
		// some versions of this test; make sure not to make it pass there!
		//
		// Support: Firefox 70+
		// Only Firefox includes border widths
		// in computed dimensions. (gh-4529)
		reliableTrDimensions: function() {
			var table, tr, trChild, trStyle;
			if ( reliableTrDimensionsVal == null ) {
				table = document.createElement( "table" );
				tr = document.createElement( "tr" );
				trChild = document.createElement( "div" );

				table.style.cssText = "position:absolute;left:-11111px;border-collapse:separate";
				tr.style.cssText = "border:1px solid";

				// Support: Chrome 86+
				// Height set through cssText does not get applied.
				// Computed height then comes back as 0.
				tr.style.height = "1px";
				trChild.style.height = "9px";

				// Support: Android 8 Chrome 86+
				// In our bodyBackground.html iframe,
				// display for all div elements is set to "inline",
				// which causes a problem only in Android 8 Chrome 86.
				// Ensuring the div is display: block
				// gets around this issue.
				trChild.style.display = "block";

				documentElement
					.appendChild( table )
					.appendChild( tr )
					.appendChild( trChild );

				trStyle = window.getComputedStyle( tr );
				reliableTrDimensionsVal = ( parseInt( trStyle.height, 10 ) +
					parseInt( trStyle.borderTopWidth, 10 ) +
					parseInt( trStyle.borderBottomWidth, 10 ) ) === tr.offsetHeight;

				documentElement.removeChild( table );
			}
			return reliableTrDimensionsVal;
		}
	} );
} )();


function curCSS( elem, name, computed ) {
	var width, minWidth, maxWidth, ret,
		isCustomProp = rcustomProp.test( name ),

		// Support: Firefox 51+
		// Retrieving style before computed somehow
		// fixes an issue with getting wrong values
		// on detached elements
		style = elem.style;

	computed = computed || getStyles( elem );

	// getPropertyValue is needed for:
	//   .css('filter') (IE 9 only, trac-12537)
	//   .css('--customProperty) (gh-3144)
	if ( computed ) {

		// Support: IE <=9 - 11+
		// IE only supports `"float"` in `getPropertyValue`; in computed styles
		// it's only available as `"cssFloat"`. We no longer modify properties
		// sent to `.css()` apart from camelCasing, so we need to check both.
		// Normally, this would create difference in behavior: if
		// `getPropertyValue` returns an empty string, the value returned
		// by `.css()` would be `undefined`. This is usually the case for
		// disconnected elements. However, in IE even disconnected elements
		// with no styles return `"none"` for `getPropertyValue( "float" )`
		ret = computed.getPropertyValue( name ) || computed[ name ];

		if ( isCustomProp && ret ) {

			// Support: Firefox 105+, Chrome <=105+
			// Spec requires trimming whitespace for custom properties (gh-4926).
			// Firefox only trims leading whitespace. Chrome just collapses
			// both leading & trailing whitespace to a single space.
			//
			// Fall back to `undefined` if empty string returned.
			// This collapses a missing definition with property defined
			// and set to an empty string but there's no standard API
			// allowing us to differentiate them without a performance penalty
			// and returning `undefined` aligns with older jQuery.
			//
			// rtrimCSS treats U+000D CARRIAGE RETURN and U+000C FORM FEED
			// as whitespace while CSS does not, but this is not a problem
			// because CSS preprocessing replaces them with U+000A LINE FEED
			// (which *is* CSS whitespace)
			// https://www.w3.org/TR/css-syntax-3/#input-preprocessing
			ret = ret.replace( rtrimCSS, "$1" ) || undefined;
		}

		if ( ret === "" && !isAttached( elem ) ) {
			ret = jQuery.style( elem, name );
		}

		// A tribute to the "awesome hack by Dean Edwards"
		// Android Browser returns percentage for some values,
		// but width seems to be reliably pixels.
		// This is against the CSSOM draft spec:
		// https://drafts.csswg.org/cssom/#resolved-values
		if ( !support.pixelBoxStyles() && rnumnonpx.test( ret ) && rboxStyle.test( name ) ) {

			// Remember the original values
			width = style.width;
			minWidth = style.minWidth;
			maxWidth = style.maxWidth;

			// Put in the new values to get a computed value out
			style.minWidth = style.maxWidth = style.width = ret;
			ret = computed.width;

			// Revert the changed values
			style.width = width;
			style.minWidth = minWidth;
			style.maxWidth = maxWidth;
		}
	}

	return ret !== undefined ?

		// Support: IE <=9 - 11 only
		// IE returns zIndex value as an integer.
		ret + "" :
		ret;
}


function addGetHookIf( conditionFn, hookFn ) {

	// Define the hook, we'll check on the first run if it's really needed.
	return {
		get: function() {
			if ( conditionFn() ) {

				// Hook not needed (or it's not possible to use it due
				// to missing dependency), remove it.
				delete this.get;
				return;
			}

			// Hook needed; redefine it so that the support test is not executed again.
			return ( this.get = hookFn ).apply( this, arguments );
		}
	};
}


var cssPrefixes = [ "Webkit", "Moz", "ms" ],
	emptyStyle = document.createElement( "div" ).style,
	vendorProps = {};

// Return a vendor-prefixed property or undefined
function vendorPropName( name ) {

	// Check for vendor prefixed names
	var capName = name[ 0 ].toUpperCase() + name.slice( 1 ),
		i = cssPrefixes.length;

	while ( i-- ) {
		name = cssPrefixes[ i ] + capName;
		if ( name in emptyStyle ) {
			return name;
		}
	}
}

// Return a potentially-mapped jQuery.cssProps or vendor prefixed property
function finalPropName( name ) {
	var final = jQuery.cssProps[ name ] || vendorProps[ name ];

	if ( final ) {
		return final;
	}
	if ( name in emptyStyle ) {
		return name;
	}
	return vendorProps[ name ] = vendorPropName( name ) || name;
}


var

	// Swappable if display is none or starts with table
	// except "table", "table-cell", or "table-caption"
	// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
	cssNormalTransform = {
		letterSpacing: "0",
		fontWeight: "400"
	};

function setPositiveNumber( _elem, value, subtract ) {

	// Any relative (+/-) values have already been
	// normalized at this point
	var matches = rcssNum.exec( value );
	return matches ?

		// Guard against undefined "subtract", e.g., when used as in cssHooks
		Math.max( 0, matches[ 2 ] - ( subtract || 0 ) ) + ( matches[ 3 ] || "px" ) :
		value;
}

function boxModelAdjustment( elem, dimension, box, isBorderBox, styles, computedVal ) {
	var i = dimension === "width" ? 1 : 0,
		extra = 0,
		delta = 0,
		marginDelta = 0;

	// Adjustment may not be necessary
	if ( box === ( isBorderBox ? "border" : "content" ) ) {
		return 0;
	}

	for ( ; i < 4; i += 2 ) {

		// Both box models exclude margin
		// Count margin delta separately to only add it after scroll gutter adjustment.
		// This is needed to make negative margins work with `outerHeight( true )` (gh-3982).
		if ( box === "margin" ) {
			marginDelta += jQuery.css( elem, box + cssExpand[ i ], true, styles );
		}

		// If we get here with a content-box, we're seeking "padding" or "border" or "margin"
		if ( !isBorderBox ) {

			// Add padding
			delta += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );

			// For "border" or "margin", add border
			if ( box !== "padding" ) {
				delta += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );

			// But still keep track of it otherwise
			} else {
				extra += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
			}

		// If we get here with a border-box (content + padding + border), we're seeking "content" or
		// "padding" or "margin"
		} else {

			// For "content", subtract padding
			if ( box === "content" ) {
				delta -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
			}

			// For "content" or "padding", subtract border
			if ( box !== "margin" ) {
				delta -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
			}
		}
	}

	// Account for positive content-box scroll gutter when requested by providing computedVal
	if ( !isBorderBox && computedVal >= 0 ) {

		// offsetWidth/offsetHeight is a rounded sum of content, padding, scroll gutter, and border
		// Assuming integer scroll gutter, subtract the rest and round down
		delta += Math.max( 0, Math.ceil(
			elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
			computedVal -
			delta -
			extra -
			0.5

		// If offsetWidth/offsetHeight is unknown, then we can't determine content-box scroll gutter
		// Use an explicit zero to avoid NaN (gh-3964)
		) ) || 0;
	}

	return delta + marginDelta;
}

function getWidthOrHeight( elem, dimension, extra ) {

	// Start with computed style
	var styles = getStyles( elem ),

		// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-4322).
		// Fake content-box until we know it's needed to know the true value.
		boxSizingNeeded = !support.boxSizingReliable() || extra,
		isBorderBox = boxSizingNeeded &&
			jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
		valueIsBorderBox = isBorderBox,

		val = curCSS( elem, dimension, styles ),
		offsetProp = "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 );

	// Support: Firefox <=54
	// Return a confounding non-pixel value or feign ignorance, as appropriate.
	if ( rnumnonpx.test( val ) ) {
		if ( !extra ) {
			return val;
		}
		val = "auto";
	}


	// Support: IE 9 - 11 only
	// Use offsetWidth/offsetHeight for when box sizing is unreliable.
	// In those cases, the computed value can be trusted to be border-box.
	if ( ( !support.boxSizingReliable() && isBorderBox ||

		// Support: IE 10 - 11+, Edge 15 - 18+
		// IE/Edge misreport `getComputedStyle` of table rows with width/height
		// set in CSS while `offset*` properties report correct values.
		// Interestingly, in some cases IE 9 doesn't suffer from this issue.
		!support.reliableTrDimensions() && nodeName( elem, "tr" ) ||

		// Fall back to offsetWidth/offsetHeight when value is "auto"
		// This happens for inline elements with no explicit setting (gh-3571)
		val === "auto" ||

		// Support: Android <=4.1 - 4.3 only
		// Also use offsetWidth/offsetHeight for misreported inline dimensions (gh-3602)
		!parseFloat( val ) && jQuery.css( elem, "display", false, styles ) === "inline" ) &&

		// Make sure the element is visible & connected
		elem.getClientRects().length ) {

		isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";

		// Where available, offsetWidth/offsetHeight approximate border box dimensions.
		// Where not available (e.g., SVG), assume unreliable box-sizing and interpret the
		// retrieved value as a content box dimension.
		valueIsBorderBox = offsetProp in elem;
		if ( valueIsBorderBox ) {
			val = elem[ offsetProp ];
		}
	}

	// Normalize "" and auto
	val = parseFloat( val ) || 0;

	// Adjust for the element's box model
	return ( val +
		boxModelAdjustment(
			elem,
			dimension,
			extra || ( isBorderBox ? "border" : "content" ),
			valueIsBorderBox,
			styles,

			// Provide the current computed size to request scroll gutter calculation (gh-3589)
			val
		)
	) + "px";
}

jQuery.extend( {

	// Add in style property hooks for overriding the default
	// behavior of getting and setting a style property
	cssHooks: {
		opacity: {
			get: function( elem, computed ) {
				if ( computed ) {

					// We should always get a number back from opacity
					var ret = curCSS( elem, "opacity" );
					return ret === "" ? "1" : ret;
				}
			}
		}
	},

	// Don't automatically add "px" to these possibly-unitless properties
	cssNumber: {
		animationIterationCount: true,
		aspectRatio: true,
		borderImageSlice: true,
		columnCount: true,
		flexGrow: true,
		flexShrink: true,
		fontWeight: true,
		gridArea: true,
		gridColumn: true,
		gridColumnEnd: true,
		gridColumnStart: true,
		gridRow: true,
		gridRowEnd: true,
		gridRowStart: true,
		lineHeight: true,
		opacity: true,
		order: true,
		orphans: true,
		scale: true,
		widows: true,
		zIndex: true,
		zoom: true,

		// SVG-related
		fillOpacity: true,
		floodOpacity: true,
		stopOpacity: true,
		strokeMiterlimit: true,
		strokeOpacity: true
	},

	// Add in properties whose names you wish to fix before
	// setting or getting the value
	cssProps: {},

	// Get and set the style property on a DOM Node
	style: function( elem, name, value, extra ) {

		// Don't set styles on text and comment nodes
		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
			return;
		}

		// Make sure that we're working with the right name
		var ret, type, hooks,
			origName = camelCase( name ),
			isCustomProp = rcustomProp.test( name ),
			style = elem.style;

		// Make sure that we're working with the right name. We don't
		// want to query the value if it is a CSS custom property
		// since they are user-defined.
		if ( !isCustomProp ) {
			name = finalPropName( origName );
		}

		// Gets hook for the prefixed version, then unprefixed version
		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

		// Check if we're setting a value
		if ( value !== undefined ) {
			type = typeof value;

			// Convert "+=" or "-=" to relative numbers (trac-7345)
			if ( type === "string" && ( ret = rcssNum.exec( value ) ) && ret[ 1 ] ) {
				value = adjustCSS( elem, name, ret );

				// Fixes bug trac-9237
				type = "number";
			}

			// Make sure that null and NaN values aren't set (trac-7116)
			if ( value == null || value !== value ) {
				return;
			}

			// If a number was passed in, add the unit (except for certain CSS properties)
			// The isCustomProp check can be removed in jQuery 4.0 when we only auto-append
			// "px" to a few hardcoded values.
			if ( type === "number" && !isCustomProp ) {
				value += ret && ret[ 3 ] || ( jQuery.cssNumber[ origName ] ? "" : "px" );
			}

			// background-* props affect original clone's values
			if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
				style[ name ] = "inherit";
			}

			// If a hook was provided, use that value, otherwise just set the specified value
			if ( !hooks || !( "set" in hooks ) ||
				( value = hooks.set( elem, value, extra ) ) !== undefined ) {

				if ( isCustomProp ) {
					style.setProperty( name, value );
				} else {
					style[ name ] = value;
				}
			}

		} else {

			// If a hook was provided get the non-computed value from there
			if ( hooks && "get" in hooks &&
				( ret = hooks.get( elem, false, extra ) ) !== undefined ) {

				return ret;
			}

			// Otherwise just get the value from the style object
			return style[ name ];
		}
	},

	css: function( elem, name, extra, styles ) {
		var val, num, hooks,
			origName = camelCase( name ),
			isCustomProp = rcustomProp.test( name );

		// Make sure that we're working with the right name. We don't
		// want to modify the value if it is a CSS custom property
		// since they are user-defined.
		if ( !isCustomProp ) {
			name = finalPropName( origName );
		}

		// Try prefixed name followed by the unprefixed name
		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];

		// If a hook was provided get the computed value from there
		if ( hooks && "get" in hooks ) {
			val = hooks.get( elem, true, extra );
		}

		// Otherwise, if a way to get the computed value exists, use that
		if ( val === undefined ) {
			val = curCSS( elem, name, styles );
		}

		// Convert "normal" to computed value
		if ( val === "normal" && name in cssNormalTransform ) {
			val = cssNormalTransform[ name ];
		}

		// Make numeric if forced or a qualifier was provided and val looks numeric
		if ( extra === "" || extra ) {
			num = parseFloat( val );
			return extra === true || isFinite( num ) ? num || 0 : val;
		}

		return val;
	}
} );

jQuery.each( [ "height", "width" ], function( _i, dimension ) {
	jQuery.cssHooks[ dimension ] = {
		get: function( elem, computed, extra ) {
			if ( computed ) {

				// Certain elements can have dimension info if we invisibly show them
				// but it must have a current display style that would benefit
				return rdisplayswap.test( jQuery.css( elem, "display" ) ) &&

					// Support: Safari 8+
					// Table columns in Safari have non-zero offsetWidth & zero
					// getBoundingClientRect().width unless display is changed.
					// Support: IE <=11 only
					// Running getBoundingClientRect on a disconnected node
					// in IE throws an error.
					( !elem.getClientRects().length || !elem.getBoundingClientRect().width ) ?
					swap( elem, cssShow, function() {
						return getWidthOrHeight( elem, dimension, extra );
					} ) :
					getWidthOrHeight( elem, dimension, extra );
			}
		},

		set: function( elem, value, extra ) {
			var matches,
				styles = getStyles( elem ),

				// Only read styles.position if the test has a chance to fail
				// to avoid forcing a reflow.
				scrollboxSizeBuggy = !support.scrollboxSize() &&
					styles.position === "absolute",

				// To avoid forcing a reflow, only fetch boxSizing if we need it (gh-3991)
				boxSizingNeeded = scrollboxSizeBuggy || extra,
				isBorderBox = boxSizingNeeded &&
					jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
				subtract = extra ?
					boxModelAdjustment(
						elem,
						dimension,
						extra,
						isBorderBox,
						styles
					) :
					0;

			// Account for unreliable border-box dimensions by comparing offset* to computed and
			// faking a content-box to get border and padding (gh-3699)
			if ( isBorderBox && scrollboxSizeBuggy ) {
				subtract -= Math.ceil(
					elem[ "offset" + dimension[ 0 ].toUpperCase() + dimension.slice( 1 ) ] -
					parseFloat( styles[ dimension ] ) -
					boxModelAdjustment( elem, dimension, "border", false, styles ) -
					0.5
				);
			}

			// Convert to pixels if value adjustment is needed
			if ( subtract && ( matches = rcssNum.exec( value ) ) &&
				( matches[ 3 ] || "px" ) !== "px" ) {

				elem.style[ dimension ] = value;
				value = jQuery.css( elem, dimension );
			}

			return setPositiveNumber( elem, value, subtract );
		}
	};
} );

jQuery.cssHooks.marginLeft = addGetHookIf( support.reliableMarginLeft,
	function( elem, computed ) {
		if ( computed ) {
			return ( parseFloat( curCSS( elem, "marginLeft" ) ) ||
				elem.getBoundingClientRect().left -
					swap( elem, { marginLeft: 0 }, function() {
						return elem.getBoundingClientRect().left;
					} )
			) + "px";
		}
	}
);

// These hooks are used by animate to expand properties
jQuery.each( {
	margin: "",
	padding: "",
	border: "Width"
}, function( prefix, suffix ) {
	jQuery.cssHooks[ prefix + suffix ] = {
		expand: function( value ) {
			var i = 0,
				expanded = {},

				// Assumes a single number if not a string
				parts = typeof value === "string" ? value.split( " " ) : [ value ];

			for ( ; i < 4; i++ ) {
				expanded[ prefix + cssExpand[ i ] + suffix ] =
					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
			}

			return expanded;
		}
	};

	if ( prefix !== "margin" ) {
		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
	}
} );

jQuery.fn.extend( {
	css: function( name, value ) {
		return access( this, function( elem, name, value ) {
			var styles, len,
				map = {},
				i = 0;

			if ( Array.isArray( name ) ) {
				styles = getStyles( elem );
				len = name.length;

				for ( ; i < len; i++ ) {
					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
				}

				return map;
			}

			return value !== undefined ?
				jQuery.style( elem, name, value ) :
				jQuery.css( elem, name );
		}, name, value, arguments.length > 1 );
	}
} );


function Tween( elem, options, prop, end, easing ) {
	return new Tween.prototype.init( elem, options, prop, end, easing );
}
jQuery.Tween = Tween;

Tween.prototype = {
	constructor: Tween,
	init: function( elem, options, prop, end, easing, unit ) {
		this.elem = elem;
		this.prop = prop;
		this.easing = easing || jQuery.easing._default;
		this.options = options;
		this.start = this.now = this.cur();
		this.end = end;
		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
	},
	cur: function() {
		var hooks = Tween.propHooks[ this.prop ];

		return hooks && hooks.get ?
			hooks.get( this ) :
			Tween.propHooks._default.get( this );
	},
	run: function( percent ) {
		var eased,
			hooks = Tween.propHooks[ this.prop ];

		if ( this.options.duration ) {
			this.pos = eased = jQuery.easing[ this.easing ](
				percent, this.options.duration * percent, 0, 1, this.options.duration
			);
		} else {
			this.pos = eased = percent;
		}
		this.now = ( this.end - this.start ) * eased + this.start;

		if ( this.options.step ) {
			this.options.step.call( this.elem, this.now, this );
		}

		if ( hooks && hooks.set ) {
			hooks.set( this );
		} else {
			Tween.propHooks._default.set( this );
		}
		return this;
	}
};

Tween.prototype.init.prototype = Tween.prototype;

Tween.propHooks = {
	_default: {
		get: function( tween ) {
			var result;

			// Use a property on the element directly when it is not a DOM element,
			// or when there is no matching style property that exists.
			if ( tween.elem.nodeType !== 1 ||
				tween.elem[ tween.prop ] != null && tween.elem.style[ tween.prop ] == null ) {
				return tween.elem[ tween.prop ];
			}

			// Passing an empty string as a 3rd parameter to .css will automatically
			// attempt a parseFloat and fallback to a string if the parse fails.
			// Simple values such as "10px" are parsed to Float;
			// complex values such as "rotate(1rad)" are returned as-is.
			result = jQuery.css( tween.elem, tween.prop, "" );

			// Empty strings, null, undefined and "auto" are converted to 0.
			return !result || result === "auto" ? 0 : result;
		},
		set: function( tween ) {

			// Use step hook for back compat.
			// Use cssHook if its there.
			// Use .style if available and use plain properties where available.
			if ( jQuery.fx.step[ tween.prop ] ) {
				jQuery.fx.step[ tween.prop ]( tween );
			} else if ( tween.elem.nodeType === 1 && (
				jQuery.cssHooks[ tween.prop ] ||
					tween.elem.style[ finalPropName( tween.prop ) ] != null ) ) {
				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
			} else {
				tween.elem[ tween.prop ] = tween.now;
			}
		}
	}
};

// Support: IE <=9 only
// Panic based approach to setting things on disconnected nodes
Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
	set: function( tween ) {
		if ( tween.elem.nodeType && tween.elem.parentNode ) {
			tween.elem[ tween.prop ] = tween.now;
		}
	}
};

jQuery.easing = {
	linear: function( p ) {
		return p;
	},
	swing: function( p ) {
		return 0.5 - Math.cos( p * Math.PI ) / 2;
	},
	_default: "swing"
};

jQuery.fx = Tween.prototype.init;

// Back compat <1.8 extension point
jQuery.fx.step = {};




var
	fxNow, inProgress,
	rfxtypes = /^(?:toggle|show|hide)$/,
	rrun = /queueHooks$/;

function schedule() {
	if ( inProgress ) {
		if ( document.hidden === false && window.requestAnimationFrame ) {
			window.requestAnimationFrame( schedule );
		} else {
			window.setTimeout( schedule, jQuery.fx.interval );
		}

		jQuery.fx.tick();
	}
}

// Animations created synchronously will run synchronously
function createFxNow() {
	window.setTimeout( function() {
		fxNow = undefined;
	} );
	return ( fxNow = Date.now() );
}

// Generate parameters to create a standard animation
function genFx( type, includeWidth ) {
	var which,
		i = 0,
		attrs = { height: type };

	// If we include width, step value is 1 to do all cssExpand values,
	// otherwise step value is 2 to skip over Left and Right
	includeWidth = includeWidth ? 1 : 0;
	for ( ; i < 4; i += 2 - includeWidth ) {
		which = cssExpand[ i ];
		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
	}

	if ( includeWidth ) {
		attrs.opacity = attrs.width = type;
	}

	return attrs;
}

function createTween( value, prop, animation ) {
	var tween,
		collection = ( Animation.tweeners[ prop ] || [] ).concat( Animation.tweeners[ "*" ] ),
		index = 0,
		length = collection.length;
	for ( ; index < length; index++ ) {
		if ( ( tween = collection[ index ].call( animation, prop, value ) ) ) {

			// We're done with this property
			return tween;
		}
	}
}

function defaultPrefilter( elem, props, opts ) {
	var prop, value, toggle, hooks, oldfire, propTween, restoreDisplay, display,
		isBox = "width" in props || "height" in props,
		anim = this,
		orig = {},
		style = elem.style,
		hidden = elem.nodeType && isHiddenWithinTree( elem ),
		dataShow = dataPriv.get( elem, "fxshow" );

	// Queue-skipping animations hijack the fx hooks
	if ( !opts.queue ) {
		hooks = jQuery._queueHooks( elem, "fx" );
		if ( hooks.unqueued == null ) {
			hooks.unqueued = 0;
			oldfire = hooks.empty.fire;
			hooks.empty.fire = function() {
				if ( !hooks.unqueued ) {
					oldfire();
				}
			};
		}
		hooks.unqueued++;

		anim.always( function() {

			// Ensure the complete handler is called before this completes
			anim.always( function() {
				hooks.unqueued--;
				if ( !jQuery.queue( elem, "fx" ).length ) {
					hooks.empty.fire();
				}
			} );
		} );
	}

	// Detect show/hide animations
	for ( prop in props ) {
		value = props[ prop ];
		if ( rfxtypes.test( value ) ) {
			delete props[ prop ];
			toggle = toggle || value === "toggle";
			if ( value === ( hidden ? "hide" : "show" ) ) {

				// Pretend to be hidden if this is a "show" and
				// there is still data from a stopped show/hide
				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
					hidden = true;

				// Ignore all other no-op show/hide data
				} else {
					continue;
				}
			}
			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
		}
	}

	// Bail out if this is a no-op like .hide().hide()
	propTween = !jQuery.isEmptyObject( props );
	if ( !propTween && jQuery.isEmptyObject( orig ) ) {
		return;
	}

	// Restrict "overflow" and "display" styles during box animations
	if ( isBox && elem.nodeType === 1 ) {

		// Support: IE <=9 - 11, Edge 12 - 15
		// Record all 3 overflow attributes because IE does not infer the shorthand
		// from identically-valued overflowX and overflowY and Edge just mirrors
		// the overflowX value there.
		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];

		// Identify a display type, preferring old show/hide data over the CSS cascade
		restoreDisplay = dataShow && dataShow.display;
		if ( restoreDisplay == null ) {
			restoreDisplay = dataPriv.get( elem, "display" );
		}
		display = jQuery.css( elem, "display" );
		if ( display === "none" ) {
			if ( restoreDisplay ) {
				display = restoreDisplay;
			} else {

				// Get nonempty value(s) by temporarily forcing visibility
				showHide( [ elem ], true );
				restoreDisplay = elem.style.display || restoreDisplay;
				display = jQuery.css( elem, "display" );
				showHide( [ elem ] );
			}
		}

		// Animate inline elements as inline-block
		if ( display === "inline" || display === "inline-block" && restoreDisplay != null ) {
			if ( jQuery.css( elem, "float" ) === "none" ) {

				// Restore the original display value at the end of pure show/hide animations
				if ( !propTween ) {
					anim.done( function() {
						style.display = restoreDisplay;
					} );
					if ( restoreDisplay == null ) {
						display = style.display;
						restoreDisplay = display === "none" ? "" : display;
					}
				}
				style.display = "inline-block";
			}
		}
	}

	if ( opts.overflow ) {
		style.overflow = "hidden";
		anim.always( function() {
			style.overflow = opts.overflow[ 0 ];
			style.overflowX = opts.overflow[ 1 ];
			style.overflowY = opts.overflow[ 2 ];
		} );
	}

	// Implement show/hide animations
	propTween = false;
	for ( prop in orig ) {

		// General show/hide setup for this element animation
		if ( !propTween ) {
			if ( dataShow ) {
				if ( "hidden" in dataShow ) {
					hidden = dataShow.hidden;
				}
			} else {
				dataShow = dataPriv.access( elem, "fxshow", { display: restoreDisplay } );
			}

			// Store hidden/visible for toggle so `.stop().toggle()` "reverses"
			if ( toggle ) {
				dataShow.hidden = !hidden;
			}

			// Show elements before animating them
			if ( hidden ) {
				showHide( [ elem ], true );
			}

			/* eslint-disable no-loop-func */

			anim.done( function() {

				/* eslint-enable no-loop-func */

				// The final step of a "hide" animation is actually hiding the element
				if ( !hidden ) {
					showHide( [ elem ] );
				}
				dataPriv.remove( elem, "fxshow" );
				for ( prop in orig ) {
					jQuery.style( elem, prop, orig[ prop ] );
				}
			} );
		}

		// Per-property setup
		propTween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
		if ( !( prop in dataShow ) ) {
			dataShow[ prop ] = propTween.start;
			if ( hidden ) {
				propTween.end = propTween.start;
				propTween.start = 0;
			}
		}
	}
}

function propFilter( props, specialEasing ) {
	var index, name, easing, value, hooks;

	// camelCase, specialEasing and expand cssHook pass
	for ( index in props ) {
		name = camelCase( index );
		easing = specialEasing[ name ];
		value = props[ index ];
		if ( Array.isArray( value ) ) {
			easing = value[ 1 ];
			value = props[ index ] = value[ 0 ];
		}

		if ( index !== name ) {
			props[ name ] = value;
			delete props[ index ];
		}

		hooks = jQuery.cssHooks[ name ];
		if ( hooks && "expand" in hooks ) {
			value = hooks.expand( value );
			delete props[ name ];

			// Not quite $.extend, this won't overwrite existing keys.
			// Reusing 'index' because we have the correct "name"
			for ( index in value ) {
				if ( !( index in props ) ) {
					props[ index ] = value[ index ];
					specialEasing[ index ] = easing;
				}
			}
		} else {
			specialEasing[ name ] = easing;
		}
	}
}

function Animation( elem, properties, options ) {
	var result,
		stopped,
		index = 0,
		length = Animation.prefilters.length,
		deferred = jQuery.Deferred().always( function() {

			// Don't match elem in the :animated selector
			delete tick.elem;
		} ),
		tick = function() {
			if ( stopped ) {
				return false;
			}
			var currentTime = fxNow || createFxNow(),
				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),

				// Support: Android 2.3 only
				// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (trac-12497)
				temp = remaining / animation.duration || 0,
				percent = 1 - temp,
				index = 0,
				length = animation.tweens.length;

			for ( ; index < length; index++ ) {
				animation.tweens[ index ].run( percent );
			}

			deferred.notifyWith( elem, [ animation, percent, remaining ] );

			// If there's more to do, yield
			if ( percent < 1 && length ) {
				return remaining;
			}

			// If this was an empty animation, synthesize a final progress notification
			if ( !length ) {
				deferred.notifyWith( elem, [ animation, 1, 0 ] );
			}

			// Resolve the animation and report its conclusion
			deferred.resolveWith( elem, [ animation ] );
			return false;
		},
		animation = deferred.promise( {
			elem: elem,
			props: jQuery.extend( {}, properties ),
			opts: jQuery.extend( true, {
				specialEasing: {},
				easing: jQuery.easing._default
			}, options ),
			originalProperties: properties,
			originalOptions: options,
			startTime: fxNow || createFxNow(),
			duration: options.duration,
			tweens: [],
			createTween: function( prop, end ) {
				var tween = jQuery.Tween( elem, animation.opts, prop, end,
					animation.opts.specialEasing[ prop ] || animation.opts.easing );
				animation.tweens.push( tween );
				return tween;
			},
			stop: function( gotoEnd ) {
				var index = 0,

					// If we are going to the end, we want to run all the tweens
					// otherwise we skip this part
					length = gotoEnd ? animation.tweens.length : 0;
				if ( stopped ) {
					return this;
				}
				stopped = true;
				for ( ; index < length; index++ ) {
					animation.tweens[ index ].run( 1 );
				}

				// Resolve when we played the last frame; otherwise, reject
				if ( gotoEnd ) {
					deferred.notifyWith( elem, [ animation, 1, 0 ] );
					deferred.resolveWith( elem, [ animation, gotoEnd ] );
				} else {
					deferred.rejectWith( elem, [ animation, gotoEnd ] );
				}
				return this;
			}
		} ),
		props = animation.props;

	propFilter( props, animation.opts.specialEasing );

	for ( ; index < length; index++ ) {
		result = Animation.prefilters[ index ].call( animation, elem, props, animation.opts );
		if ( result ) {
			if ( isFunction( result.stop ) ) {
				jQuery._queueHooks( animation.elem, animation.opts.queue ).stop =
					result.stop.bind( result );
			}
			return result;
		}
	}

	jQuery.map( props, createTween, animation );

	if ( isFunction( animation.opts.start ) ) {
		animation.opts.start.call( elem, animation );
	}

	// Attach callbacks from options
	animation
		.progress( animation.opts.progress )
		.done( animation.opts.done, animation.opts.complete )
		.fail( animation.opts.fail )
		.always( animation.opts.always );

	jQuery.fx.timer(
		jQuery.extend( tick, {
			elem: elem,
			anim: animation,
			queue: animation.opts.queue
		} )
	);

	return animation;
}

jQuery.Animation = jQuery.extend( Animation, {

	tweeners: {
		"*": [ function( prop, value ) {
			var tween = this.createTween( prop, value );
			adjustCSS( tween.elem, prop, rcssNum.exec( value ), tween );
			return tween;
		} ]
	},

	tweener: function( props, callback ) {
		if ( isFunction( props ) ) {
			callback = props;
			props = [ "*" ];
		} else {
			props = props.match( rnothtmlwhite );
		}

		var prop,
			index = 0,
			length = props.length;

		for ( ; index < length; index++ ) {
			prop = props[ index ];
			Animation.tweeners[ prop ] = Animation.tweeners[ prop ] || [];
			Animation.tweeners[ prop ].unshift( callback );
		}
	},

	prefilters: [ defaultPrefilter ],

	prefilter: function( callback, prepend ) {
		if ( prepend ) {
			Animation.prefilters.unshift( callback );
		} else {
			Animation.prefilters.push( callback );
		}
	}
} );

jQuery.speed = function( speed, easing, fn ) {
	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
		complete: fn || !fn && easing ||
			isFunction( speed ) && speed,
		duration: speed,
		easing: fn && easing || easing && !isFunction( easing ) && easing
	};

	// Go to the end state if fx are off
	if ( jQuery.fx.off ) {
		opt.duration = 0;

	} else {
		if ( typeof opt.duration !== "number" ) {
			if ( opt.duration in jQuery.fx.speeds ) {
				opt.duration = jQuery.fx.speeds[ opt.duration ];

			} else {
				opt.duration = jQuery.fx.speeds._default;
			}
		}
	}

	// Normalize opt.queue - true/undefined/null -> "fx"
	if ( opt.queue == null || opt.queue === true ) {
		opt.queue = "fx";
	}

	// Queueing
	opt.old = opt.complete;

	opt.complete = function() {
		if ( isFunction( opt.old ) ) {
			opt.old.call( this );
		}

		if ( opt.queue ) {
			jQuery.dequeue( this, opt.queue );
		}
	};

	return opt;
};

jQuery.fn.extend( {
	fadeTo: function( speed, to, easing, callback ) {

		// Show any hidden elements after setting opacity to 0
		return this.filter( isHiddenWithinTree ).css( "opacity", 0 ).show()

			// Animate to the value specified
			.end().animate( { opacity: to }, speed, easing, callback );
	},
	animate: function( prop, speed, easing, callback ) {
		var empty = jQuery.isEmptyObject( prop ),
			optall = jQuery.speed( speed, easing, callback ),
			doAnimation = function() {

				// Operate on a copy of prop so per-property easing won't be lost
				var anim = Animation( this, jQuery.extend( {}, prop ), optall );

				// Empty animations, or finishing resolves immediately
				if ( empty || dataPriv.get( this, "finish" ) ) {
					anim.stop( true );
				}
			};

		doAnimation.finish = doAnimation;

		return empty || optall.queue === false ?
			this.each( doAnimation ) :
			this.queue( optall.queue, doAnimation );
	},
	stop: function( type, clearQueue, gotoEnd ) {
		var stopQueue = function( hooks ) {
			var stop = hooks.stop;
			delete hooks.stop;
			stop( gotoEnd );
		};

		if ( typeof type !== "string" ) {
			gotoEnd = clearQueue;
			clearQueue = type;
			type = undefined;
		}
		if ( clearQueue ) {
			this.queue( type || "fx", [] );
		}

		return this.each( function() {
			var dequeue = true,
				index = type != null && type + "queueHooks",
				timers = jQuery.timers,
				data = dataPriv.get( this );

			if ( index ) {
				if ( data[ index ] && data[ index ].stop ) {
					stopQueue( data[ index ] );
				}
			} else {
				for ( index in data ) {
					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
						stopQueue( data[ index ] );
					}
				}
			}

			for ( index = timers.length; index--; ) {
				if ( timers[ index ].elem === this &&
					( type == null || timers[ index ].queue === type ) ) {

					timers[ index ].anim.stop( gotoEnd );
					dequeue = false;
					timers.splice( index, 1 );
				}
			}

			// Start the next in the queue if the last step wasn't forced.
			// Timers currently will call their complete callbacks, which
			// will dequeue but only if they were gotoEnd.
			if ( dequeue || !gotoEnd ) {
				jQuery.dequeue( this, type );
			}
		} );
	},
	finish: function( type ) {
		if ( type !== false ) {
			type = type || "fx";
		}
		return this.each( function() {
			var index,
				data = dataPriv.get( this ),
				queue = data[ type + "queue" ],
				hooks = data[ type + "queueHooks" ],
				timers = jQuery.timers,
				length = queue ? queue.length : 0;

			// Enable finishing flag on private data
			data.finish = true;

			// Empty the queue first
			jQuery.queue( this, type, [] );

			if ( hooks && hooks.stop ) {
				hooks.stop.call( this, true );
			}

			// Look for any active animations, and finish them
			for ( index = timers.length; index--; ) {
				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
					timers[ index ].anim.stop( true );
					timers.splice( index, 1 );
				}
			}

			// Look for any animations in the old queue and finish them
			for ( index = 0; index < length; index++ ) {
				if ( queue[ index ] && queue[ index ].finish ) {
					queue[ index ].finish.call( this );
				}
			}

			// Turn off finishing flag
			delete data.finish;
		} );
	}
} );

jQuery.each( [ "toggle", "show", "hide" ], function( _i, name ) {
	var cssFn = jQuery.fn[ name ];
	jQuery.fn[ name ] = function( speed, easing, callback ) {
		return speed == null || typeof speed === "boolean" ?
			cssFn.apply( this, arguments ) :
			this.animate( genFx( name, true ), speed, easing, callback );
	};
} );

// Generate shortcuts for custom animations
jQuery.each( {
	slideDown: genFx( "show" ),
	slideUp: genFx( "hide" ),
	slideToggle: genFx( "toggle" ),
	fadeIn: { opacity: "show" },
	fadeOut: { opacity: "hide" },
	fadeToggle: { opacity: "toggle" }
}, function( name, props ) {
	jQuery.fn[ name ] = function( speed, easing, callback ) {
		return this.animate( props, speed, easing, callback );
	};
} );

jQuery.timers = [];
jQuery.fx.tick = function() {
	var timer,
		i = 0,
		timers = jQuery.timers;

	fxNow = Date.now();

	for ( ; i < timers.length; i++ ) {
		timer = timers[ i ];

		// Run the timer and safely remove it when done (allowing for external removal)
		if ( !timer() && timers[ i ] === timer ) {
			timers.splice( i--, 1 );
		}
	}

	if ( !timers.length ) {
		jQuery.fx.stop();
	}
	fxNow = undefined;
};

jQuery.fx.timer = function( timer ) {
	jQuery.timers.push( timer );
	jQuery.fx.start();
};

jQuery.fx.interval = 13;
jQuery.fx.start = function() {
	if ( inProgress ) {
		return;
	}

	inProgress = true;
	schedule();
};

jQuery.fx.stop = function() {
	inProgress = null;
};

jQuery.fx.speeds = {
	slow: 600,
	fast: 200,

	// Default speed
	_default: 400
};


// Based off of the plugin by Clint Helfers, with permission.
jQuery.fn.delay = function( time, type ) {
	time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
	type = type || "fx";

	return this.queue( type, function( next, hooks ) {
		var timeout = window.setTimeout( next, time );
		hooks.stop = function() {
			window.clearTimeout( timeout );
		};
	} );
};


( function() {
	var input = document.createElement( "input" ),
		select = document.createElement( "select" ),
		opt = select.appendChild( document.createElement( "option" ) );

	input.type = "checkbox";

	// Support: Android <=4.3 only
	// Default value for a checkbox should be "on"
	support.checkOn = input.value !== "";

	// Support: IE <=11 only
	// Must access selectedIndex to make default options select
	support.optSelected = opt.selected;

	// Support: IE <=11 only
	// An input loses its value after becoming a radio
	input = document.createElement( "input" );
	input.value = "t";
	input.type = "radio";
	support.radioValue = input.value === "t";
} )();


var boolHook,
	attrHandle = jQuery.expr.attrHandle;

jQuery.fn.extend( {
	attr: function( name, value ) {
		return access( this, jQuery.attr, name, value, arguments.length > 1 );
	},

	removeAttr: function( name ) {
		return this.each( function() {
			jQuery.removeAttr( this, name );
		} );
	}
} );

jQuery.extend( {
	attr: function( elem, name, value ) {
		var ret, hooks,
			nType = elem.nodeType;

		// Don't get/set attributes on text, comment and attribute nodes
		if ( nType === 3 || nType === 8 || nType === 2 ) {
			return;
		}

		// Fallback to prop when attributes are not supported
		if ( typeof elem.getAttribute === "undefined" ) {
			return jQuery.prop( elem, name, value );
		}

		// Attribute hooks are determined by the lowercase version
		// Grab necessary hook if one is defined
		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
			hooks = jQuery.attrHooks[ name.toLowerCase() ] ||
				( jQuery.expr.match.bool.test( name ) ? boolHook : undefined );
		}

		if ( value !== undefined ) {
			if ( value === null ) {
				jQuery.removeAttr( elem, name );
				return;
			}

			if ( hooks && "set" in hooks &&
				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
				return ret;
			}

			elem.setAttribute( name, value + "" );
			return value;
		}

		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
			return ret;
		}

		ret = jQuery.find.attr( elem, name );

		// Non-existent attributes return null, we normalize to undefined
		return ret == null ? undefined : ret;
	},

	attrHooks: {
		type: {
			set: function( elem, value ) {
				if ( !support.radioValue && value === "radio" &&
					nodeName( elem, "input" ) ) {
					var val = elem.value;
					elem.setAttribute( "type", value );
					if ( val ) {
						elem.value = val;
					}
					return value;
				}
			}
		}
	},

	removeAttr: function( elem, value ) {
		var name,
			i = 0,

			// Attribute names can contain non-HTML whitespace characters
			// https://html.spec.whatwg.org/multipage/syntax.html#attributes-2
			attrNames = value && value.match( rnothtmlwhite );

		if ( attrNames && elem.nodeType === 1 ) {
			while ( ( name = attrNames[ i++ ] ) ) {
				elem.removeAttribute( name );
			}
		}
	}
} );

// Hooks for boolean attributes
boolHook = {
	set: function( elem, value, name ) {
		if ( value === false ) {

			// Remove boolean attributes when set to false
			jQuery.removeAttr( elem, name );
		} else {
			elem.setAttribute( name, name );
		}
		return name;
	}
};

jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( _i, name ) {
	var getter = attrHandle[ name ] || jQuery.find.attr;

	attrHandle[ name ] = function( elem, name, isXML ) {
		var ret, handle,
			lowercaseName = name.toLowerCase();

		if ( !isXML ) {

			// Avoid an infinite loop by temporarily removing this function from the getter
			handle = attrHandle[ lowercaseName ];
			attrHandle[ lowercaseName ] = ret;
			ret = getter( elem, name, isXML ) != null ?
				lowercaseName :
				null;
			attrHandle[ lowercaseName ] = handle;
		}
		return ret;
	};
} );




var rfocusable = /^(?:input|select|textarea|button)$/i,
	rclickable = /^(?:a|area)$/i;

jQuery.fn.extend( {
	prop: function( name, value ) {
		return access( this, jQuery.prop, name, value, arguments.length > 1 );
	},

	removeProp: function( name ) {
		return this.each( function() {
			delete this[ jQuery.propFix[ name ] || name ];
		} );
	}
} );

jQuery.extend( {
	prop: function( elem, name, value ) {
		var ret, hooks,
			nType = elem.nodeType;

		// Don't get/set properties on text, comment and attribute nodes
		if ( nType === 3 || nType === 8 || nType === 2 ) {
			return;
		}

		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {

			// Fix name and attach hooks
			name = jQuery.propFix[ name ] || name;
			hooks = jQuery.propHooks[ name ];
		}

		if ( value !== undefined ) {
			if ( hooks && "set" in hooks &&
				( ret = hooks.set( elem, value, name ) ) !== undefined ) {
				return ret;
			}

			return ( elem[ name ] = value );
		}

		if ( hooks && "get" in hooks && ( ret = hooks.get( elem, name ) ) !== null ) {
			return ret;
		}

		return elem[ name ];
	},

	propHooks: {
		tabIndex: {
			get: function( elem ) {

				// Support: IE <=9 - 11 only
				// elem.tabIndex doesn't always return the
				// correct value when it hasn't been explicitly set
				// Use proper attribute retrieval (trac-12072)
				var tabindex = jQuery.find.attr( elem, "tabindex" );

				if ( tabindex ) {
					return parseInt( tabindex, 10 );
				}

				if (
					rfocusable.test( elem.nodeName ) ||
					rclickable.test( elem.nodeName ) &&
					elem.href
				) {
					return 0;
				}

				return -1;
			}
		}
	},

	propFix: {
		"for": "htmlFor",
		"class": "className"
	}
} );

// Support: IE <=11 only
// Accessing the selectedIndex property
// forces the browser to respect setting selected
// on the option
// The getter ensures a default option is selected
// when in an optgroup
// eslint rule "no-unused-expressions" is disabled for this code
// since it considers such accessions noop
if ( !support.optSelected ) {
	jQuery.propHooks.selected = {
		get: function( elem ) {

			/* eslint no-unused-expressions: "off" */

			var parent = elem.parentNode;
			if ( parent && parent.parentNode ) {
				parent.parentNode.selectedIndex;
			}
			return null;
		},
		set: function( elem ) {

			/* eslint no-unused-expressions: "off" */

			var parent = elem.parentNode;
			if ( parent ) {
				parent.selectedIndex;

				if ( parent.parentNode ) {
					parent.parentNode.selectedIndex;
				}
			}
		}
	};
}

jQuery.each( [
	"tabIndex",
	"readOnly",
	"maxLength",
	"cellSpacing",
	"cellPadding",
	"rowSpan",
	"colSpan",
	"useMap",
	"frameBorder",
	"contentEditable"
], function() {
	jQuery.propFix[ this.toLowerCase() ] = this;
} );




	// Strip and collapse whitespace according to HTML spec
	// https://infra.spec.whatwg.org/#strip-and-collapse-ascii-whitespace
	function stripAndCollapse( value ) {
		var tokens = value.match( rnothtmlwhite ) || [];
		return tokens.join( " " );
	}


function getClass( elem ) {
	return elem.getAttribute && elem.getAttribute( "class" ) || "";
}

function classesToArray( value ) {
	if ( Array.isArray( value ) ) {
		return value;
	}
	if ( typeof value === "string" ) {
		return value.match( rnothtmlwhite ) || [];
	}
	return [];
}

jQuery.fn.extend( {
	addClass: function( value ) {
		var classNames, cur, curValue, className, i, finalValue;

		if ( isFunction( value ) ) {
			return this.each( function( j ) {
				jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
			} );
		}

		classNames = classesToArray( value );

		if ( classNames.length ) {
			return this.each( function() {
				curValue = getClass( this );
				cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );

				if ( cur ) {
					for ( i = 0; i < classNames.length; i++ ) {
						className = classNames[ i ];
						if ( cur.indexOf( " " + className + " " ) < 0 ) {
							cur += className + " ";
						}
					}

					// Only assign if different to avoid unneeded rendering.
					finalValue = stripAndCollapse( cur );
					if ( curValue !== finalValue ) {
						this.setAttribute( "class", finalValue );
					}
				}
			} );
		}

		return this;
	},

	removeClass: function( value ) {
		var classNames, cur, curValue, className, i, finalValue;

		if ( isFunction( value ) ) {
			return this.each( function( j ) {
				jQuery( this ).removeClass( value.call( this, j, getClass( this ) ) );
			} );
		}

		if ( !arguments.length ) {
			return this.attr( "class", "" );
		}

		classNames = classesToArray( value );

		if ( classNames.length ) {
			return this.each( function() {
				curValue = getClass( this );

				// This expression is here for better compressibility (see addClass)
				cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );

				if ( cur ) {
					for ( i = 0; i < classNames.length; i++ ) {
						className = classNames[ i ];

						// Remove *all* instances
						while ( cur.indexOf( " " + className + " " ) > -1 ) {
							cur = cur.replace( " " + className + " ", " " );
						}
					}

					// Only assign if different to avoid unneeded rendering.
					finalValue = stripAndCollapse( cur );
					if ( curValue !== finalValue ) {
						this.setAttribute( "class", finalValue );
					}
				}
			} );
		}

		return this;
	},

	toggleClass: function( value, stateVal ) {
		var classNames, className, i, self,
			type = typeof value,
			isValidValue = type === "string" || Array.isArray( value );

		if ( isFunction( value ) ) {
			return this.each( function( i ) {
				jQuery( this ).toggleClass(
					value.call( this, i, getClass( this ), stateVal ),
					stateVal
				);
			} );
		}

		if ( typeof stateVal === "boolean" && isValidValue ) {
			return stateVal ? this.addClass( value ) : this.removeClass( value );
		}

		classNames = classesToArray( value );

		return this.each( function() {
			if ( isValidValue ) {

				// Toggle individual class names
				self = jQuery( this );

				for ( i = 0; i < classNames.length; i++ ) {
					className = classNames[ i ];

					// Check each className given, space separated list
					if ( self.hasClass( className ) ) {
						self.removeClass( className );
					} else {
						self.addClass( className );
					}
				}

			// Toggle whole class name
			} else if ( value === undefined || type === "boolean" ) {
				className = getClass( this );
				if ( className ) {

					// Store className if set
					dataPriv.set( this, "__className__", className );
				}

				// If the element has a class name or if we're passed `false`,
				// then remove the whole classname (if there was one, the above saved it).
				// Otherwise bring back whatever was previously saved (if anything),
				// falling back to the empty string if nothing was stored.
				if ( this.setAttribute ) {
					this.setAttribute( "class",
						className || value === false ?
							"" :
							dataPriv.get( this, "__className__" ) || ""
					);
				}
			}
		} );
	},

	hasClass: function( selector ) {
		var className, elem,
			i = 0;

		className = " " + selector + " ";
		while ( ( elem = this[ i++ ] ) ) {
			if ( elem.nodeType === 1 &&
				( " " + stripAndCollapse( getClass( elem ) ) + " " ).indexOf( className ) > -1 ) {
				return true;
			}
		}

		return false;
	}
} );




var rreturn = /\r/g;

jQuery.fn.extend( {
	val: function( value ) {
		var hooks, ret, valueIsFunction,
			elem = this[ 0 ];

		if ( !arguments.length ) {
			if ( elem ) {
				hooks = jQuery.valHooks[ elem.type ] ||
					jQuery.valHooks[ elem.nodeName.toLowerCase() ];

				if ( hooks &&
					"get" in hooks &&
					( ret = hooks.get( elem, "value" ) ) !== undefined
				) {
					return ret;
				}

				ret = elem.value;

				// Handle most common string cases
				if ( typeof ret === "string" ) {
					return ret.replace( rreturn, "" );
				}

				// Handle cases where value is null/undef or number
				return ret == null ? "" : ret;
			}

			return;
		}

		valueIsFunction = isFunction( value );

		return this.each( function( i ) {
			var val;

			if ( this.nodeType !== 1 ) {
				return;
			}

			if ( valueIsFunction ) {
				val = value.call( this, i, jQuery( this ).val() );
			} else {
				val = value;
			}

			// Treat null/undefined as ""; convert numbers to string
			if ( val == null ) {
				val = "";

			} else if ( typeof val === "number" ) {
				val += "";

			} else if ( Array.isArray( val ) ) {
				val = jQuery.map( val, function( value ) {
					return value == null ? "" : value + "";
				} );
			}

			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];

			// If set returns undefined, fall back to normal setting
			if ( !hooks || !( "set" in hooks ) || hooks.set( this, val, "value" ) === undefined ) {
				this.value = val;
			}
		} );
	}
} );

jQuery.extend( {
	valHooks: {
		option: {
			get: function( elem ) {

				var val = jQuery.find.attr( elem, "value" );
				return val != null ?
					val :

					// Support: IE <=10 - 11 only
					// option.text throws exceptions (trac-14686, trac-14858)
					// Strip and collapse whitespace
					// https://html.spec.whatwg.org/#strip-and-collapse-whitespace
					stripAndCollapse( jQuery.text( elem ) );
			}
		},
		select: {
			get: function( elem ) {
				var value, option, i,
					options = elem.options,
					index = elem.selectedIndex,
					one = elem.type === "select-one",
					values = one ? null : [],
					max = one ? index + 1 : options.length;

				if ( index < 0 ) {
					i = max;

				} else {
					i = one ? index : 0;
				}

				// Loop through all the selected options
				for ( ; i < max; i++ ) {
					option = options[ i ];

					// Support: IE <=9 only
					// IE8-9 doesn't update selected after form reset (trac-2551)
					if ( ( option.selected || i === index ) &&

							// Don't return options that are disabled or in a disabled optgroup
							!option.disabled &&
							( !option.parentNode.disabled ||
								!nodeName( option.parentNode, "optgroup" ) ) ) {

						// Get the specific value for the option
						value = jQuery( option ).val();

						// We don't need an array for one selects
						if ( one ) {
							return value;
						}

						// Multi-Selects return an array
						values.push( value );
					}
				}

				return values;
			},

			set: function( elem, value ) {
				var optionSet, option,
					options = elem.options,
					values = jQuery.makeArray( value ),
					i = options.length;

				while ( i-- ) {
					option = options[ i ];

					/* eslint-disable no-cond-assign */

					if ( option.selected =
						jQuery.inArray( jQuery.valHooks.option.get( option ), values ) > -1
					) {
						optionSet = true;
					}

					/* eslint-enable no-cond-assign */
				}

				// Force browsers to behave consistently when non-matching value is set
				if ( !optionSet ) {
					elem.selectedIndex = -1;
				}
				return values;
			}
		}
	}
} );

// Radios and checkboxes getter/setter
jQuery.each( [ "radio", "checkbox" ], function() {
	jQuery.valHooks[ this ] = {
		set: function( elem, value ) {
			if ( Array.isArray( value ) ) {
				return ( elem.checked = jQuery.inArray( jQuery( elem ).val(), value ) > -1 );
			}
		}
	};
	if ( !support.checkOn ) {
		jQuery.valHooks[ this ].get = function( elem ) {
			return elem.getAttribute( "value" ) === null ? "on" : elem.value;
		};
	}
} );




// Return jQuery for attributes-only inclusion
var location = window.location;

var nonce = { guid: Date.now() };

var rquery = ( /\?/ );



// Cross-browser xml parsing
jQuery.parseXML = function( data ) {
	var xml, parserErrorElem;
	if ( !data || typeof data !== "string" ) {
		return null;
	}

	// Support: IE 9 - 11 only
	// IE throws on parseFromString with invalid input.
	try {
		xml = ( new window.DOMParser() ).parseFromString( data, "text/xml" );
	} catch ( e ) {}

	parserErrorElem = xml && xml.getElementsByTagName( "parsererror" )[ 0 ];
	if ( !xml || parserErrorElem ) {
		jQuery.error( "Invalid XML: " + (
			parserErrorElem ?
				jQuery.map( parserErrorElem.childNodes, function( el ) {
					return el.textContent;
				} ).join( "\n" ) :
				data
		) );
	}
	return xml;
};


var rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
	stopPropagationCallback = function( e ) {
		e.stopPropagation();
	};

jQuery.extend( jQuery.event, {

	trigger: function( event, data, elem, onlyHandlers ) {

		var i, cur, tmp, bubbleType, ontype, handle, special, lastElement,
			eventPath = [ elem || document ],
			type = hasOwn.call( event, "type" ) ? event.type : event,
			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split( "." ) : [];

		cur = lastElement = tmp = elem = elem || document;

		// Don't do events on text and comment nodes
		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
			return;
		}

		// focus/blur morphs to focusin/out; ensure we're not firing them right now
		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
			return;
		}

		if ( type.indexOf( "." ) > -1 ) {

			// Namespaced trigger; create a regexp to match event type in handle()
			namespaces = type.split( "." );
			type = namespaces.shift();
			namespaces.sort();
		}
		ontype = type.indexOf( ":" ) < 0 && "on" + type;

		// Caller can pass in a jQuery.Event object, Object, or just an event type string
		event = event[ jQuery.expando ] ?
			event :
			new jQuery.Event( type, typeof event === "object" && event );

		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
		event.isTrigger = onlyHandlers ? 2 : 3;
		event.namespace = namespaces.join( "." );
		event.rnamespace = event.namespace ?
			new RegExp( "(^|\\.)" + namespaces.join( "\\.(?:.*\\.|)" ) + "(\\.|$)" ) :
			null;

		// Clean up the event in case it is being reused
		event.result = undefined;
		if ( !event.target ) {
			event.target = elem;
		}

		// Clone any incoming data and prepend the event, creating the handler arg list
		data = data == null ?
			[ event ] :
			jQuery.makeArray( data, [ event ] );

		// Allow special events to draw outside the lines
		special = jQuery.event.special[ type ] || {};
		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
			return;
		}

		// Determine event propagation path in advance, per W3C events spec (trac-9951)
		// Bubble up to document, then to window; watch for a global ownerDocument var (trac-9724)
		if ( !onlyHandlers && !special.noBubble && !isWindow( elem ) ) {

			bubbleType = special.delegateType || type;
			if ( !rfocusMorph.test( bubbleType + type ) ) {
				cur = cur.parentNode;
			}
			for ( ; cur; cur = cur.parentNode ) {
				eventPath.push( cur );
				tmp = cur;
			}

			// Only add window if we got to document (e.g., not plain obj or detached DOM)
			if ( tmp === ( elem.ownerDocument || document ) ) {
				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
			}
		}

		// Fire handlers on the event path
		i = 0;
		while ( ( cur = eventPath[ i++ ] ) && !event.isPropagationStopped() ) {
			lastElement = cur;
			event.type = i > 1 ?
				bubbleType :
				special.bindType || type;

			// jQuery handler
			handle = ( dataPriv.get( cur, "events" ) || Object.create( null ) )[ event.type ] &&
				dataPriv.get( cur, "handle" );
			if ( handle ) {
				handle.apply( cur, data );
			}

			// Native handler
			handle = ontype && cur[ ontype ];
			if ( handle && handle.apply && acceptData( cur ) ) {
				event.result = handle.apply( cur, data );
				if ( event.result === false ) {
					event.preventDefault();
				}
			}
		}
		event.type = type;

		// If nobody prevented the default action, do it now
		if ( !onlyHandlers && !event.isDefaultPrevented() ) {

			if ( ( !special._default ||
				special._default.apply( eventPath.pop(), data ) === false ) &&
				acceptData( elem ) ) {

				// Call a native DOM method on the target with the same name as the event.
				// Don't do default actions on window, that's where global variables be (trac-6170)
				if ( ontype && isFunction( elem[ type ] ) && !isWindow( elem ) ) {

					// Don't re-trigger an onFOO event when we call its FOO() method
					tmp = elem[ ontype ];

					if ( tmp ) {
						elem[ ontype ] = null;
					}

					// Prevent re-triggering of the same event, since we already bubbled it above
					jQuery.event.triggered = type;

					if ( event.isPropagationStopped() ) {
						lastElement.addEventListener( type, stopPropagationCallback );
					}

					elem[ type ]();

					if ( event.isPropagationStopped() ) {
						lastElement.removeEventListener( type, stopPropagationCallback );
					}

					jQuery.event.triggered = undefined;

					if ( tmp ) {
						elem[ ontype ] = tmp;
					}
				}
			}
		}

		return event.result;
	},

	// Piggyback on a donor event to simulate a different one
	// Used only for `focus(in | out)` events
	simulate: function( type, elem, event ) {
		var e = jQuery.extend(
			new jQuery.Event(),
			event,
			{
				type: type,
				isSimulated: true
			}
		);

		jQuery.event.trigger( e, null, elem );
	}

} );

jQuery.fn.extend( {

	trigger: function( type, data ) {
		return this.each( function() {
			jQuery.event.trigger( type, data, this );
		} );
	},
	triggerHandler: function( type, data ) {
		var elem = this[ 0 ];
		if ( elem ) {
			return jQuery.event.trigger( type, data, elem, true );
		}
	}
} );


var
	rbracket = /\[\]$/,
	rCRLF = /\r?\n/g,
	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
	rsubmittable = /^(?:input|select|textarea|keygen)/i;

function buildParams( prefix, obj, traditional, add ) {
	var name;

	if ( Array.isArray( obj ) ) {

		// Serialize array item.
		jQuery.each( obj, function( i, v ) {
			if ( traditional || rbracket.test( prefix ) ) {

				// Treat each array item as a scalar.
				add( prefix, v );

			} else {

				// Item is non-scalar (array or object), encode its numeric index.
				buildParams(
					prefix + "[" + ( typeof v === "object" && v != null ? i : "" ) + "]",
					v,
					traditional,
					add
				);
			}
		} );

	} else if ( !traditional && toType( obj ) === "object" ) {

		// Serialize object item.
		for ( name in obj ) {
			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
		}

	} else {

		// Serialize scalar item.
		add( prefix, obj );
	}
}

// Serialize an array of form elements or a set of
// key/values into a query string
jQuery.param = function( a, traditional ) {
	var prefix,
		s = [],
		add = function( key, valueOrFunction ) {

			// If value is a function, invoke it and use its return value
			var value = isFunction( valueOrFunction ) ?
				valueOrFunction() :
				valueOrFunction;

			s[ s.length ] = encodeURIComponent( key ) + "=" +
				encodeURIComponent( value == null ? "" : value );
		};

	if ( a == null ) {
		return "";
	}

	// If an array was passed in, assume that it is an array of form elements.
	if ( Array.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {

		// Serialize the form elements
		jQuery.each( a, function() {
			add( this.name, this.value );
		} );

	} else {

		// If traditional, encode the "old" way (the way 1.3.2 or older
		// did it), otherwise encode params recursively.
		for ( prefix in a ) {
			buildParams( prefix, a[ prefix ], traditional, add );
		}
	}

	// Return the resulting serialization
	return s.join( "&" );
};

jQuery.fn.extend( {
	serialize: function() {
		return jQuery.param( this.serializeArray() );
	},
	serializeArray: function() {
		return this.map( function() {

			// Can add propHook for "elements" to filter or add form elements
			var elements = jQuery.prop( this, "elements" );
			return elements ? jQuery.makeArray( elements ) : this;
		} ).filter( function() {
			var type = this.type;

			// Use .is( ":disabled" ) so that fieldset[disabled] works
			return this.name && !jQuery( this ).is( ":disabled" ) &&
				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
				( this.checked || !rcheckableType.test( type ) );
		} ).map( function( _i, elem ) {
			var val = jQuery( this ).val();

			if ( val == null ) {
				return null;
			}

			if ( Array.isArray( val ) ) {
				return jQuery.map( val, function( val ) {
					return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
				} );
			}

			return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
		} ).get();
	}
} );


var
	r20 = /%20/g,
	rhash = /#.*$/,
	rantiCache = /([?&])_=[^&]*/,
	rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,

	// trac-7653, trac-8125, trac-8152: local protocol detection
	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
	rnoContent = /^(?:GET|HEAD)$/,
	rprotocol = /^\/\//,

	/* Prefilters
	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
	 * 2) These are called:
	 *    - BEFORE asking for a transport
	 *    - AFTER param serialization (s.data is a string if s.processData is true)
	 * 3) key is the dataType
	 * 4) the catchall symbol "*" can be used
	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
	 */
	prefilters = {},

	/* Transports bindings
	 * 1) key is the dataType
	 * 2) the catchall symbol "*" can be used
	 * 3) selection will start with transport dataType and THEN go to "*" if needed
	 */
	transports = {},

	// Avoid comment-prolog char sequence (trac-10098); must appease lint and evade compression
	allTypes = "*/".concat( "*" ),

	// Anchor tag for parsing the document origin
	originAnchor = document.createElement( "a" );

originAnchor.href = location.href;

// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
function addToPrefiltersOrTransports( structure ) {

	// dataTypeExpression is optional and defaults to "*"
	return function( dataTypeExpression, func ) {

		if ( typeof dataTypeExpression !== "string" ) {
			func = dataTypeExpression;
			dataTypeExpression = "*";
		}

		var dataType,
			i = 0,
			dataTypes = dataTypeExpression.toLowerCase().match( rnothtmlwhite ) || [];

		if ( isFunction( func ) ) {

			// For each dataType in the dataTypeExpression
			while ( ( dataType = dataTypes[ i++ ] ) ) {

				// Prepend if requested
				if ( dataType[ 0 ] === "+" ) {
					dataType = dataType.slice( 1 ) || "*";
					( structure[ dataType ] = structure[ dataType ] || [] ).unshift( func );

				// Otherwise append
				} else {
					( structure[ dataType ] = structure[ dataType ] || [] ).push( func );
				}
			}
		}
	};
}

// Base inspection function for prefilters and transports
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {

	var inspected = {},
		seekingTransport = ( structure === transports );

	function inspect( dataType ) {
		var selected;
		inspected[ dataType ] = true;
		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
			if ( typeof dataTypeOrTransport === "string" &&
				!seekingTransport && !inspected[ dataTypeOrTransport ] ) {

				options.dataTypes.unshift( dataTypeOrTransport );
				inspect( dataTypeOrTransport );
				return false;
			} else if ( seekingTransport ) {
				return !( selected = dataTypeOrTransport );
			}
		} );
		return selected;
	}

	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
}

// A special extend for ajax options
// that takes "flat" options (not to be deep extended)
// Fixes trac-9887
function ajaxExtend( target, src ) {
	var key, deep,
		flatOptions = jQuery.ajaxSettings.flatOptions || {};

	for ( key in src ) {
		if ( src[ key ] !== undefined ) {
			( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
		}
	}
	if ( deep ) {
		jQuery.extend( true, target, deep );
	}

	return target;
}

/* Handles responses to an ajax request:
 * - finds the right dataType (mediates between content-type and expected dataType)
 * - returns the corresponding response
 */
function ajaxHandleResponses( s, jqXHR, responses ) {

	var ct, type, finalDataType, firstDataType,
		contents = s.contents,
		dataTypes = s.dataTypes;

	// Remove auto dataType and get content-type in the process
	while ( dataTypes[ 0 ] === "*" ) {
		dataTypes.shift();
		if ( ct === undefined ) {
			ct = s.mimeType || jqXHR.getResponseHeader( "Content-Type" );
		}
	}

	// Check if we're dealing with a known content-type
	if ( ct ) {
		for ( type in contents ) {
			if ( contents[ type ] && contents[ type ].test( ct ) ) {
				dataTypes.unshift( type );
				break;
			}
		}
	}

	// Check to see if we have a response for the expected dataType
	if ( dataTypes[ 0 ] in responses ) {
		finalDataType = dataTypes[ 0 ];
	} else {

		// Try convertible dataTypes
		for ( type in responses ) {
			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[ 0 ] ] ) {
				finalDataType = type;
				break;
			}
			if ( !firstDataType ) {
				firstDataType = type;
			}
		}

		// Or just use first one
		finalDataType = finalDataType || firstDataType;
	}

	// If we found a dataType
	// We add the dataType to the list if needed
	// and return the corresponding response
	if ( finalDataType ) {
		if ( finalDataType !== dataTypes[ 0 ] ) {
			dataTypes.unshift( finalDataType );
		}
		return responses[ finalDataType ];
	}
}

/* Chain conversions given the request and the original response
 * Also sets the responseXXX fields on the jqXHR instance
 */
function ajaxConvert( s, response, jqXHR, isSuccess ) {
	var conv2, current, conv, tmp, prev,
		converters = {},

		// Work with a copy of dataTypes in case we need to modify it for conversion
		dataTypes = s.dataTypes.slice();

	// Create converters map with lowercased keys
	if ( dataTypes[ 1 ] ) {
		for ( conv in s.converters ) {
			converters[ conv.toLowerCase() ] = s.converters[ conv ];
		}
	}

	current = dataTypes.shift();

	// Convert to each sequential dataType
	while ( current ) {

		if ( s.responseFields[ current ] ) {
			jqXHR[ s.responseFields[ current ] ] = response;
		}

		// Apply the dataFilter if provided
		if ( !prev && isSuccess && s.dataFilter ) {
			response = s.dataFilter( response, s.dataType );
		}

		prev = current;
		current = dataTypes.shift();

		if ( current ) {

			// There's only work to do if current dataType is non-auto
			if ( current === "*" ) {

				current = prev;

			// Convert response if prev dataType is non-auto and differs from current
			} else if ( prev !== "*" && prev !== current ) {

				// Seek a direct converter
				conv = converters[ prev + " " + current ] || converters[ "* " + current ];

				// If none found, seek a pair
				if ( !conv ) {
					for ( conv2 in converters ) {

						// If conv2 outputs current
						tmp = conv2.split( " " );
						if ( tmp[ 1 ] === current ) {

							// If prev can be converted to accepted input
							conv = converters[ prev + " " + tmp[ 0 ] ] ||
								converters[ "* " + tmp[ 0 ] ];
							if ( conv ) {

								// Condense equivalence converters
								if ( conv === true ) {
									conv = converters[ conv2 ];

								// Otherwise, insert the intermediate dataType
								} else if ( converters[ conv2 ] !== true ) {
									current = tmp[ 0 ];
									dataTypes.unshift( tmp[ 1 ] );
								}
								break;
							}
						}
					}
				}

				// Apply converter (if not an equivalence)
				if ( conv !== true ) {

					// Unless errors are allowed to bubble, catch and return them
					if ( conv && s.throws ) {
						response = conv( response );
					} else {
						try {
							response = conv( response );
						} catch ( e ) {
							return {
								state: "parsererror",
								error: conv ? e : "No conversion from " + prev + " to " + current
							};
						}
					}
				}
			}
		}
	}

	return { state: "success", data: response };
}

jQuery.extend( {

	// Counter for holding the number of active queries
	active: 0,

	// Last-Modified header cache for next request
	lastModified: {},
	etag: {},

	ajaxSettings: {
		url: location.href,
		type: "GET",
		isLocal: rlocalProtocol.test( location.protocol ),
		global: true,
		processData: true,
		async: true,
		contentType: "application/x-www-form-urlencoded; charset=UTF-8",

		/*
		timeout: 0,
		data: null,
		dataType: null,
		username: null,
		password: null,
		cache: null,
		throws: false,
		traditional: false,
		headers: {},
		*/

		accepts: {
			"*": allTypes,
			text: "text/plain",
			html: "text/html",
			xml: "application/xml, text/xml",
			json: "application/json, text/javascript"
		},

		contents: {
			xml: /\bxml\b/,
			html: /\bhtml/,
			json: /\bjson\b/
		},

		responseFields: {
			xml: "responseXML",
			text: "responseText",
			json: "responseJSON"
		},

		// Data converters
		// Keys separate source (or catchall "*") and destination types with a single space
		converters: {

			// Convert anything to text
			"* text": String,

			// Text to html (true = no transformation)
			"text html": true,

			// Evaluate text as a json expression
			"text json": JSON.parse,

			// Parse text as xml
			"text xml": jQuery.parseXML
		},

		// For options that shouldn't be deep extended:
		// you can add your own custom options here if
		// and when you create one that shouldn't be
		// deep extended (see ajaxExtend)
		flatOptions: {
			url: true,
			context: true
		}
	},

	// Creates a full fledged settings object into target
	// with both ajaxSettings and settings fields.
	// If target is omitted, writes into ajaxSettings.
	ajaxSetup: function( target, settings ) {
		return settings ?

			// Building a settings object
			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :

			// Extending ajaxSettings
			ajaxExtend( jQuery.ajaxSettings, target );
	},

	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
	ajaxTransport: addToPrefiltersOrTransports( transports ),

	// Main method
	ajax: function( url, options ) {

		// If url is an object, simulate pre-1.5 signature
		if ( typeof url === "object" ) {
			options = url;
			url = undefined;
		}

		// Force options to be an object
		options = options || {};

		var transport,

			// URL without anti-cache param
			cacheURL,

			// Response headers
			responseHeadersString,
			responseHeaders,

			// timeout handle
			timeoutTimer,

			// Url cleanup var
			urlAnchor,

			// Request state (becomes false upon send and true upon completion)
			completed,

			// To know if global events are to be dispatched
			fireGlobals,

			// Loop variable
			i,

			// uncached part of the url
			uncached,

			// Create the final options object
			s = jQuery.ajaxSetup( {}, options ),

			// Callbacks context
			callbackContext = s.context || s,

			// Context for global events is callbackContext if it is a DOM node or jQuery collection
			globalEventContext = s.context &&
				( callbackContext.nodeType || callbackContext.jquery ) ?
				jQuery( callbackContext ) :
				jQuery.event,

			// Deferreds
			deferred = jQuery.Deferred(),
			completeDeferred = jQuery.Callbacks( "once memory" ),

			// Status-dependent callbacks
			statusCode = s.statusCode || {},

			// Headers (they are sent all at once)
			requestHeaders = {},
			requestHeadersNames = {},

			// Default abort message
			strAbort = "canceled",

			// Fake xhr
			jqXHR = {
				readyState: 0,

				// Builds headers hashtable if needed
				getResponseHeader: function( key ) {
					var match;
					if ( completed ) {
						if ( !responseHeaders ) {
							responseHeaders = {};
							while ( ( match = rheaders.exec( responseHeadersString ) ) ) {
								responseHeaders[ match[ 1 ].toLowerCase() + " " ] =
									( responseHeaders[ match[ 1 ].toLowerCase() + " " ] || [] )
										.concat( match[ 2 ] );
							}
						}
						match = responseHeaders[ key.toLowerCase() + " " ];
					}
					return match == null ? null : match.join( ", " );
				},

				// Raw string
				getAllResponseHeaders: function() {
					return completed ? responseHeadersString : null;
				},

				// Caches the header
				setRequestHeader: function( name, value ) {
					if ( completed == null ) {
						name = requestHeadersNames[ name.toLowerCase() ] =
							requestHeadersNames[ name.toLowerCase() ] || name;
						requestHeaders[ name ] = value;
					}
					return this;
				},

				// Overrides response content-type header
				overrideMimeType: function( type ) {
					if ( completed == null ) {
						s.mimeType = type;
					}
					return this;
				},

				// Status-dependent callbacks
				statusCode: function( map ) {
					var code;
					if ( map ) {
						if ( completed ) {

							// Execute the appropriate callbacks
							jqXHR.always( map[ jqXHR.status ] );
						} else {

							// Lazy-add the new callbacks in a way that preserves old ones
							for ( code in map ) {
								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
							}
						}
					}
					return this;
				},

				// Cancel the request
				abort: function( statusText ) {
					var finalText = statusText || strAbort;
					if ( transport ) {
						transport.abort( finalText );
					}
					done( 0, finalText );
					return this;
				}
			};

		// Attach deferreds
		deferred.promise( jqXHR );

		// Add protocol if not provided (prefilters might expect it)
		// Handle falsy url in the settings object (trac-10093: consistency with old signature)
		// We also use the url parameter if available
		s.url = ( ( url || s.url || location.href ) + "" )
			.replace( rprotocol, location.protocol + "//" );

		// Alias method option to type as per ticket trac-12004
		s.type = options.method || options.type || s.method || s.type;

		// Extract dataTypes list
		s.dataTypes = ( s.dataType || "*" ).toLowerCase().match( rnothtmlwhite ) || [ "" ];

		// A cross-domain request is in order when the origin doesn't match the current origin.
		if ( s.crossDomain == null ) {
			urlAnchor = document.createElement( "a" );

			// Support: IE <=8 - 11, Edge 12 - 15
			// IE throws exception on accessing the href property if url is malformed,
			// e.g. http://example.com:80x/
			try {
				urlAnchor.href = s.url;

				// Support: IE <=8 - 11 only
				// Anchor's host property isn't correctly set when s.url is relative
				urlAnchor.href = urlAnchor.href;
				s.crossDomain = originAnchor.protocol + "//" + originAnchor.host !==
					urlAnchor.protocol + "//" + urlAnchor.host;
			} catch ( e ) {

				// If there is an error parsing the URL, assume it is crossDomain,
				// it can be rejected by the transport if it is invalid
				s.crossDomain = true;
			}
		}

		// Convert data if not already a string
		if ( s.data && s.processData && typeof s.data !== "string" ) {
			s.data = jQuery.param( s.data, s.traditional );
		}

		// Apply prefilters
		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );

		// If request was aborted inside a prefilter, stop there
		if ( completed ) {
			return jqXHR;
		}

		// We can fire global events as of now if asked to
		// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (trac-15118)
		fireGlobals = jQuery.event && s.global;

		// Watch for a new set of requests
		if ( fireGlobals && jQuery.active++ === 0 ) {
			jQuery.event.trigger( "ajaxStart" );
		}

		// Uppercase the type
		s.type = s.type.toUpperCase();

		// Determine if request has content
		s.hasContent = !rnoContent.test( s.type );

		// Save the URL in case we're toying with the If-Modified-Since
		// and/or If-None-Match header later on
		// Remove hash to simplify url manipulation
		cacheURL = s.url.replace( rhash, "" );

		// More options handling for requests with no content
		if ( !s.hasContent ) {

			// Remember the hash so we can put it back
			uncached = s.url.slice( cacheURL.length );

			// If data is available and should be processed, append data to url
			if ( s.data && ( s.processData || typeof s.data === "string" ) ) {
				cacheURL += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data;

				// trac-9682: remove data so that it's not used in an eventual retry
				delete s.data;
			}

			// Add or update anti-cache param if needed
			if ( s.cache === false ) {
				cacheURL = cacheURL.replace( rantiCache, "$1" );
				uncached = ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ( nonce.guid++ ) +
					uncached;
			}

			// Put hash and anti-cache on the URL that will be requested (gh-1732)
			s.url = cacheURL + uncached;

		// Change '%20' to '+' if this is encoded form body content (gh-2658)
		} else if ( s.data && s.processData &&
			( s.contentType || "" ).indexOf( "application/x-www-form-urlencoded" ) === 0 ) {
			s.data = s.data.replace( r20, "+" );
		}

		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
		if ( s.ifModified ) {
			if ( jQuery.lastModified[ cacheURL ] ) {
				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
			}
			if ( jQuery.etag[ cacheURL ] ) {
				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
			}
		}

		// Set the correct header, if data is being sent
		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
			jqXHR.setRequestHeader( "Content-Type", s.contentType );
		}

		// Set the Accepts header for the server, depending on the dataType
		jqXHR.setRequestHeader(
			"Accept",
			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[ 0 ] ] ?
				s.accepts[ s.dataTypes[ 0 ] ] +
					( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
				s.accepts[ "*" ]
		);

		// Check for headers option
		for ( i in s.headers ) {
			jqXHR.setRequestHeader( i, s.headers[ i ] );
		}

		// Allow custom headers/mimetypes and early abort
		if ( s.beforeSend &&
			( s.beforeSend.call( callbackContext, jqXHR, s ) === false || completed ) ) {

			// Abort if not done already and return
			return jqXHR.abort();
		}

		// Aborting is no longer a cancellation
		strAbort = "abort";

		// Install callbacks on deferreds
		completeDeferred.add( s.complete );
		jqXHR.done( s.success );
		jqXHR.fail( s.error );

		// Get transport
		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );

		// If no transport, we auto-abort
		if ( !transport ) {
			done( -1, "No Transport" );
		} else {
			jqXHR.readyState = 1;

			// Send global event
			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
			}

			// If request was aborted inside ajaxSend, stop there
			if ( completed ) {
				return jqXHR;
			}

			// Timeout
			if ( s.async && s.timeout > 0 ) {
				timeoutTimer = window.setTimeout( function() {
					jqXHR.abort( "timeout" );
				}, s.timeout );
			}

			try {
				completed = false;
				transport.send( requestHeaders, done );
			} catch ( e ) {

				// Rethrow post-completion exceptions
				if ( completed ) {
					throw e;
				}

				// Propagate others as results
				done( -1, e );
			}
		}

		// Callback for when everything is done
		function done( status, nativeStatusText, responses, headers ) {
			var isSuccess, success, error, response, modified,
				statusText = nativeStatusText;

			// Ignore repeat invocations
			if ( completed ) {
				return;
			}

			completed = true;

			// Clear timeout if it exists
			if ( timeoutTimer ) {
				window.clearTimeout( timeoutTimer );
			}

			// Dereference transport for early garbage collection
			// (no matter how long the jqXHR object will be used)
			transport = undefined;

			// Cache response headers
			responseHeadersString = headers || "";

			// Set readyState
			jqXHR.readyState = status > 0 ? 4 : 0;

			// Determine if successful
			isSuccess = status >= 200 && status < 300 || status === 304;

			// Get response data
			if ( responses ) {
				response = ajaxHandleResponses( s, jqXHR, responses );
			}

			// Use a noop converter for missing script but not if jsonp
			if ( !isSuccess &&
				jQuery.inArray( "script", s.dataTypes ) > -1 &&
				jQuery.inArray( "json", s.dataTypes ) < 0 ) {
				s.converters[ "text script" ] = function() {};
			}

			// Convert no matter what (that way responseXXX fields are always set)
			response = ajaxConvert( s, response, jqXHR, isSuccess );

			// If successful, handle type chaining
			if ( isSuccess ) {

				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
				if ( s.ifModified ) {
					modified = jqXHR.getResponseHeader( "Last-Modified" );
					if ( modified ) {
						jQuery.lastModified[ cacheURL ] = modified;
					}
					modified = jqXHR.getResponseHeader( "etag" );
					if ( modified ) {
						jQuery.etag[ cacheURL ] = modified;
					}
				}

				// if no content
				if ( status === 204 || s.type === "HEAD" ) {
					statusText = "nocontent";

				// if not modified
				} else if ( status === 304 ) {
					statusText = "notmodified";

				// If we have data, let's convert it
				} else {
					statusText = response.state;
					success = response.data;
					error = response.error;
					isSuccess = !error;
				}
			} else {

				// Extract error from statusText and normalize for non-aborts
				error = statusText;
				if ( status || !statusText ) {
					statusText = "error";
					if ( status < 0 ) {
						status = 0;
					}
				}
			}

			// Set data for the fake xhr object
			jqXHR.status = status;
			jqXHR.statusText = ( nativeStatusText || statusText ) + "";

			// Success/Error
			if ( isSuccess ) {
				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
			} else {
				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
			}

			// Status-dependent callbacks
			jqXHR.statusCode( statusCode );
			statusCode = undefined;

			if ( fireGlobals ) {
				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
					[ jqXHR, s, isSuccess ? success : error ] );
			}

			// Complete
			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );

			if ( fireGlobals ) {
				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );

				// Handle the global AJAX counter
				if ( !( --jQuery.active ) ) {
					jQuery.event.trigger( "ajaxStop" );
				}
			}
		}

		return jqXHR;
	},

	getJSON: function( url, data, callback ) {
		return jQuery.get( url, data, callback, "json" );
	},

	getScript: function( url, callback ) {
		return jQuery.get( url, undefined, callback, "script" );
	}
} );

jQuery.each( [ "get", "post" ], function( _i, method ) {
	jQuery[ method ] = function( url, data, callback, type ) {

		// Shift arguments if data argument was omitted
		if ( isFunction( data ) ) {
			type = type || callback;
			callback = data;
			data = undefined;
		}

		// The url can be an options object (which then must have .url)
		return jQuery.ajax( jQuery.extend( {
			url: url,
			type: method,
			dataType: type,
			data: data,
			success: callback
		}, jQuery.isPlainObject( url ) && url ) );
	};
} );

jQuery.ajaxPrefilter( function( s ) {
	var i;
	for ( i in s.headers ) {
		if ( i.toLowerCase() === "content-type" ) {
			s.contentType = s.headers[ i ] || "";
		}
	}
} );


jQuery._evalUrl = function( url, options, doc ) {
	return jQuery.ajax( {
		url: url,

		// Make this explicit, since user can override this through ajaxSetup (trac-11264)
		type: "GET",
		dataType: "script",
		cache: true,
		async: false,
		global: false,

		// Only evaluate the response if it is successful (gh-4126)
		// dataFilter is not invoked for failure responses, so using it instead
		// of the default converter is kludgy but it works.
		converters: {
			"text script": function() {}
		},
		dataFilter: function( response ) {
			jQuery.globalEval( response, options, doc );
		}
	} );
};


jQuery.fn.extend( {
	wrapAll: function( html ) {
		var wrap;

		if ( this[ 0 ] ) {
			if ( isFunction( html ) ) {
				html = html.call( this[ 0 ] );
			}

			// The elements to wrap the target around
			wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );

			if ( this[ 0 ].parentNode ) {
				wrap.insertBefore( this[ 0 ] );
			}

			wrap.map( function() {
				var elem = this;

				while ( elem.firstElementChild ) {
					elem = elem.firstElementChild;
				}

				return elem;
			} ).append( this );
		}

		return this;
	},

	wrapInner: function( html ) {
		if ( isFunction( html ) ) {
			return this.each( function( i ) {
				jQuery( this ).wrapInner( html.call( this, i ) );
			} );
		}

		return this.each( function() {
			var self = jQuery( this ),
				contents = self.contents();

			if ( contents.length ) {
				contents.wrapAll( html );

			} else {
				self.append( html );
			}
		} );
	},

	wrap: function( html ) {
		var htmlIsFunction = isFunction( html );

		return this.each( function( i ) {
			jQuery( this ).wrapAll( htmlIsFunction ? html.call( this, i ) : html );
		} );
	},

	unwrap: function( selector ) {
		this.parent( selector ).not( "body" ).each( function() {
			jQuery( this ).replaceWith( this.childNodes );
		} );
		return this;
	}
} );


jQuery.expr.pseudos.hidden = function( elem ) {
	return !jQuery.expr.pseudos.visible( elem );
};
jQuery.expr.pseudos.visible = function( elem ) {
	return !!( elem.offsetWidth || elem.offsetHeight || elem.getClientRects().length );
};




jQuery.ajaxSettings.xhr = function() {
	try {
		return new window.XMLHttpRequest();
	} catch ( e ) {}
};

var xhrSuccessStatus = {

		// File protocol always yields status code 0, assume 200
		0: 200,

		// Support: IE <=9 only
		// trac-1450: sometimes IE returns 1223 when it should be 204
		1223: 204
	},
	xhrSupported = jQuery.ajaxSettings.xhr();

support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
support.ajax = xhrSupported = !!xhrSupported;

jQuery.ajaxTransport( function( options ) {
	var callback, errorCallback;

	// Cross domain only allowed if supported through XMLHttpRequest
	if ( support.cors || xhrSupported && !options.crossDomain ) {
		return {
			send: function( headers, complete ) {
				var i,
					xhr = options.xhr();

				xhr.open(
					options.type,
					options.url,
					options.async,
					options.username,
					options.password
				);

				// Apply custom fields if provided
				if ( options.xhrFields ) {
					for ( i in options.xhrFields ) {
						xhr[ i ] = options.xhrFields[ i ];
					}
				}

				// Override mime type if needed
				if ( options.mimeType && xhr.overrideMimeType ) {
					xhr.overrideMimeType( options.mimeType );
				}

				// X-Requested-With header
				// For cross-domain requests, seeing as conditions for a preflight are
				// akin to a jigsaw puzzle, we simply never set it to be sure.
				// (it can always be set on a per-request basis or even using ajaxSetup)
				// For same-domain requests, won't change header if already provided.
				if ( !options.crossDomain && !headers[ "X-Requested-With" ] ) {
					headers[ "X-Requested-With" ] = "XMLHttpRequest";
				}

				// Set headers
				for ( i in headers ) {
					xhr.setRequestHeader( i, headers[ i ] );
				}

				// Callback
				callback = function( type ) {
					return function() {
						if ( callback ) {
							callback = errorCallback = xhr.onload =
								xhr.onerror = xhr.onabort = xhr.ontimeout =
									xhr.onreadystatechange = null;

							if ( type === "abort" ) {
								xhr.abort();
							} else if ( type === "error" ) {

								// Support: IE <=9 only
								// On a manual native abort, IE9 throws
								// errors on any property access that is not readyState
								if ( typeof xhr.status !== "number" ) {
									complete( 0, "error" );
								} else {
									complete(

										// File: protocol always yields status 0; see trac-8605, trac-14207
										xhr.status,
										xhr.statusText
									);
								}
							} else {
								complete(
									xhrSuccessStatus[ xhr.status ] || xhr.status,
									xhr.statusText,

									// Support: IE <=9 only
									// IE9 has no XHR2 but throws on binary (trac-11426)
									// For XHR2 non-text, let the caller handle it (gh-2498)
									( xhr.responseType || "text" ) !== "text"  ||
									typeof xhr.responseText !== "string" ?
										{ binary: xhr.response } :
										{ text: xhr.responseText },
									xhr.getAllResponseHeaders()
								);
							}
						}
					};
				};

				// Listen to events
				xhr.onload = callback();
				errorCallback = xhr.onerror = xhr.ontimeout = callback( "error" );

				// Support: IE 9 only
				// Use onreadystatechange to replace onabort
				// to handle uncaught aborts
				if ( xhr.onabort !== undefined ) {
					xhr.onabort = errorCallback;
				} else {
					xhr.onreadystatechange = function() {

						// Check readyState before timeout as it changes
						if ( xhr.readyState === 4 ) {

							// Allow onerror to be called first,
							// but that will not handle a native abort
							// Also, save errorCallback to a variable
							// as xhr.onerror cannot be accessed
							window.setTimeout( function() {
								if ( callback ) {
									errorCallback();
								}
							} );
						}
					};
				}

				// Create the abort callback
				callback = callback( "abort" );

				try {

					// Do send the request (this may raise an exception)
					xhr.send( options.hasContent && options.data || null );
				} catch ( e ) {

					// trac-14683: Only rethrow if this hasn't been notified as an error yet
					if ( callback ) {
						throw e;
					}
				}
			},

			abort: function() {
				if ( callback ) {
					callback();
				}
			}
		};
	}
} );




// Prevent auto-execution of scripts when no explicit dataType was provided (See gh-2432)
jQuery.ajaxPrefilter( function( s ) {
	if ( s.crossDomain ) {
		s.contents.script = false;
	}
} );

// Install script dataType
jQuery.ajaxSetup( {
	accepts: {
		script: "text/javascript, application/javascript, " +
			"application/ecmascript, application/x-ecmascript"
	},
	contents: {
		script: /\b(?:java|ecma)script\b/
	},
	converters: {
		"text script": function( text ) {
			jQuery.globalEval( text );
			return text;
		}
	}
} );

// Handle cache's special case and crossDomain
jQuery.ajaxPrefilter( "script", function( s ) {
	if ( s.cache === undefined ) {
		s.cache = false;
	}
	if ( s.crossDomain ) {
		s.type = "GET";
	}
} );

// Bind script tag hack transport
jQuery.ajaxTransport( "script", function( s ) {

	// This transport only deals with cross domain or forced-by-attrs requests
	if ( s.crossDomain || s.scriptAttrs ) {
		var script, callback;
		return {
			send: function( _, complete ) {
				script = jQuery( "<script>" )
					.attr( s.scriptAttrs || {} )
					.prop( { charset: s.scriptCharset, src: s.url } )
					.on( "load error", callback = function( evt ) {
						script.remove();
						callback = null;
						if ( evt ) {
							complete( evt.type === "error" ? 404 : 200, evt.type );
						}
					} );

				// Use native DOM manipulation to avoid our domManip AJAX trickery
				document.head.appendChild( script[ 0 ] );
			},
			abort: function() {
				if ( callback ) {
					callback();
				}
			}
		};
	}
} );




var oldCallbacks = [],
	rjsonp = /(=)\?(?=&|$)|\?\?/;

// Default jsonp settings
jQuery.ajaxSetup( {
	jsonp: "callback",
	jsonpCallback: function() {
		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce.guid++ ) );
		this[ callback ] = true;
		return callback;
	}
} );

// Detect, normalize options and install callbacks for jsonp requests
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {

	var callbackName, overwritten, responseContainer,
		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
			"url" :
			typeof s.data === "string" &&
				( s.contentType || "" )
					.indexOf( "application/x-www-form-urlencoded" ) === 0 &&
				rjsonp.test( s.data ) && "data"
		);

	// Handle iff the expected data type is "jsonp" or we have a parameter to set
	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {

		// Get callback name, remembering preexisting value associated with it
		callbackName = s.jsonpCallback = isFunction( s.jsonpCallback ) ?
			s.jsonpCallback() :
			s.jsonpCallback;

		// Insert callback into url or form data
		if ( jsonProp ) {
			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
		} else if ( s.jsonp !== false ) {
			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
		}

		// Use data converter to retrieve json after script execution
		s.converters[ "script json" ] = function() {
			if ( !responseContainer ) {
				jQuery.error( callbackName + " was not called" );
			}
			return responseContainer[ 0 ];
		};

		// Force json dataType
		s.dataTypes[ 0 ] = "json";

		// Install callback
		overwritten = window[ callbackName ];
		window[ callbackName ] = function() {
			responseContainer = arguments;
		};

		// Clean-up function (fires after converters)
		jqXHR.always( function() {

			// If previous value didn't exist - remove it
			if ( overwritten === undefined ) {
				jQuery( window ).removeProp( callbackName );

			// Otherwise restore preexisting value
			} else {
				window[ callbackName ] = overwritten;
			}

			// Save back as free
			if ( s[ callbackName ] ) {

				// Make sure that re-using the options doesn't screw things around
				s.jsonpCallback = originalSettings.jsonpCallback;

				// Save the callback name for future use
				oldCallbacks.push( callbackName );
			}

			// Call if it was a function and we have a response
			if ( responseContainer && isFunction( overwritten ) ) {
				overwritten( responseContainer[ 0 ] );
			}

			responseContainer = overwritten = undefined;
		} );

		// Delegate to script
		return "script";
	}
} );




// Support: Safari 8 only
// In Safari 8 documents created via document.implementation.createHTMLDocument
// collapse sibling forms: the second one becomes a child of the first one.
// Because of that, this security measure has to be disabled in Safari 8.
// https://bugs.webkit.org/show_bug.cgi?id=137337
support.createHTMLDocument = ( function() {
	var body = document.implementation.createHTMLDocument( "" ).body;
	body.innerHTML = "<form></form><form></form>";
	return body.childNodes.length === 2;
} )();


// Argument "data" should be string of html
// context (optional): If specified, the fragment will be created in this context,
// defaults to document
// keepScripts (optional): If true, will include scripts passed in the html string
jQuery.parseHTML = function( data, context, keepScripts ) {
	if ( typeof data !== "string" ) {
		return [];
	}
	if ( typeof context === "boolean" ) {
		keepScripts = context;
		context = false;
	}

	var base, parsed, scripts;

	if ( !context ) {

		// Stop scripts or inline event handlers from being executed immediately
		// by using document.implementation
		if ( support.createHTMLDocument ) {
			context = document.implementation.createHTMLDocument( "" );

			// Set the base href for the created document
			// so any parsed elements with URLs
			// are based on the document's URL (gh-2965)
			base = context.createElement( "base" );
			base.href = document.location.href;
			context.head.appendChild( base );
		} else {
			context = document;
		}
	}

	parsed = rsingleTag.exec( data );
	scripts = !keepScripts && [];

	// Single tag
	if ( parsed ) {
		return [ context.createElement( parsed[ 1 ] ) ];
	}

	parsed = buildFragment( [ data ], context, scripts );

	if ( scripts && scripts.length ) {
		jQuery( scripts ).remove();
	}

	return jQuery.merge( [], parsed.childNodes );
};


/**
 * Load a url into a page
 */
jQuery.fn.load = function( url, params, callback ) {
	var selector, type, response,
		self = this,
		off = url.indexOf( " " );

	if ( off > -1 ) {
		selector = stripAndCollapse( url.slice( off ) );
		url = url.slice( 0, off );
	}

	// If it's a function
	if ( isFunction( params ) ) {

		// We assume that it's the callback
		callback = params;
		params = undefined;

	// Otherwise, build a param string
	} else if ( params && typeof params === "object" ) {
		type = "POST";
	}

	// If we have elements to modify, make the request
	if ( self.length > 0 ) {
		jQuery.ajax( {
			url: url,

			// If "type" variable is undefined, then "GET" method will be used.
			// Make value of this field explicit since
			// user can override it through ajaxSetup method
			type: type || "GET",
			dataType: "html",
			data: params
		} ).done( function( responseText ) {

			// Save response for use in complete callback
			response = arguments;

			self.html( selector ?

				// If a selector was specified, locate the right elements in a dummy div
				// Exclude scripts to avoid IE 'Permission Denied' errors
				jQuery( "<div>" ).append( jQuery.parseHTML( responseText ) ).find( selector ) :

				// Otherwise use the full result
				responseText );

		// If the request succeeds, this function gets "data", "status", "jqXHR"
		// but they are ignored because response was set above.
		// If it fails, this function gets "jqXHR", "status", "error"
		} ).always( callback && function( jqXHR, status ) {
			self.each( function() {
				callback.apply( this, response || [ jqXHR.responseText, status, jqXHR ] );
			} );
		} );
	}

	return this;
};




jQuery.expr.pseudos.animated = function( elem ) {
	return jQuery.grep( jQuery.timers, function( fn ) {
		return elem === fn.elem;
	} ).length;
};




jQuery.offset = {
	setOffset: function( elem, options, i ) {
		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
			position = jQuery.css( elem, "position" ),
			curElem = jQuery( elem ),
			props = {};

		// Set position first, in-case top/left are set even on static elem
		if ( position === "static" ) {
			elem.style.position = "relative";
		}

		curOffset = curElem.offset();
		curCSSTop = jQuery.css( elem, "top" );
		curCSSLeft = jQuery.css( elem, "left" );
		calculatePosition = ( position === "absolute" || position === "fixed" ) &&
			( curCSSTop + curCSSLeft ).indexOf( "auto" ) > -1;

		// Need to be able to calculate position if either
		// top or left is auto and position is either absolute or fixed
		if ( calculatePosition ) {
			curPosition = curElem.position();
			curTop = curPosition.top;
			curLeft = curPosition.left;

		} else {
			curTop = parseFloat( curCSSTop ) || 0;
			curLeft = parseFloat( curCSSLeft ) || 0;
		}

		if ( isFunction( options ) ) {

			// Use jQuery.extend here to allow modification of coordinates argument (gh-1848)
			options = options.call( elem, i, jQuery.extend( {}, curOffset ) );
		}

		if ( options.top != null ) {
			props.top = ( options.top - curOffset.top ) + curTop;
		}
		if ( options.left != null ) {
			props.left = ( options.left - curOffset.left ) + curLeft;
		}

		if ( "using" in options ) {
			options.using.call( elem, props );

		} else {
			curElem.css( props );
		}
	}
};

jQuery.fn.extend( {

	// offset() relates an element's border box to the document origin
	offset: function( options ) {

		// Preserve chaining for setter
		if ( arguments.length ) {
			return options === undefined ?
				this :
				this.each( function( i ) {
					jQuery.offset.setOffset( this, options, i );
				} );
		}

		var rect, win,
			elem = this[ 0 ];

		if ( !elem ) {
			return;
		}

		// Return zeros for disconnected and hidden (display: none) elements (gh-2310)
		// Support: IE <=11 only
		// Running getBoundingClientRect on a
		// disconnected node in IE throws an error
		if ( !elem.getClientRects().length ) {
			return { top: 0, left: 0 };
		}

		// Get document-relative position by adding viewport scroll to viewport-relative gBCR
		rect = elem.getBoundingClientRect();
		win = elem.ownerDocument.defaultView;
		return {
			top: rect.top + win.pageYOffset,
			left: rect.left + win.pageXOffset
		};
	},

	// position() relates an element's margin box to its offset parent's padding box
	// This corresponds to the behavior of CSS absolute positioning
	position: function() {
		if ( !this[ 0 ] ) {
			return;
		}

		var offsetParent, offset, doc,
			elem = this[ 0 ],
			parentOffset = { top: 0, left: 0 };

		// position:fixed elements are offset from the viewport, which itself always has zero offset
		if ( jQuery.css( elem, "position" ) === "fixed" ) {

			// Assume position:fixed implies availability of getBoundingClientRect
			offset = elem.getBoundingClientRect();

		} else {
			offset = this.offset();

			// Account for the *real* offset parent, which can be the document or its root element
			// when a statically positioned element is identified
			doc = elem.ownerDocument;
			offsetParent = elem.offsetParent || doc.documentElement;
			while ( offsetParent &&
				( offsetParent === doc.body || offsetParent === doc.documentElement ) &&
				jQuery.css( offsetParent, "position" ) === "static" ) {

				offsetParent = offsetParent.parentNode;
			}
			if ( offsetParent && offsetParent !== elem && offsetParent.nodeType === 1 ) {

				// Incorporate borders into its offset, since they are outside its content origin
				parentOffset = jQuery( offsetParent ).offset();
				parentOffset.top += jQuery.css( offsetParent, "borderTopWidth", true );
				parentOffset.left += jQuery.css( offsetParent, "borderLeftWidth", true );
			}
		}

		// Subtract parent offsets and element margins
		return {
			top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
		};
	},

	// This method will return documentElement in the following cases:
	// 1) For the element inside the iframe without offsetParent, this method will return
	//    documentElement of the parent window
	// 2) For the hidden or detached element
	// 3) For body or html element, i.e. in case of the html node - it will return itself
	//
	// but those exceptions were never presented as a real life use-cases
	// and might be considered as more preferable results.
	//
	// This logic, however, is not guaranteed and can change at any point in the future
	offsetParent: function() {
		return this.map( function() {
			var offsetParent = this.offsetParent;

			while ( offsetParent && jQuery.css( offsetParent, "position" ) === "static" ) {
				offsetParent = offsetParent.offsetParent;
			}

			return offsetParent || documentElement;
		} );
	}
} );

// Create scrollLeft and scrollTop methods
jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
	var top = "pageYOffset" === prop;

	jQuery.fn[ method ] = function( val ) {
		return access( this, function( elem, method, val ) {

			// Coalesce documents and windows
			var win;
			if ( isWindow( elem ) ) {
				win = elem;
			} else if ( elem.nodeType === 9 ) {
				win = elem.defaultView;
			}

			if ( val === undefined ) {
				return win ? win[ prop ] : elem[ method ];
			}

			if ( win ) {
				win.scrollTo(
					!top ? val : win.pageXOffset,
					top ? val : win.pageYOffset
				);

			} else {
				elem[ method ] = val;
			}
		}, method, val, arguments.length );
	};
} );

// Support: Safari <=7 - 9.1, Chrome <=37 - 49
// Add the top/left cssHooks using jQuery.fn.position
// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
// Blink bug: https://bugs.chromium.org/p/chromium/issues/detail?id=589347
// getComputedStyle returns percent when specified for top/left/bottom/right;
// rather than make the css module depend on the offset module, just check for it here
jQuery.each( [ "top", "left" ], function( _i, prop ) {
	jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
		function( elem, computed ) {
			if ( computed ) {
				computed = curCSS( elem, prop );

				// If curCSS returns percentage, fallback to offset
				return rnumnonpx.test( computed ) ?
					jQuery( elem ).position()[ prop ] + "px" :
					computed;
			}
		}
	);
} );


// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
	jQuery.each( {
		padding: "inner" + name,
		content: type,
		"": "outer" + name
	}, function( defaultExtra, funcName ) {

		// Margin is only for outerHeight, outerWidth
		jQuery.fn[ funcName ] = function( margin, value ) {
			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );

			return access( this, function( elem, type, value ) {
				var doc;

				if ( isWindow( elem ) ) {

					// $( window ).outerWidth/Height return w/h including scrollbars (gh-1729)
					return funcName.indexOf( "outer" ) === 0 ?
						elem[ "inner" + name ] :
						elem.document.documentElement[ "client" + name ];
				}

				// Get document width or height
				if ( elem.nodeType === 9 ) {
					doc = elem.documentElement;

					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
					// whichever is greatest
					return Math.max(
						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
						elem.body[ "offset" + name ], doc[ "offset" + name ],
						doc[ "client" + name ]
					);
				}

				return value === undefined ?

					// Get width or height on the element, requesting but not forcing parseFloat
					jQuery.css( elem, type, extra ) :

					// Set width or height on the element
					jQuery.style( elem, type, value, extra );
			}, type, chainable ? margin : undefined, chainable );
		};
	} );
} );


jQuery.each( [
	"ajaxStart",
	"ajaxStop",
	"ajaxComplete",
	"ajaxError",
	"ajaxSuccess",
	"ajaxSend"
], function( _i, type ) {
	jQuery.fn[ type ] = function( fn ) {
		return this.on( type, fn );
	};
} );




jQuery.fn.extend( {

	bind: function( types, data, fn ) {
		return this.on( types, null, data, fn );
	},
	unbind: function( types, fn ) {
		return this.off( types, null, fn );
	},

	delegate: function( selector, types, data, fn ) {
		return this.on( types, selector, data, fn );
	},
	undelegate: function( selector, types, fn ) {

		// ( namespace ) or ( selector, types [, fn] )
		return arguments.length === 1 ?
			this.off( selector, "**" ) :
			this.off( types, selector || "**", fn );
	},

	hover: function( fnOver, fnOut ) {
		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
	}
} );

jQuery.each(
	( "blur focus focusin focusout resize scroll click dblclick " +
	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
	"change select submit keydown keypress keyup contextmenu" ).split( " " ),
	function( _i, name ) {

		// Handle event binding
		jQuery.fn[ name ] = function( data, fn ) {
			return arguments.length > 0 ?
				this.on( name, null, data, fn ) :
				this.trigger( name );
		};
	}
);




// Support: Android <=4.0 only
// Make sure we trim BOM and NBSP
// Require that the "whitespace run" starts from a non-whitespace
// to avoid O(N^2) behavior when the engine would try matching "\s+$" at each space position.
var rtrim = /^[\s\uFEFF\xA0]+|([^\s\uFEFF\xA0])[\s\uFEFF\xA0]+$/g;

// Bind a function to a context, optionally partially applying any
// arguments.
// jQuery.proxy is deprecated to promote standards (specifically Function#bind)
// However, it is not slated for removal any time soon
jQuery.proxy = function( fn, context ) {
	var tmp, args, proxy;

	if ( typeof context === "string" ) {
		tmp = fn[ context ];
		context = fn;
		fn = tmp;
	}

	// Quick check to determine if target is callable, in the spec
	// this throws a TypeError, but we will just return undefined.
	if ( !isFunction( fn ) ) {
		return undefined;
	}

	// Simulated bind
	args = slice.call( arguments, 2 );
	proxy = function() {
		return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
	};

	// Set the guid of unique handler to the same of original handler, so it can be removed
	proxy.guid = fn.guid = fn.guid || jQuery.guid++;

	return proxy;
};

jQuery.holdReady = function( hold ) {
	if ( hold ) {
		jQuery.readyWait++;
	} else {
		jQuery.ready( true );
	}
};
jQuery.isArray = Array.isArray;
jQuery.parseJSON = JSON.parse;
jQuery.nodeName = nodeName;
jQuery.isFunction = isFunction;
jQuery.isWindow = isWindow;
jQuery.camelCase = camelCase;
jQuery.type = toType;

jQuery.now = Date.now;

jQuery.isNumeric = function( obj ) {

	// As of jQuery 3.0, isNumeric is limited to
	// strings and numbers (primitives or objects)
	// that can be coerced to finite numbers (gh-2662)
	var type = jQuery.type( obj );
	return ( type === "number" || type === "string" ) &&

		// parseFloat NaNs numeric-cast false positives ("")
		// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
		// subtraction forces infinities to NaN
		!isNaN( obj - parseFloat( obj ) );
};

jQuery.trim = function( text ) {
	return text == null ?
		"" :
		( text + "" ).replace( rtrim, "$1" );
};



// Register as a named AMD module, since jQuery can be concatenated with other
// files that may use define, but not via a proper concatenation script that
// understands anonymous AMD modules. A named AMD is safest and most robust
// way to register. Lowercase jquery is used because AMD module names are
// derived from file names, and jQuery is normally delivered in a lowercase
// file name. Do this after creating the global so that if an AMD module wants
// to call noConflict to hide this version of jQuery, it will work.

// Note that for maximum portability, libraries that are not jQuery should
// declare themselves as anonymous modules, and avoid setting a global if an
// AMD loader is present. jQuery is a special case. For more information, see
// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon

if ( typeof define === "function" && define.amd ) {
	define( "jquery", [], function() {
		return jQuery;
	} );
}




var

	// Map over jQuery in case of overwrite
	_jQuery = window.jQuery,

	// Map over the $ in case of overwrite
	_$ = window.$;

jQuery.noConflict = function( deep ) {
	if ( window.$ === jQuery ) {
		window.$ = _$;
	}

	if ( deep && window.jQuery === jQuery ) {
		window.jQuery = _jQuery;
	}

	return jQuery;
};

// Expose jQuery and $ identifiers, even in AMD
// (trac-7102#comment:10, https://github.com/jquery/jquery/pull/557)
// and CommonJS for browser emulators (trac-13566)
if ( typeof noGlobal === "undefined" ) {
	window.jQuery = window.$ = jQuery;
}




return jQuery;
} );
/* jshint node: true */

/**
 * Unobtrusive scripting adapter for jQuery
 * https://github.com/rails/jquery-ujs
 *
 * Requires jQuery 1.8.0 or later.
 *
 * Released under the MIT license
 *
 */


(function() {
  'use strict';

  var jqueryUjsInit = function($, undefined) {

  // Cut down on the number of issues from people inadvertently including jquery_ujs twice
  // by detecting and raising an error when it happens.
  if ( $.rails !== undefined ) {
    $.error('jquery-ujs has already been loaded!');
  }

  // Shorthand to make it a little easier to call public rails functions from within rails.js
  var rails;
  var $document = $(document);

  $.rails = rails = {
    // Link elements bound by jquery-ujs
    linkClickSelector: 'a[data-confirm], a[data-method], a[data-remote]:not([disabled]), a[data-disable-with], a[data-disable]',

    // Button elements bound by jquery-ujs
    buttonClickSelector: 'button[data-remote]:not([form]):not(form button), button[data-confirm]:not([form]):not(form button)',

    // Select elements bound by jquery-ujs
    inputChangeSelector: 'select[data-remote], input[data-remote], textarea[data-remote]',

    // Form elements bound by jquery-ujs
    formSubmitSelector: 'form:not([data-turbo=true])',

    // Form input elements bound by jquery-ujs
    formInputClickSelector: 'form:not([data-turbo=true]) input[type=submit], form:not([data-turbo=true]) input[type=image], form:not([data-turbo=true]) button[type=submit], form:not([data-turbo=true]) button:not([type]), input[type=submit][form], input[type=image][form], button[type=submit][form], button[form]:not([type])',

    // Form input elements disabled during form submission
    disableSelector: 'input[data-disable-with]:enabled, button[data-disable-with]:enabled, textarea[data-disable-with]:enabled, input[data-disable]:enabled, button[data-disable]:enabled, textarea[data-disable]:enabled',

    // Form input elements re-enabled after form submission
    enableSelector: 'input[data-disable-with]:disabled, button[data-disable-with]:disabled, textarea[data-disable-with]:disabled, input[data-disable]:disabled, button[data-disable]:disabled, textarea[data-disable]:disabled',

    // Form required input elements
    requiredInputSelector: 'input[name][required]:not([disabled]), textarea[name][required]:not([disabled])',

    // Form file input elements
    fileInputSelector: 'input[name][type=file]:not([disabled])',

    // Link onClick disable selector with possible reenable after remote submission
    linkDisableSelector: 'a[data-disable-with], a[data-disable]',

    // Button onClick disable selector with possible reenable after remote submission
    buttonDisableSelector: 'button[data-remote][data-disable-with], button[data-remote][data-disable]',

    // Up-to-date Cross-Site Request Forgery token
    csrfToken: function() {
     return $('meta[name=csrf-token]').attr('content');
    },

    // URL param that must contain the CSRF token
    csrfParam: function() {
     return $('meta[name=csrf-param]').attr('content');
    },

    // Make sure that every Ajax request sends the CSRF token
    CSRFProtection: function(xhr) {
      var token = rails.csrfToken();
      if (token) xhr.setRequestHeader('X-CSRF-Token', token);
    },

    // Make sure that all forms have actual up-to-date tokens (cached forms contain old ones)
    refreshCSRFTokens: function(){
      $('form input[name="' + rails.csrfParam() + '"]').val(rails.csrfToken());
    },

    // Triggers an event on an element and returns false if the event result is false
    fire: function(obj, name, data) {
      var event = $.Event(name);
      obj.trigger(event, data);
      return event.result !== false;
    },

    // Default confirm dialog, may be overridden with custom confirm dialog in $.rails.confirm
    confirm: function(message) {
      return confirm(message);
    },

    // Default ajax function, may be overridden with custom function in $.rails.ajax
    ajax: function(options) {
      return $.ajax(options);
    },

    // Default way to get an element's href. May be overridden at $.rails.href.
    href: function(element) {
      return element[0].href;
    },

    // Checks "data-remote" if true to handle the request through a XHR request.
    isRemote: function(element) {
      return element.data('remote') !== undefined && element.data('remote') !== false;
    },

    // Submits "remote" forms and links with ajax
    handleRemote: function(element) {
      var method, url, data, withCredentials, dataType, options;

      if (rails.fire(element, 'ajax:before')) {
        withCredentials = element.data('with-credentials') || null;
        dataType = element.data('type') || ($.ajaxSettings && $.ajaxSettings.dataType);

        if (element.is('form')) {
          method = element.data('ujs:submit-button-formmethod') || element.attr('method');
          url = element.data('ujs:submit-button-formaction') || element.attr('action');
          data = $(element[0]).serializeArray();
          // memoized value from clicked submit button
          var button = element.data('ujs:submit-button');
          if (button) {
            data.push(button);
            element.data('ujs:submit-button', null);
          }
          element.data('ujs:submit-button-formmethod', null);
          element.data('ujs:submit-button-formaction', null);
        } else if (element.is(rails.inputChangeSelector)) {
          method = element.data('method');
          url = element.data('url');
          data = element.serialize();
          if (element.data('params')) data = data + '&' + element.data('params');
        } else if (element.is(rails.buttonClickSelector)) {
          method = element.data('method') || 'get';
          url = element.data('url');
          data = element.serialize();
          if (element.data('params')) data = data + '&' + element.data('params');
        } else {
          method = element.data('method');
          url = rails.href(element);
          data = element.data('params') || null;
        }

        options = {
          type: method || 'GET', data: data, dataType: dataType,
          // stopping the "ajax:beforeSend" event will cancel the ajax request
          beforeSend: function(xhr, settings) {
            if (settings.dataType === undefined) {
              xhr.setRequestHeader('accept', '*/*;q=0.5, ' + settings.accepts.script);
            }
            if (rails.fire(element, 'ajax:beforeSend', [xhr, settings])) {
              element.trigger('ajax:send', xhr);
            } else {
              return false;
            }
          },
          success: function(data, status, xhr) {
            element.trigger('ajax:success', [data, status, xhr]);
          },
          complete: function(xhr, status) {
            element.trigger('ajax:complete', [xhr, status]);
          },
          error: function(xhr, status, error) {
            element.trigger('ajax:error', [xhr, status, error]);
          },
          crossDomain: rails.isCrossDomain(url)
        };

        // There is no withCredentials for IE6-8 when
        // "Enable native XMLHTTP support" is disabled
        if (withCredentials) {
          options.xhrFields = {
            withCredentials: withCredentials
          };
        }

        // Only pass url to `ajax` options if not blank
        if (url) { options.url = url; }

        return rails.ajax(options);
      } else {
        return false;
      }
    },

    // Determines if the request is a cross domain request.
    isCrossDomain: function(url) {
      var originAnchor = document.createElement('a');
      originAnchor.href = location.href;
      var urlAnchor = document.createElement('a');

      try {
        urlAnchor.href = url;
        // This is a workaround to a IE bug.
        urlAnchor.href = urlAnchor.href;

        // If URL protocol is false or is a string containing a single colon
        // *and* host are false, assume it is not a cross-domain request
        // (should only be the case for IE7 and IE compatibility mode).
        // Otherwise, evaluate protocol and host of the URL against the origin
        // protocol and host.
        return !(((!urlAnchor.protocol || urlAnchor.protocol === ':') && !urlAnchor.host) ||
          (originAnchor.protocol + '//' + originAnchor.host ===
            urlAnchor.protocol + '//' + urlAnchor.host));
      } catch (e) {
        // If there is an error parsing the URL, assume it is crossDomain.
        return true;
      }
    },

    // Handles "data-method" on links such as:
    // <a href="/users/5" data-method="delete" rel="nofollow" data-confirm="Are you sure?">Delete</a>
    handleMethod: function(link) {
      var href = rails.href(link),
        method = link.data('method'),
        target = link.attr('target'),
        csrfToken = rails.csrfToken(),
        csrfParam = rails.csrfParam(),
        form = $('<form method="post" action="' + href + '"></form>'),
        metadataInput = '<input name="_method" value="' + method + '" type="hidden" />';

      if (csrfParam !== undefined && csrfToken !== undefined && !rails.isCrossDomain(href)) {
        metadataInput += '<input name="' + csrfParam + '" value="' + csrfToken + '" type="hidden" />';
      }

      if (target) { form.attr('target', target); }

      form.hide().append(metadataInput).appendTo('body');
      form.submit();
    },

    // Helper function that returns form elements that match the specified CSS selector
    // If form is actually a "form" element this will return associated elements outside the from that have
    // the html form attribute set
    formElements: function(form, selector) {
      return form.is('form') ? $(form[0].elements).filter(selector) : form.find(selector);
    },

    /* Disables form elements:
      - Caches element value in 'ujs:enable-with' data store
      - Replaces element text with value of 'data-disable-with' attribute
      - Sets disabled property to true
    */
    disableFormElements: function(form) {
      rails.formElements(form, rails.disableSelector).each(function() {
        rails.disableFormElement($(this));
      });
    },

    disableFormElement: function(element) {
      var method, replacement;

      method = element.is('button') ? 'html' : 'val';
      replacement = element.data('disable-with');

      if (replacement !== undefined) {
        element.data('ujs:enable-with', element[method]());
        element[method](replacement);
      }

      element.prop('disabled', true);
      element.data('ujs:disabled', true);
    },

    /* Re-enables disabled form elements:
      - Replaces element text with cached value from 'ujs:enable-with' data store (created in `disableFormElements`)
      - Sets disabled property to false
    */
    enableFormElements: function(form) {
      rails.formElements(form, rails.enableSelector).each(function() {
        rails.enableFormElement($(this));
      });
    },

    enableFormElement: function(element) {
      var method = element.is('button') ? 'html' : 'val';
      if (element.data('ujs:enable-with') !== undefined) {
        element[method](element.data('ujs:enable-with'));
        element.removeData('ujs:enable-with'); // clean up cache
      }
      element.prop('disabled', false);
      element.removeData('ujs:disabled');
    },

   /* For 'data-confirm' attribute:
      - Fires `confirm` event
      - Shows the confirmation dialog
      - Fires the `confirm:complete` event

      Returns `true` if no function stops the chain and user chose yes; `false` otherwise.
      Attaching a handler to the element's `confirm` event that returns a `falsy` value cancels the confirmation dialog.
      Attaching a handler to the element's `confirm:complete` event that returns a `falsy` value makes this function
      return false. The `confirm:complete` event is fired whether or not the user answered true or false to the dialog.
   */
    allowAction: function(element) {
      var message = element.data('confirm'),
          answer = false, callback;
      if (!message) { return true; }

      if (rails.fire(element, 'confirm')) {
        try {
          answer = rails.confirm(message);
        } catch (e) {
          (console.error || console.log).call(console, e.stack || e);
        }
        callback = rails.fire(element, 'confirm:complete', [answer]);
      }
      return answer && callback;
    },

    // Helper function which checks for blank inputs in a form that match the specified CSS selector
    blankInputs: function(form, specifiedSelector, nonBlank) {
      var foundInputs = $(),
        input,
        valueToCheck,
        radiosForNameWithNoneSelected,
        radioName,
        selector = specifiedSelector || 'input,textarea',
        requiredInputs = form.find(selector),
        checkedRadioButtonNames = {};

      requiredInputs.each(function() {
        input = $(this);
        if (input.is('input[type=radio]')) {

          // Don't count unchecked required radio as blank if other radio with same name is checked,
          // regardless of whether same-name radio input has required attribute or not. The spec
          // states https://www.w3.org/TR/html5/forms.html#the-required-attribute
          radioName = input.attr('name');

          // Skip if we've already seen the radio with this name.
          if (!checkedRadioButtonNames[radioName]) {

            // If none checked
            if (form.find('input[type=radio]:checked[name="' + radioName + '"]').length === 0) {
              radiosForNameWithNoneSelected = form.find(
                'input[type=radio][name="' + radioName + '"]');
              foundInputs = foundInputs.add(radiosForNameWithNoneSelected);
            }

            // We only need to check each name once.
            checkedRadioButtonNames[radioName] = radioName;
          }
        } else {
          valueToCheck = input.is('input[type=checkbox],input[type=radio]') ? input.is(':checked') : !!input.val();
          if (valueToCheck === nonBlank) {
            foundInputs = foundInputs.add(input);
          }
        }
      });
      return foundInputs.length ? foundInputs : false;
    },

    // Helper function which checks for non-blank inputs in a form that match the specified CSS selector
    nonBlankInputs: function(form, specifiedSelector) {
      return rails.blankInputs(form, specifiedSelector, true); // true specifies nonBlank
    },

    // Helper function, needed to provide consistent behavior in IE
    stopEverything: function(e) {
      $(e.target).trigger('ujs:everythingStopped');
      e.stopImmediatePropagation();
      return false;
    },

    //  Replace element's html with the 'data-disable-with' after storing original html
    //  and prevent clicking on it
    disableElement: function(element) {
      var replacement = element.data('disable-with');

      if (replacement !== undefined) {
        element.data('ujs:enable-with', element.html()); // store enabled state
        element.html(replacement);
      }

      element.on('click.railsDisable', function(e) { // prevent further clicking
        return rails.stopEverything(e);
      });
      element.data('ujs:disabled', true);
    },

    // Restore element to its original state which was disabled by 'disableElement' above
    enableElement: function(element) {
      if (element.data('ujs:enable-with') !== undefined) {
        element.html(element.data('ujs:enable-with')); // set to old enabled state
        element.removeData('ujs:enable-with'); // clean up cache
      }
      element.off('click.railsDisable'); // enable element
      element.removeData('ujs:disabled');
    }
  };

  if (rails.fire($document, 'rails:attachBindings')) {

    $.ajaxPrefilter(function(options, originalOptions, xhr){ if ( !options.crossDomain ) { rails.CSRFProtection(xhr); }});

    // This event works the same as the load event, except that it fires every
    // time the page is loaded.
    //
    // See https://github.com/rails/jquery-ujs/issues/357
    // See https://developer.mozilla.org/en-US/docs/Using_Firefox_1.5_caching
    $(window).on('pageshow.rails', function () {
      $($.rails.enableSelector).each(function () {
        var element = $(this);

        if (element.data('ujs:disabled')) {
          $.rails.enableFormElement(element);
        }
      });

      $($.rails.linkDisableSelector).each(function () {
        var element = $(this);

        if (element.data('ujs:disabled')) {
          $.rails.enableElement(element);
        }
      });
    });

    $document.on('ajax:complete', rails.linkDisableSelector, function() {
        rails.enableElement($(this));
    });

    $document.on('ajax:complete', rails.buttonDisableSelector, function() {
        rails.enableFormElement($(this));
    });

    $document.on('click.rails', rails.linkClickSelector, function(e) {
      var link = $(this), method = link.data('method'), data = link.data('params'), metaClick = e.metaKey || e.ctrlKey;
      if (!rails.allowAction(link)) return rails.stopEverything(e);

      if (!metaClick && link.is(rails.linkDisableSelector)) rails.disableElement(link);

      if (rails.isRemote(link)) {
        if (metaClick && (!method || method === 'GET') && !data) { return true; }

        var handleRemote = rails.handleRemote(link);
        // Response from rails.handleRemote() will either be false or a deferred object promise.
        if (handleRemote === false) {
          rails.enableElement(link);
        } else {
          handleRemote.fail( function() { rails.enableElement(link); } );
        }
        return false;

      } else if (method) {
        rails.handleMethod(link);
        return false;
      }
    });

    $document.on('click.rails', rails.buttonClickSelector, function(e) {
      var button = $(this);

      if (!rails.allowAction(button) || !rails.isRemote(button)) return rails.stopEverything(e);

      if (button.is(rails.buttonDisableSelector)) rails.disableFormElement(button);

      var handleRemote = rails.handleRemote(button);
      // Response from rails.handleRemote() will either be false or a deferred object promise.
      if (handleRemote === false) {
        rails.enableFormElement(button);
      } else {
        handleRemote.fail( function() { rails.enableFormElement(button); } );
      }
      return false;
    });

    $document.on('change.rails', rails.inputChangeSelector, function(e) {
      var link = $(this);
      if (!rails.allowAction(link) || !rails.isRemote(link)) return rails.stopEverything(e);

      rails.handleRemote(link);
      return false;
    });

    $document.on('submit.rails', rails.formSubmitSelector, function(e) {
      var form = $(this),
        remote = rails.isRemote(form),
        blankRequiredInputs,
        nonBlankFileInputs;

      if (!rails.allowAction(form)) return rails.stopEverything(e);

      // Skip other logic when required values are missing or file upload is present
      if (form.attr('novalidate') === undefined) {
        if (form.data('ujs:formnovalidate-button') === undefined) {
          blankRequiredInputs = rails.blankInputs(form, rails.requiredInputSelector, false);
          if (blankRequiredInputs && rails.fire(form, 'ajax:aborted:required', [blankRequiredInputs])) {
            return rails.stopEverything(e);
          }
        } else {
          // Clear the formnovalidate in case the next button click is not on a formnovalidate button
          // Not strictly necessary to do here, since it is also reset on each button click, but just to be certain
          form.data('ujs:formnovalidate-button', undefined);
        }
      }

      if (remote) {
        nonBlankFileInputs = rails.nonBlankInputs(form, rails.fileInputSelector);
        if (nonBlankFileInputs) {
          // Slight timeout so that the submit button gets properly serialized
          // (make it easy for event handler to serialize form without disabled values)
          setTimeout(function(){ rails.disableFormElements(form); }, 13);
          var aborted = rails.fire(form, 'ajax:aborted:file', [nonBlankFileInputs]);

          // Re-enable form elements if event bindings return false (canceling normal form submission)
          if (!aborted) { setTimeout(function(){ rails.enableFormElements(form); }, 13); }

          return aborted;
        }

        rails.handleRemote(form);
        return false;

      } else {
        // Slight timeout so that the submit button gets properly serialized
        setTimeout(function(){ rails.disableFormElements(form); }, 13);
      }
    });

    $document.on('click.rails', rails.formInputClickSelector, function(event) {
      var button = $(this);

      if (!rails.allowAction(button)) return rails.stopEverything(event);

      // Register the pressed submit button
      var name = button.attr('name'),
        data = name ? {name:name, value:button.val()} : null;

      var form = button.closest('form');
      if (form.length === 0) {
        form = $('#' + button.attr('form'));
      }
      form.data('ujs:submit-button', data);

      // Save attributes from button
      form.data('ujs:formnovalidate-button', button.attr('formnovalidate'));
      form.data('ujs:submit-button-formaction', button.attr('formaction'));
      form.data('ujs:submit-button-formmethod', button.attr('formmethod'));
    });

    $document.on('ajax:send.rails', rails.formSubmitSelector, function(event) {
      if (this === event.target) rails.disableFormElements($(this));
    });

    $document.on('ajax:complete.rails', rails.formSubmitSelector, function(event) {
      if (this === event.target) rails.enableFormElements($(this));
    });

    $(function(){
      rails.refreshCSRFTokens();
    });
  }

  };

  if (window.jQuery) {
    jqueryUjsInit(jQuery);
  } else if (typeof exports === 'object' && typeof module === 'object') {
    module.exports = jqueryUjsInit;
  }
})();
/*
Turbolinks 5.2.0
Copyright © 2018 Basecamp, LLC
 */

(function(){var t=this;(function(){(function(){this.Turbolinks={supported:function(){return null!=window.history.pushState&&null!=window.requestAnimationFrame&&null!=window.addEventListener}(),visit:function(t,r){return e.controller.visit(t,r)},clearCache:function(){return e.controller.clearCache()},setProgressBarDelay:function(t){return e.controller.setProgressBarDelay(t)}}}).call(this)}).call(t);var e=t.Turbolinks;(function(){(function(){var t,r,n,o=[].slice;e.copyObject=function(t){var e,r,n;r={};for(e in t)n=t[e],r[e]=n;return r},e.closest=function(e,r){return t.call(e,r)},t=function(){var t,e;return t=document.documentElement,null!=(e=t.closest)?e:function(t){var e;for(e=this;e;){if(e.nodeType===Node.ELEMENT_NODE&&r.call(e,t))return e;e=e.parentNode}}}(),e.defer=function(t){return setTimeout(t,1)},e.throttle=function(t){var e;return e=null,function(){var r;return r=1<=arguments.length?o.call(arguments,0):[],null!=e?e:e=requestAnimationFrame(function(n){return function(){return e=null,t.apply(n,r)}}(this))}},e.dispatch=function(t,e){var r,o,i,s,a,u;return a=null!=e?e:{},u=a.target,r=a.cancelable,o=a.data,i=document.createEvent("Events"),i.initEvent(t,!0,r===!0),i.data=null!=o?o:{},i.cancelable&&!n&&(s=i.preventDefault,i.preventDefault=function(){return this.defaultPrevented||Object.defineProperty(this,"defaultPrevented",{get:function(){return!0}}),s.call(this)}),(null!=u?u:document).dispatchEvent(i),i},n=function(){var t;return t=document.createEvent("Events"),t.initEvent("test",!0,!0),t.preventDefault(),t.defaultPrevented}(),e.match=function(t,e){return r.call(t,e)},r=function(){var t,e,r,n;return t=document.documentElement,null!=(e=null!=(r=null!=(n=t.matchesSelector)?n:t.webkitMatchesSelector)?r:t.msMatchesSelector)?e:t.mozMatchesSelector}(),e.uuid=function(){var t,e,r;for(r="",t=e=1;36>=e;t=++e)r+=9===t||14===t||19===t||24===t?"-":15===t?"4":20===t?(Math.floor(4*Math.random())+8).toString(16):Math.floor(15*Math.random()).toString(16);return r}}).call(this),function(){e.Location=function(){function t(t){var e,r;null==t&&(t=""),r=document.createElement("a"),r.href=t.toString(),this.absoluteURL=r.href,e=r.hash.length,2>e?this.requestURL=this.absoluteURL:(this.requestURL=this.absoluteURL.slice(0,-e),this.anchor=r.hash.slice(1))}var e,r,n,o;return t.wrap=function(t){return t instanceof this?t:new this(t)},t.prototype.getOrigin=function(){return this.absoluteURL.split("/",3).join("/")},t.prototype.getPath=function(){var t,e;return null!=(t=null!=(e=this.requestURL.match(/\/\/[^\/]*(\/[^?;]*)/))?e[1]:void 0)?t:"/"},t.prototype.getPathComponents=function(){return this.getPath().split("/").slice(1)},t.prototype.getLastPathComponent=function(){return this.getPathComponents().slice(-1)[0]},t.prototype.getExtension=function(){var t,e;return null!=(t=null!=(e=this.getLastPathComponent().match(/\.[^.]*$/))?e[0]:void 0)?t:""},t.prototype.isHTML=function(){return this.getExtension().match(/^(?:|\.(?:htm|html|xhtml))$/)},t.prototype.isPrefixedBy=function(t){var e;return e=r(t),this.isEqualTo(t)||o(this.absoluteURL,e)},t.prototype.isEqualTo=function(t){return this.absoluteURL===(null!=t?t.absoluteURL:void 0)},t.prototype.toCacheKey=function(){return this.requestURL},t.prototype.toJSON=function(){return this.absoluteURL},t.prototype.toString=function(){return this.absoluteURL},t.prototype.valueOf=function(){return this.absoluteURL},r=function(t){return e(t.getOrigin()+t.getPath())},e=function(t){return n(t,"/")?t:t+"/"},o=function(t,e){return t.slice(0,e.length)===e},n=function(t,e){return t.slice(-e.length)===e},t}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.HttpRequest=function(){function r(r,n,o){this.delegate=r,this.requestCanceled=t(this.requestCanceled,this),this.requestTimedOut=t(this.requestTimedOut,this),this.requestFailed=t(this.requestFailed,this),this.requestLoaded=t(this.requestLoaded,this),this.requestProgressed=t(this.requestProgressed,this),this.url=e.Location.wrap(n).requestURL,this.referrer=e.Location.wrap(o).absoluteURL,this.createXHR()}return r.NETWORK_FAILURE=0,r.TIMEOUT_FAILURE=-1,r.timeout=60,r.prototype.send=function(){var t;return this.xhr&&!this.sent?(this.notifyApplicationBeforeRequestStart(),this.setProgress(0),this.xhr.send(),this.sent=!0,"function"==typeof(t=this.delegate).requestStarted?t.requestStarted():void 0):void 0},r.prototype.cancel=function(){return this.xhr&&this.sent?this.xhr.abort():void 0},r.prototype.requestProgressed=function(t){return t.lengthComputable?this.setProgress(t.loaded/t.total):void 0},r.prototype.requestLoaded=function(){return this.endRequest(function(t){return function(){var e;return 200<=(e=t.xhr.status)&&300>e?t.delegate.requestCompletedWithResponse(t.xhr.responseText,t.xhr.getResponseHeader("Turbolinks-Location")):(t.failed=!0,t.delegate.requestFailedWithStatusCode(t.xhr.status,t.xhr.responseText))}}(this))},r.prototype.requestFailed=function(){return this.endRequest(function(t){return function(){return t.failed=!0,t.delegate.requestFailedWithStatusCode(t.constructor.NETWORK_FAILURE)}}(this))},r.prototype.requestTimedOut=function(){return this.endRequest(function(t){return function(){return t.failed=!0,t.delegate.requestFailedWithStatusCode(t.constructor.TIMEOUT_FAILURE)}}(this))},r.prototype.requestCanceled=function(){return this.endRequest()},r.prototype.notifyApplicationBeforeRequestStart=function(){return e.dispatch("turbolinks:request-start",{data:{url:this.url,xhr:this.xhr}})},r.prototype.notifyApplicationAfterRequestEnd=function(){return e.dispatch("turbolinks:request-end",{data:{url:this.url,xhr:this.xhr}})},r.prototype.createXHR=function(){return this.xhr=new XMLHttpRequest,this.xhr.open("GET",this.url,!0),this.xhr.timeout=1e3*this.constructor.timeout,this.xhr.setRequestHeader("Accept","text/html, application/xhtml+xml"),this.xhr.setRequestHeader("Turbolinks-Referrer",this.referrer),this.xhr.onprogress=this.requestProgressed,this.xhr.onload=this.requestLoaded,this.xhr.onerror=this.requestFailed,this.xhr.ontimeout=this.requestTimedOut,this.xhr.onabort=this.requestCanceled},r.prototype.endRequest=function(t){return this.xhr?(this.notifyApplicationAfterRequestEnd(),null!=t&&t.call(this),this.destroy()):void 0},r.prototype.setProgress=function(t){var e;return this.progress=t,"function"==typeof(e=this.delegate).requestProgressed?e.requestProgressed(this.progress):void 0},r.prototype.destroy=function(){var t;return this.setProgress(1),"function"==typeof(t=this.delegate).requestFinished&&t.requestFinished(),this.delegate=null,this.xhr=null},r}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.ProgressBar=function(){function e(){this.trickle=t(this.trickle,this),this.stylesheetElement=this.createStylesheetElement(),this.progressElement=this.createProgressElement()}var r;return r=300,e.defaultCSS=".turbolinks-progress-bar {\n  position: fixed;\n  display: block;\n  top: 0;\n  left: 0;\n  height: 3px;\n  background: #0076ff;\n  z-index: 9999;\n  transition: width "+r+"ms ease-out, opacity "+r/2+"ms "+r/2+"ms ease-in;\n  transform: translate3d(0, 0, 0);\n}",e.prototype.show=function(){return this.visible?void 0:(this.visible=!0,this.installStylesheetElement(),this.installProgressElement(),this.startTrickling())},e.prototype.hide=function(){return this.visible&&!this.hiding?(this.hiding=!0,this.fadeProgressElement(function(t){return function(){return t.uninstallProgressElement(),t.stopTrickling(),t.visible=!1,t.hiding=!1}}(this))):void 0},e.prototype.setValue=function(t){return this.value=t,this.refresh()},e.prototype.installStylesheetElement=function(){return document.head.insertBefore(this.stylesheetElement,document.head.firstChild)},e.prototype.installProgressElement=function(){return this.progressElement.style.width=0,this.progressElement.style.opacity=1,document.documentElement.insertBefore(this.progressElement,document.body),this.refresh()},e.prototype.fadeProgressElement=function(t){return this.progressElement.style.opacity=0,setTimeout(t,1.5*r)},e.prototype.uninstallProgressElement=function(){return this.progressElement.parentNode?document.documentElement.removeChild(this.progressElement):void 0},e.prototype.startTrickling=function(){return null!=this.trickleInterval?this.trickleInterval:this.trickleInterval=setInterval(this.trickle,r)},e.prototype.stopTrickling=function(){return clearInterval(this.trickleInterval),this.trickleInterval=null},e.prototype.trickle=function(){return this.setValue(this.value+Math.random()/100)},e.prototype.refresh=function(){return requestAnimationFrame(function(t){return function(){return t.progressElement.style.width=10+90*t.value+"%"}}(this))},e.prototype.createStylesheetElement=function(){var t;return t=document.createElement("style"),t.type="text/css",t.textContent=this.constructor.defaultCSS,t},e.prototype.createProgressElement=function(){var t;return t=document.createElement("div"),t.className="turbolinks-progress-bar",t},e}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.BrowserAdapter=function(){function r(r){this.controller=r,this.showProgressBar=t(this.showProgressBar,this),this.progressBar=new e.ProgressBar}var n,o,i;return i=e.HttpRequest,n=i.NETWORK_FAILURE,o=i.TIMEOUT_FAILURE,r.prototype.visitProposedToLocationWithAction=function(t,e){return this.controller.startVisitToLocationWithAction(t,e)},r.prototype.visitStarted=function(t){return t.issueRequest(),t.changeHistory(),t.loadCachedSnapshot()},r.prototype.visitRequestStarted=function(t){return this.progressBar.setValue(0),t.hasCachedSnapshot()||"restore"!==t.action?this.showProgressBarAfterDelay():this.showProgressBar()},r.prototype.visitRequestProgressed=function(t){return this.progressBar.setValue(t.progress)},r.prototype.visitRequestCompleted=function(t){return t.loadResponse()},r.prototype.visitRequestFailedWithStatusCode=function(t,e){switch(e){case n:case o:return this.reload();default:return t.loadResponse()}},r.prototype.visitRequestFinished=function(t){return this.hideProgressBar()},r.prototype.visitCompleted=function(t){return t.followRedirect()},r.prototype.pageInvalidated=function(){return this.reload()},r.prototype.showProgressBarAfterDelay=function(){return this.progressBarTimeout=setTimeout(this.showProgressBar,this.controller.progressBarDelay)},r.prototype.showProgressBar=function(){return this.progressBar.show()},r.prototype.hideProgressBar=function(){return this.progressBar.hide(),clearTimeout(this.progressBarTimeout)},r.prototype.reload=function(){return window.location.reload()},r}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.History=function(){function r(e){this.delegate=e,this.onPageLoad=t(this.onPageLoad,this),this.onPopState=t(this.onPopState,this)}return r.prototype.start=function(){return this.started?void 0:(addEventListener("popstate",this.onPopState,!1),addEventListener("load",this.onPageLoad,!1),this.started=!0)},r.prototype.stop=function(){return this.started?(removeEventListener("popstate",this.onPopState,!1),removeEventListener("load",this.onPageLoad,!1),this.started=!1):void 0},r.prototype.push=function(t,r){return t=e.Location.wrap(t),this.update("push",t,r)},r.prototype.replace=function(t,r){return t=e.Location.wrap(t),this.update("replace",t,r)},r.prototype.onPopState=function(t){var r,n,o,i;return this.shouldHandlePopState()&&(i=null!=(n=t.state)?n.turbolinks:void 0)?(r=e.Location.wrap(window.location),o=i.restorationIdentifier,this.delegate.historyPoppedToLocationWithRestorationIdentifier(r,o)):void 0},r.prototype.onPageLoad=function(t){return e.defer(function(t){return function(){return t.pageLoaded=!0}}(this))},r.prototype.shouldHandlePopState=function(){return this.pageIsLoaded()},r.prototype.pageIsLoaded=function(){return this.pageLoaded||"complete"===document.readyState},r.prototype.update=function(t,e,r){var n;return n={turbolinks:{restorationIdentifier:r}},history[t+"State"](n,null,e)},r}()}.call(this),function(){e.HeadDetails=function(){function t(t){var e,r,n,s,a,u;for(this.elements={},n=0,a=t.length;a>n;n++)u=t[n],u.nodeType===Node.ELEMENT_NODE&&(s=u.outerHTML,r=null!=(e=this.elements)[s]?e[s]:e[s]={type:i(u),tracked:o(u),elements:[]},r.elements.push(u))}var e,r,n,o,i;return t.fromHeadElement=function(t){var e;return new this(null!=(e=null!=t?t.childNodes:void 0)?e:[])},t.prototype.hasElementWithKey=function(t){return t in this.elements},t.prototype.getTrackedElementSignature=function(){var t,e;return function(){var r,n;r=this.elements,n=[];for(t in r)e=r[t].tracked,e&&n.push(t);return n}.call(this).join("")},t.prototype.getScriptElementsNotInDetails=function(t){return this.getElementsMatchingTypeNotInDetails("script",t)},t.prototype.getStylesheetElementsNotInDetails=function(t){return this.getElementsMatchingTypeNotInDetails("stylesheet",t)},t.prototype.getElementsMatchingTypeNotInDetails=function(t,e){var r,n,o,i,s,a;o=this.elements,s=[];for(n in o)i=o[n],a=i.type,r=i.elements,a!==t||e.hasElementWithKey(n)||s.push(r[0]);return s},t.prototype.getProvisionalElements=function(){var t,e,r,n,o,i,s;r=[],n=this.elements;for(e in n)o=n[e],s=o.type,i=o.tracked,t=o.elements,null!=s||i?t.length>1&&r.push.apply(r,t.slice(1)):r.push.apply(r,t);return r},t.prototype.getMetaValue=function(t){var e;return null!=(e=this.findMetaElementByName(t))?e.getAttribute("content"):void 0},t.prototype.findMetaElementByName=function(t){var r,n,o,i;r=void 0,i=this.elements;for(o in i)n=i[o].elements,e(n[0],t)&&(r=n[0]);return r},i=function(t){return r(t)?"script":n(t)?"stylesheet":void 0},o=function(t){return"reload"===t.getAttribute("data-turbolinks-track")},r=function(t){var e;return e=t.tagName.toLowerCase(),"script"===e},n=function(t){var e;return e=t.tagName.toLowerCase(),"style"===e||"link"===e&&"stylesheet"===t.getAttribute("rel")},e=function(t,e){var r;return r=t.tagName.toLowerCase(),"meta"===r&&t.getAttribute("name")===e},t}()}.call(this),function(){e.Snapshot=function(){function t(t,e){this.headDetails=t,this.bodyElement=e}return t.wrap=function(t){return t instanceof this?t:"string"==typeof t?this.fromHTMLString(t):this.fromHTMLElement(t)},t.fromHTMLString=function(t){var e;return e=document.createElement("html"),e.innerHTML=t,this.fromHTMLElement(e)},t.fromHTMLElement=function(t){var r,n,o,i;return o=t.querySelector("head"),r=null!=(i=t.querySelector("body"))?i:document.createElement("body"),n=e.HeadDetails.fromHeadElement(o),new this(n,r)},t.prototype.clone=function(){return new this.constructor(this.headDetails,this.bodyElement.cloneNode(!0))},t.prototype.getRootLocation=function(){var t,r;return r=null!=(t=this.getSetting("root"))?t:"/",new e.Location(r)},t.prototype.getCacheControlValue=function(){return this.getSetting("cache-control")},t.prototype.getElementForAnchor=function(t){try{return this.bodyElement.querySelector("[id='"+t+"'], a[name='"+t+"']")}catch(e){}},t.prototype.getPermanentElements=function(){return this.bodyElement.querySelectorAll("[id][data-turbolinks-permanent]")},t.prototype.getPermanentElementById=function(t){return this.bodyElement.querySelector("#"+t+"[data-turbolinks-permanent]")},t.prototype.getPermanentElementsPresentInSnapshot=function(t){var e,r,n,o,i;for(o=this.getPermanentElements(),i=[],r=0,n=o.length;n>r;r++)e=o[r],t.getPermanentElementById(e.id)&&i.push(e);return i},t.prototype.findFirstAutofocusableElement=function(){return this.bodyElement.querySelector("[autofocus]")},t.prototype.hasAnchor=function(t){return null!=this.getElementForAnchor(t)},t.prototype.isPreviewable=function(){return"no-preview"!==this.getCacheControlValue()},t.prototype.isCacheable=function(){return"no-cache"!==this.getCacheControlValue()},t.prototype.isVisitable=function(){return"reload"!==this.getSetting("visit-control")},t.prototype.getSetting=function(t){return this.headDetails.getMetaValue("turbolinks-"+t)},t}()}.call(this),function(){var t=[].slice;e.Renderer=function(){function e(){}var r;return e.render=function(){var e,r,n,o;return n=arguments[0],r=arguments[1],e=3<=arguments.length?t.call(arguments,2):[],o=function(t,e,r){r.prototype=t.prototype;var n=new r,o=t.apply(n,e);return Object(o)===o?o:n}(this,e,function(){}),o.delegate=n,o.render(r),o},e.prototype.renderView=function(t){return this.delegate.viewWillRender(this.newBody),t(),this.delegate.viewRendered(this.newBody)},e.prototype.invalidateView=function(){return this.delegate.viewInvalidated()},e.prototype.createScriptElement=function(t){var e;return"false"===t.getAttribute("data-turbolinks-eval")?t:(e=document.createElement("script"),e.textContent=t.textContent,e.async=!1,r(e,t),e)},r=function(t,e){var r,n,o,i,s,a,u;for(i=e.attributes,a=[],r=0,n=i.length;n>r;r++)s=i[r],o=s.name,u=s.value,a.push(t.setAttribute(o,u));return a},e}()}.call(this),function(){var t,r,n=function(t,e){function r(){this.constructor=t}for(var n in e)o.call(e,n)&&(t[n]=e[n]);return r.prototype=e.prototype,t.prototype=new r,t.__super__=e.prototype,t},o={}.hasOwnProperty;e.SnapshotRenderer=function(e){function o(t,e,r){this.currentSnapshot=t,this.newSnapshot=e,this.isPreview=r,this.currentHeadDetails=this.currentSnapshot.headDetails,this.newHeadDetails=this.newSnapshot.headDetails,this.currentBody=this.currentSnapshot.bodyElement,this.newBody=this.newSnapshot.bodyElement}return n(o,e),o.prototype.render=function(t){return this.shouldRender()?(this.mergeHead(),this.renderView(function(e){return function(){return e.replaceBody(),e.isPreview||e.focusFirstAutofocusableElement(),t()}}(this))):this.invalidateView()},o.prototype.mergeHead=function(){return this.copyNewHeadStylesheetElements(),this.copyNewHeadScriptElements(),this.removeCurrentHeadProvisionalElements(),this.copyNewHeadProvisionalElements()},o.prototype.replaceBody=function(){var t;return t=this.relocateCurrentBodyPermanentElements(),this.activateNewBodyScriptElements(),this.assignNewBody(),this.replacePlaceholderElementsWithClonedPermanentElements(t)},o.prototype.shouldRender=function(){return this.newSnapshot.isVisitable()&&this.trackedElementsAreIdentical()},o.prototype.trackedElementsAreIdentical=function(){return this.currentHeadDetails.getTrackedElementSignature()===this.newHeadDetails.getTrackedElementSignature()},o.prototype.copyNewHeadStylesheetElements=function(){var t,e,r,n,o;for(n=this.getNewHeadStylesheetElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.appendChild(t));return o},o.prototype.copyNewHeadScriptElements=function(){var t,e,r,n,o;for(n=this.getNewHeadScriptElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.appendChild(this.createScriptElement(t)));return o},o.prototype.removeCurrentHeadProvisionalElements=function(){var t,e,r,n,o;for(n=this.getCurrentHeadProvisionalElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.removeChild(t));return o},o.prototype.copyNewHeadProvisionalElements=function(){var t,e,r,n,o;for(n=this.getNewHeadProvisionalElements(),o=[],e=0,r=n.length;r>e;e++)t=n[e],o.push(document.head.appendChild(t));return o},o.prototype.relocateCurrentBodyPermanentElements=function(){var e,n,o,i,s,a,u;for(a=this.getCurrentBodyPermanentElements(),u=[],e=0,n=a.length;n>e;e++)i=a[e],s=t(i),o=this.newSnapshot.getPermanentElementById(i.id),r(i,s.element),r(o,i),u.push(s);return u},o.prototype.replacePlaceholderElementsWithClonedPermanentElements=function(t){var e,n,o,i,s,a,u;for(u=[],o=0,i=t.length;i>o;o++)a=t[o],n=a.element,s=a.permanentElement,e=s.cloneNode(!0),u.push(r(n,e));return u},o.prototype.activateNewBodyScriptElements=function(){var t,e,n,o,i,s;for(i=this.getNewBodyScriptElements(),s=[],e=0,o=i.length;o>e;e++)n=i[e],t=this.createScriptElement(n),s.push(r(n,t));return s},o.prototype.assignNewBody=function(){return document.body=this.newBody},o.prototype.focusFirstAutofocusableElement=function(){var t;return null!=(t=this.newSnapshot.findFirstAutofocusableElement())?t.focus():void 0},o.prototype.getNewHeadStylesheetElements=function(){return this.newHeadDetails.getStylesheetElementsNotInDetails(this.currentHeadDetails)},o.prototype.getNewHeadScriptElements=function(){return this.newHeadDetails.getScriptElementsNotInDetails(this.currentHeadDetails)},o.prototype.getCurrentHeadProvisionalElements=function(){return this.currentHeadDetails.getProvisionalElements()},o.prototype.getNewHeadProvisionalElements=function(){return this.newHeadDetails.getProvisionalElements()},o.prototype.getCurrentBodyPermanentElements=function(){return this.currentSnapshot.getPermanentElementsPresentInSnapshot(this.newSnapshot)},o.prototype.getNewBodyScriptElements=function(){return this.newBody.querySelectorAll("script")},o}(e.Renderer),t=function(t){var e;return e=document.createElement("meta"),e.setAttribute("name","turbolinks-permanent-placeholder"),e.setAttribute("content",t.id),{element:e,permanentElement:t}},r=function(t,e){var r;return(r=t.parentNode)?r.replaceChild(e,t):void 0}}.call(this),function(){var t=function(t,e){function n(){this.constructor=t}for(var o in e)r.call(e,o)&&(t[o]=e[o]);return n.prototype=e.prototype,t.prototype=new n,t.__super__=e.prototype,t},r={}.hasOwnProperty;e.ErrorRenderer=function(e){function r(t){var e;e=document.createElement("html"),e.innerHTML=t,this.newHead=e.querySelector("head"),this.newBody=e.querySelector("body")}return t(r,e),r.prototype.render=function(t){return this.renderView(function(e){return function(){return e.replaceHeadAndBody(),e.activateBodyScriptElements(),t()}}(this))},r.prototype.replaceHeadAndBody=function(){var t,e;return e=document.head,t=document.body,e.parentNode.replaceChild(this.newHead,e),t.parentNode.replaceChild(this.newBody,t)},r.prototype.activateBodyScriptElements=function(){var t,e,r,n,o,i;for(n=this.getScriptElements(),i=[],e=0,r=n.length;r>e;e++)o=n[e],t=this.createScriptElement(o),i.push(o.parentNode.replaceChild(t,o));return i},r.prototype.getScriptElements=function(){return document.documentElement.querySelectorAll("script")},r}(e.Renderer)}.call(this),function(){e.View=function(){function t(t){this.delegate=t,this.htmlElement=document.documentElement}return t.prototype.getRootLocation=function(){return this.getSnapshot().getRootLocation()},t.prototype.getElementForAnchor=function(t){return this.getSnapshot().getElementForAnchor(t)},t.prototype.getSnapshot=function(){return e.Snapshot.fromHTMLElement(this.htmlElement)},t.prototype.render=function(t,e){var r,n,o;return o=t.snapshot,r=t.error,n=t.isPreview,this.markAsPreview(n),null!=o?this.renderSnapshot(o,n,e):this.renderError(r,e)},t.prototype.markAsPreview=function(t){return t?this.htmlElement.setAttribute("data-turbolinks-preview",""):this.htmlElement.removeAttribute("data-turbolinks-preview")},t.prototype.renderSnapshot=function(t,r,n){return e.SnapshotRenderer.render(this.delegate,n,this.getSnapshot(),e.Snapshot.wrap(t),r)},t.prototype.renderError=function(t,r){return e.ErrorRenderer.render(this.delegate,r,t)},t}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.ScrollManager=function(){function r(r){this.delegate=r,this.onScroll=t(this.onScroll,this),this.onScroll=e.throttle(this.onScroll)}return r.prototype.start=function(){return this.started?void 0:(addEventListener("scroll",this.onScroll,!1),this.onScroll(),this.started=!0)},r.prototype.stop=function(){return this.started?(removeEventListener("scroll",this.onScroll,!1),this.started=!1):void 0},r.prototype.scrollToElement=function(t){return t.scrollIntoView()},r.prototype.scrollToPosition=function(t){var e,r;return e=t.x,r=t.y,window.scrollTo(e,r)},r.prototype.onScroll=function(t){return this.updatePosition({x:window.pageXOffset,y:window.pageYOffset})},r.prototype.updatePosition=function(t){var e;return this.position=t,null!=(e=this.delegate)?e.scrollPositionChanged(this.position):void 0},r}()}.call(this),function(){e.SnapshotCache=function(){function t(t){this.size=t,this.keys=[],this.snapshots={}}var r;return t.prototype.has=function(t){var e;return e=r(t),e in this.snapshots},t.prototype.get=function(t){var e;if(this.has(t))return e=this.read(t),this.touch(t),e},t.prototype.put=function(t,e){return this.write(t,e),this.touch(t),e},t.prototype.read=function(t){var e;return e=r(t),this.snapshots[e]},t.prototype.write=function(t,e){var n;return n=r(t),this.snapshots[n]=e},t.prototype.touch=function(t){var e,n;return n=r(t),e=this.keys.indexOf(n),e>-1&&this.keys.splice(e,1),this.keys.unshift(n),this.trim()},t.prototype.trim=function(){var t,e,r,n,o;for(n=this.keys.splice(this.size),o=[],t=0,r=n.length;r>t;t++)e=n[t],o.push(delete this.snapshots[e]);return o},r=function(t){return e.Location.wrap(t).toCacheKey()},t}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.Visit=function(){function r(r,n,o){this.controller=r,this.action=o,this.performScroll=t(this.performScroll,this),this.identifier=e.uuid(),this.location=e.Location.wrap(n),this.adapter=this.controller.adapter,this.state="initialized",this.timingMetrics={}}var n;return r.prototype.start=function(){return"initialized"===this.state?(this.recordTimingMetric("visitStart"),this.state="started",this.adapter.visitStarted(this)):void 0},r.prototype.cancel=function(){var t;return"started"===this.state?(null!=(t=this.request)&&t.cancel(),this.cancelRender(),this.state="canceled"):void 0},r.prototype.complete=function(){var t;return"started"===this.state?(this.recordTimingMetric("visitEnd"),this.state="completed","function"==typeof(t=this.adapter).visitCompleted&&t.visitCompleted(this),this.controller.visitCompleted(this)):void 0},r.prototype.fail=function(){var t;return"started"===this.state?(this.state="failed","function"==typeof(t=this.adapter).visitFailed?t.visitFailed(this):void 0):void 0},r.prototype.changeHistory=function(){var t,e;return this.historyChanged?void 0:(t=this.location.isEqualTo(this.referrer)?"replace":this.action,e=n(t),this.controller[e](this.location,this.restorationIdentifier),this.historyChanged=!0)},r.prototype.issueRequest=function(){return this.shouldIssueRequest()&&null==this.request?(this.progress=0,this.request=new e.HttpRequest(this,this.location,this.referrer),this.request.send()):void 0},r.prototype.getCachedSnapshot=function(){var t;return!(t=this.controller.getCachedSnapshotForLocation(this.location))||null!=this.location.anchor&&!t.hasAnchor(this.location.anchor)||"restore"!==this.action&&!t.isPreviewable()?void 0:t},r.prototype.hasCachedSnapshot=function(){return null!=this.getCachedSnapshot()},r.prototype.loadCachedSnapshot=function(){var t,e;return(e=this.getCachedSnapshot())?(t=this.shouldIssueRequest(),this.render(function(){var r;return this.cacheSnapshot(),this.controller.render({snapshot:e,isPreview:t},this.performScroll),"function"==typeof(r=this.adapter).visitRendered&&r.visitRendered(this),t?void 0:this.complete()})):void 0},r.prototype.loadResponse=function(){return null!=this.response?this.render(function(){var t,e;return this.cacheSnapshot(),this.request.failed?(this.controller.render({error:this.response},this.performScroll),"function"==typeof(t=this.adapter).visitRendered&&t.visitRendered(this),this.fail()):(this.controller.render({snapshot:this.response},this.performScroll),"function"==typeof(e=this.adapter).visitRendered&&e.visitRendered(this),this.complete())}):void 0},r.prototype.followRedirect=function(){return this.redirectedToLocation&&!this.followedRedirect?(this.location=this.redirectedToLocation,this.controller.replaceHistoryWithLocationAndRestorationIdentifier(this.redirectedToLocation,this.restorationIdentifier),this.followedRedirect=!0):void 0},r.prototype.requestStarted=function(){var t;return this.recordTimingMetric("requestStart"),"function"==typeof(t=this.adapter).visitRequestStarted?t.visitRequestStarted(this):void 0},r.prototype.requestProgressed=function(t){var e;return this.progress=t,"function"==typeof(e=this.adapter).visitRequestProgressed?e.visitRequestProgressed(this):void 0},r.prototype.requestCompletedWithResponse=function(t,r){return this.response=t,null!=r&&(this.redirectedToLocation=e.Location.wrap(r)),this.adapter.visitRequestCompleted(this)},r.prototype.requestFailedWithStatusCode=function(t,e){return this.response=e,this.adapter.visitRequestFailedWithStatusCode(this,t)},r.prototype.requestFinished=function(){var t;return this.recordTimingMetric("requestEnd"),"function"==typeof(t=this.adapter).visitRequestFinished?t.visitRequestFinished(this):void 0},r.prototype.performScroll=function(){return this.scrolled?void 0:("restore"===this.action?this.scrollToRestoredPosition()||this.scrollToTop():this.scrollToAnchor()||this.scrollToTop(),this.scrolled=!0)},r.prototype.scrollToRestoredPosition=function(){var t,e;return t=null!=(e=this.restorationData)?e.scrollPosition:void 0,null!=t?(this.controller.scrollToPosition(t),!0):void 0},r.prototype.scrollToAnchor=function(){return null!=this.location.anchor?(this.controller.scrollToAnchor(this.location.anchor),!0):void 0},r.prototype.scrollToTop=function(){return this.controller.scrollToPosition({x:0,y:0})},r.prototype.recordTimingMetric=function(t){var e;return null!=(e=this.timingMetrics)[t]?e[t]:e[t]=(new Date).getTime()},r.prototype.getTimingMetrics=function(){return e.copyObject(this.timingMetrics)},n=function(t){switch(t){case"replace":return"replaceHistoryWithLocationAndRestorationIdentifier";case"advance":case"restore":return"pushHistoryWithLocationAndRestorationIdentifier"}},r.prototype.shouldIssueRequest=function(){return"restore"===this.action?!this.hasCachedSnapshot():!0},r.prototype.cacheSnapshot=function(){return this.snapshotCached?void 0:(this.controller.cacheSnapshot(),this.snapshotCached=!0)},r.prototype.render=function(t){return this.cancelRender(),this.frame=requestAnimationFrame(function(e){return function(){return e.frame=null,t.call(e)}}(this))},r.prototype.cancelRender=function(){return this.frame?cancelAnimationFrame(this.frame):void 0},r}()}.call(this),function(){var t=function(t,e){return function(){return t.apply(e,arguments)}};e.Controller=function(){function r(){this.clickBubbled=t(this.clickBubbled,this),this.clickCaptured=t(this.clickCaptured,this),this.pageLoaded=t(this.pageLoaded,this),this.history=new e.History(this),this.view=new e.View(this),this.scrollManager=new e.ScrollManager(this),this.restorationData={},this.clearCache(),this.setProgressBarDelay(500)}return r.prototype.start=function(){return e.supported&&!this.started?(addEventListener("click",this.clickCaptured,!0),addEventListener("DOMContentLoaded",this.pageLoaded,!1),this.scrollManager.start(),this.startHistory(),this.started=!0,this.enabled=!0):void 0},r.prototype.disable=function(){return this.enabled=!1},r.prototype.stop=function(){return this.started?(removeEventListener("click",this.clickCaptured,!0),removeEventListener("DOMContentLoaded",this.pageLoaded,!1),this.scrollManager.stop(),this.stopHistory(),this.started=!1):void 0},r.prototype.clearCache=function(){return this.cache=new e.SnapshotCache(10)},r.prototype.visit=function(t,r){var n,o;return null==r&&(r={}),t=e.Location.wrap(t),this.applicationAllowsVisitingLocation(t)?this.locationIsVisitable(t)?(n=null!=(o=r.action)?o:"advance",this.adapter.visitProposedToLocationWithAction(t,n)):window.location=t:void 0},r.prototype.startVisitToLocationWithAction=function(t,r,n){var o;return e.supported?(o=this.getRestorationDataForIdentifier(n),this.startVisit(t,r,{restorationData:o})):window.location=t},r.prototype.setProgressBarDelay=function(t){return this.progressBarDelay=t},r.prototype.startHistory=function(){return this.location=e.Location.wrap(window.location),this.restorationIdentifier=e.uuid(),this.history.start(),this.history.replace(this.location,this.restorationIdentifier)},r.prototype.stopHistory=function(){return this.history.stop()},r.prototype.pushHistoryWithLocationAndRestorationIdentifier=function(t,r){return this.restorationIdentifier=r,this.location=e.Location.wrap(t),this.history.push(this.location,this.restorationIdentifier)},r.prototype.replaceHistoryWithLocationAndRestorationIdentifier=function(t,r){return this.restorationIdentifier=r,this.location=e.Location.wrap(t),this.history.replace(this.location,this.restorationIdentifier)},r.prototype.historyPoppedToLocationWithRestorationIdentifier=function(t,r){var n;return this.restorationIdentifier=r,this.enabled?(n=this.getRestorationDataForIdentifier(this.restorationIdentifier),this.startVisit(t,"restore",{restorationIdentifier:this.restorationIdentifier,restorationData:n,historyChanged:!0}),this.location=e.Location.wrap(t)):this.adapter.pageInvalidated()},r.prototype.getCachedSnapshotForLocation=function(t){var e;return null!=(e=this.cache.get(t))?e.clone():void 0},r.prototype.shouldCacheSnapshot=function(){return this.view.getSnapshot().isCacheable();
},r.prototype.cacheSnapshot=function(){var t,r;return this.shouldCacheSnapshot()?(this.notifyApplicationBeforeCachingSnapshot(),r=this.view.getSnapshot(),t=this.lastRenderedLocation,e.defer(function(e){return function(){return e.cache.put(t,r.clone())}}(this))):void 0},r.prototype.scrollToAnchor=function(t){var e;return(e=this.view.getElementForAnchor(t))?this.scrollToElement(e):this.scrollToPosition({x:0,y:0})},r.prototype.scrollToElement=function(t){return this.scrollManager.scrollToElement(t)},r.prototype.scrollToPosition=function(t){return this.scrollManager.scrollToPosition(t)},r.prototype.scrollPositionChanged=function(t){var e;return e=this.getCurrentRestorationData(),e.scrollPosition=t},r.prototype.render=function(t,e){return this.view.render(t,e)},r.prototype.viewInvalidated=function(){return this.adapter.pageInvalidated()},r.prototype.viewWillRender=function(t){return this.notifyApplicationBeforeRender(t)},r.prototype.viewRendered=function(){return this.lastRenderedLocation=this.currentVisit.location,this.notifyApplicationAfterRender()},r.prototype.pageLoaded=function(){return this.lastRenderedLocation=this.location,this.notifyApplicationAfterPageLoad()},r.prototype.clickCaptured=function(){return removeEventListener("click",this.clickBubbled,!1),addEventListener("click",this.clickBubbled,!1)},r.prototype.clickBubbled=function(t){var e,r,n;return this.enabled&&this.clickEventIsSignificant(t)&&(r=this.getVisitableLinkForNode(t.target))&&(n=this.getVisitableLocationForLink(r))&&this.applicationAllowsFollowingLinkToLocation(r,n)?(t.preventDefault(),e=this.getActionForLink(r),this.visit(n,{action:e})):void 0},r.prototype.applicationAllowsFollowingLinkToLocation=function(t,e){var r;return r=this.notifyApplicationAfterClickingLinkToLocation(t,e),!r.defaultPrevented},r.prototype.applicationAllowsVisitingLocation=function(t){var e;return e=this.notifyApplicationBeforeVisitingLocation(t),!e.defaultPrevented},r.prototype.notifyApplicationAfterClickingLinkToLocation=function(t,r){return e.dispatch("turbolinks:click",{target:t,data:{url:r.absoluteURL},cancelable:!0})},r.prototype.notifyApplicationBeforeVisitingLocation=function(t){return e.dispatch("turbolinks:before-visit",{data:{url:t.absoluteURL},cancelable:!0})},r.prototype.notifyApplicationAfterVisitingLocation=function(t){return e.dispatch("turbolinks:visit",{data:{url:t.absoluteURL}})},r.prototype.notifyApplicationBeforeCachingSnapshot=function(){return e.dispatch("turbolinks:before-cache")},r.prototype.notifyApplicationBeforeRender=function(t){return e.dispatch("turbolinks:before-render",{data:{newBody:t}})},r.prototype.notifyApplicationAfterRender=function(){return e.dispatch("turbolinks:render")},r.prototype.notifyApplicationAfterPageLoad=function(t){return null==t&&(t={}),e.dispatch("turbolinks:load",{data:{url:this.location.absoluteURL,timing:t}})},r.prototype.startVisit=function(t,e,r){var n;return null!=(n=this.currentVisit)&&n.cancel(),this.currentVisit=this.createVisit(t,e,r),this.currentVisit.start(),this.notifyApplicationAfterVisitingLocation(t)},r.prototype.createVisit=function(t,r,n){var o,i,s,a,u;return i=null!=n?n:{},a=i.restorationIdentifier,s=i.restorationData,o=i.historyChanged,u=new e.Visit(this,t,r),u.restorationIdentifier=null!=a?a:e.uuid(),u.restorationData=e.copyObject(s),u.historyChanged=o,u.referrer=this.location,u},r.prototype.visitCompleted=function(t){return this.notifyApplicationAfterPageLoad(t.getTimingMetrics())},r.prototype.clickEventIsSignificant=function(t){return!(t.defaultPrevented||t.target.isContentEditable||t.which>1||t.altKey||t.ctrlKey||t.metaKey||t.shiftKey)},r.prototype.getVisitableLinkForNode=function(t){return this.nodeIsVisitable(t)?e.closest(t,"a[href]:not([target]):not([download])"):void 0},r.prototype.getVisitableLocationForLink=function(t){var r;return r=new e.Location(t.getAttribute("href")),this.locationIsVisitable(r)?r:void 0},r.prototype.getActionForLink=function(t){var e;return null!=(e=t.getAttribute("data-turbolinks-action"))?e:"advance"},r.prototype.nodeIsVisitable=function(t){var r;return(r=e.closest(t,"[data-turbolinks]"))?"false"!==r.getAttribute("data-turbolinks"):!0},r.prototype.locationIsVisitable=function(t){return t.isPrefixedBy(this.view.getRootLocation())&&t.isHTML()},r.prototype.getCurrentRestorationData=function(){return this.getRestorationDataForIdentifier(this.restorationIdentifier)},r.prototype.getRestorationDataForIdentifier=function(t){var e;return null!=(e=this.restorationData)[t]?e[t]:e[t]={}},r}()}.call(this),function(){!function(){var t,e;if((t=e=document.currentScript)&&!e.hasAttribute("data-turbolinks-suppress-warning"))for(;t=t.parentNode;)if(t===document.body)return console.warn("You are loading Turbolinks from a <script> element inside the <body> element. This is probably not what you meant to do!\n\nLoad your application\u2019s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.\n\nFor more information, see: https://github.com/turbolinks/turbolinks#working-with-script-elements\n\n\u2014\u2014\nSuppress this warning by adding a `data-turbolinks-suppress-warning` attribute to: %s",e.outerHTML)}()}.call(this),function(){var t,r,n;e.start=function(){return r()?(null==e.controller&&(e.controller=t()),e.controller.start()):void 0},r=function(){return null==window.Turbolinks&&(window.Turbolinks=e),n()},t=function(){var t;return t=new e.Controller,t.adapter=new e.BrowserAdapter(t),t},n=function(){return window.Turbolinks===e},n()&&e.start()}.call(this)}).call(this),"object"==typeof module&&module.exports?module.exports=e:"function"==typeof define&&define.amd&&define(e)}).call(this);
$(document).on('turbolinks:load', function() {

  $('#address_checkpoint').on('autocompletechange change', function (event, ui) {
    if(typeof ui !== 'undefined'){
      if(typeof ui.item !== 'undefined'){
        $('#checkpoint_id').val(ui.item.id);
        $.ajax({
          dataType: "json",
          cache: false,
          url: '/addresses/update_geocerca/' + ui.item.id,
          timeout: 5000,
          error: function(XMLHttpRequest, errorTextStatus, error) {
            console.log("Failed to submit : " + errorTextStatus + " ;" + error);
          },
          success: function(data) { // Clear all options from person_city_id select
            if (data.success){
              removePolyline();
              map.setCenter({lat: parseFloat(data.data[0].lat), lng: parseFloat(data.data[0].lng)});
              var polygonCoords = [];
              data.data.forEach(function(polypoint) {
                polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
              })
              var polygon = new google.maps.Polygon({
                paths: polygonCoords,
                strokeColor: '#000000',
                fillColor: '#FF0000',
                fillOpacity: 0.4,
                editable: true
              });
              currentPolygon = polygon;
              polygon.setMap(map);
              drawingManager.setDrawingMode(google.maps.drawing.OverlayType.CIRCLE);
              $("#reDraw").removeClass("hide");
              $("#address_polypoints").val(setPolypointsGeo(data.data));
            }
          }
        });
      }
    }
  }).change();


  $('#route_checkpoint_check').on('change', function () {
    val = $('#route_checkpoint_check').val();
    $.ajax({
      dataType: "json",
      cache: false,
      url: '/addresses/update_geocerca/' + val,
      timeout: 5000,
      error: function(XMLHttpRequest, errorTextStatus, error) {
        console.log("Failed to submit : " + errorTextStatus + " ;" + error);
      },
          success: function(data) { // Clear all options from person_city_id select
            if (data.success){
              removePolyline();
              map.setCenter({lat: parseFloat(data.data[0].lat), lng: parseFloat(data.data[0].lng)});
              var polygonCoords = [];
              data.data.forEach(function(polypoint) {
                polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
              })
              var polygon = new google.maps.Polygon({
                paths: polygonCoords,
                strokeColor: '#000000',
                fillColor: '#FF0000',
                fillOpacity: 0.4,
                editable: true
              });
              currentPolygon = polygon;
              polygon.setMap(map);
              // drawingManager.setDrawingMode(google.maps.drawing.OverlayType.CIRCLE);
              // $("#reDraw").removeClass("hide");
              $("#address_polypoints").val(setPolypointsGeo(data.data));
            }
          }
        });
  });


  $.widget( "custom.catcomplete", $.ui.autocomplete, {
    _renderMenu: function( ul, items ) {
      var self = this,
      currentCategory = "";
      $.each( items, function( index, item ) {
        if ( item.category != currentCategory ) {
          ul.append( "<li class='ui-autocomplete-category'>" + item.category + "</li>" );
          currentCategory = item.category;
        }
        self._renderItem( ul, item );
      });
    }
  });

  $(document).on('change', 'select.dep', function() {
    var id_value_string = $(this).val();
    var select = $('select.city');
    if (id_value_string == "") {
     $(select).children('option').remove();
     var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
     $(row).appendTo(select);
		} else {      //Send the request and update person_city_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/system/tables/cities/getcitiesbydepartment/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					alert("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { // Clear all options from person_city_id select
					if (data.success){
						$(select).children('option').remove();  //put in a empty default line
						var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
						$(row).appendTo(select);   // Fill person_city_id select
						$.each(data.data, function(i, j) {
							row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
							$(row).appendTo(select);
						});
					}
				}
			});
		}
	});

  if ($('#address_address_text').length) {
		// para habilitar el campo address_address_text
		$("#address_city_id").on('change', function() {
			if ( $("#address_city_id").val() != "") $('#address_address_text').removeAttr("disabled")
		});
		// Rellena el campo ciudad
		if ($("#address_department_id").val() != "") {
			var row = "<option value=\"" + cdad_id + "\" selected >" + cdad + "</option>";
			$(row).appendTo("#address_city_id");
		}
	}



  setTimeout(
    function() 
    {
     var value_checkpoint = $('#route_checkpoint_check').val();

     if (value_checkpoint != null){
      $.ajax({
        dataType: "json",
        cache: false,
        url: '/addresses/update_geocerca/' + value_checkpoint,
        timeout: 5000,
        error: function(XMLHttpRequest, errorTextStatus, error) {
          console.log("Failed to submit : " + errorTextStatus + " ;" + error);
        },
      success: function(data) { // Clear all options from person_city_id select
        if (data.success){
          removePolyline();
          map.setCenter({lat: parseFloat(data.data[0].lat), lng: parseFloat(data.data[0].lng)});
          var polygonCoords = [];
          data.data.forEach(function(polypoint) {
            polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
          })
          var polygon = new google.maps.Polygon({
            paths: polygonCoords,
            strokeColor: '#000000',
            fillColor: '#FF0000',
            fillOpacity: 0.4,
            editable: true
          });
          currentPolygon = polygon;
          polygon.setMap(map);
          // drawingManager.setDrawingMode(google.maps.drawing.OverlayType.CIRCLE);
          $("#reDraw").removeClass("hide");
          $("#address_polypoints").val(setPolypointsGeo(data.data));
        }
      }
    });
    }
  }, 1000);
});


function initializeAddresses() {
	function geocodeAddress(geocoder, resultsMap) {
		var address = $('#address_address_text').val()+", "+$('#address_city_id option:selected').text()+", "+$('#address_department_id option:selected').text();
		geocoder.geocode({'address': address}, function(results, status) {
      if (status === 'OK') {
       resultsMap.setCenter(results[0].geometry.location);
       marker = new google.maps.Marker({
         map: resultsMap,
         position: results[0].geometry.location,
         draggable: true
       });

       document.getElementById("address_lat").value = marker.getPosition().lat().toString();
       document.getElementById("address_lng").value = marker.getPosition().lng().toString();

       google.maps.event.addListener(marker,'dragend',function(event) {
        document.getElementById("address_lat").value = this.getPosition().lat().toString();
        document.getElementById("address_lng").value = this.getPosition().lng().toString();
      });
     } else {
       alert('Geocode was not successful for the following reason: ' + status);
     }
   });
	}
	var geocoder = new google.maps.Geocoder();
  if (document.getElementById('address_address_text') != null ){
    document.getElementById('address_address_text').addEventListener('change', function() {
      geocodeAddress(geocoder, map);
    });
  }

}

// https://developers.google.com/maps/documentation/javascript/examples/drawing-tools
var map, drawingManager, currentPolygon, currentCircle;

function initMap() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: parseFloat($("#address_lat").val()) || 4.678457, lng: parseFloat($("#address_lng").val()) || -74.059232},
    zoom: 18
  });

  drawingManager = new google.maps.drawing.DrawingManager({
    drawingMode: google.maps.drawing.OverlayType.POLYGON,
    drawingControl: false,
    circleOptions: {
    	fillColor: '#b4b4ff',
    	fillOpacity: 0.4,
    	strokeColor: "#000000",
    	strokeWeight: 3,
    	clickable: false,
    	editable: true,
    	zIndex: 1
    },
    polygonOptions: {
    	fillColor: '#FF0000',
    	fillOpacity: 0.4,
    	strokeColor: "#000000",
    	strokeWeight: 3,
    	clickable: false,
    	editable: true
    }
  });
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setMap(map);
  }

  if ($("#address_radio_distance_m").val() && $("#address_lat").val() && $("#address_polypoints").val()) {
  	drawingManager.setDrawingMode(null);
    $("#reDraw").removeClass("hide");

    var polygonCoords = [];
    JSON.parse($("#address_polypoints").val()).forEach(function(polypoint) {
      polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
    })
    var polygon = new google.maps.Polygon({
     paths: polygonCoords,
     strokeColor: '#000000',
     fillColor: '#FF0000',
     fillOpacity: 0.4,
     editable: true
   });
    currentPolygon = polygon;
    google.maps.event.addListener(polygon.getPath(), 'set_at', function() {
     $("#address_polypoints").val(setPolypoints(polygon.getPath()));
   });
    google.maps.event.addListener(polygon.getPath(), 'insert_at', function() {
     $("#address_polypoints").val(setPolypoints(polygon.getPath()));
   });
    polygon.setMap(map);

    var center = new google.maps.LatLng($("#address_lat").val(), $("#address_lng").val());
    var circle = new google.maps.Circle({
     center: center,
     map: map,
     radius: parseInt($("#address_radio_distance_m").val()),
     fillColor: '#b4b4ff',
     fillOpacity: 0.4,
     strokeColor: "#000000",
     editable: true
   });
    currentCircle = circle;
    google.maps.event.addListener(circle, 'radius_changed', function(){
      $("#address_radio_distance_m").val(parseInt(this.getRadius()));
    })
    google.maps.event.addListener(circle, 'center_changed', function(){
      $("#address_lat").val(this.getCenter().lat().toString());
      $("#address_lng").val(this.getCenter().lng().toString());
    })
  }
  google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
  	if (event.type == 'polygon') {
  		drawingManager.setDrawingMode(google.maps.drawing.OverlayType.CIRCLE);
  		$("#reDraw").removeClass("hide");
  		$("#address_polypoints").val(setPolypoints(event.overlay.getPath()));
  		currentPolygon = event.overlay;
      google.maps.event.addListener(event.overlay.getPath(), 'set_at', function() {
        $("#address_polypoints").val(setPolypoints(event.overlay.getPath()));
      });
      google.maps.event.addListener(event.overlay.getPath(), 'insert_at', function() {
        $("#address_polypoints").val(setPolypoints(event.overlay.getPath()));
      });
    }else if (event.type == 'circle') {
      $("#address_lat").val(event.overlay.getCenter().lat().toString());
      $("#address_lng").val(event.overlay.getCenter().lng().toString())
      $("#reDraw").removeClass("hide");
      $("#address_radio_distance_m").val(parseInt(event.overlay.getRadius()));
      drawingManager.setDrawingMode(null);
      currentCircle = event.overlay;
      google.maps.event.addListener(event.overlay, 'radius_changed', function(){
       $("#address_radio_distance_m").val(parseInt(this.getRadius()));
     })
      google.maps.event.addListener(event.overlay, 'center_changed', function(){
        $("#address_lat").val(this.getCenter().lat().toString());
        $("#address_lng").val(this.getCenter().lng().toString());
      })
    }
  });
  google.maps.event.addDomListener(window, 'load', initializeAddresses);
}

function removePolyline() {
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
  }
  if (currentPolygon) {
    currentPolygon.setMap(null);
    currentPolygon = null;
  }
  if (currentCircle) {
    currentCircle.setMap(null);
    currentCircle = null;
  }
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setMap(map);
  }
  $("#reDraw").addClass("hide");
}

function getBoundsForPoly(poly) {
	var bounds = new google.maps.LatLngBounds;
	poly.getPath().forEach(function(latLng) {
		bounds.extend(latLng);
	});
	return bounds;
}

function setPolypoints(path){
	var polypoints = [];
	path.forEach(function(element) {
    polypoints.push('{"lat": "' + element.lat() + '", "lng": "' + element.lng() + '"}');
  })
  return '['+polypoints.toString()+']';
}

function setPolypointsGeo(path){
  var polypoints = [];
  $.each(path, function(k, element) {
    polypoints.push('{"lat": "' + element.lat + '", "lng": "' + element.lng + '"}');
  });
  return '['+polypoints.toString()+']';
}

function validPolyline(){
  if ($("#address_status").val() == true){
    if (!currentPolygon || !currentCircle || !$("#address_polypoints").val() || !document.getElementById("address_lat").value || !document.getElementById("address_lng").value){
      alert('Debe agregar una polígono en el mapa para continuar');
      return false;
    }
  }
}

;
(function() {


}).call(this);
$(document).ready(function() {
	$('.checkbox-big-customer-indicator').change(function() {
		var is_add = false
		if (this.checked) { is_add = true }
		$.ajax({
			url: 'bigcustomer_indicators',
			type: "POST",
        	data: { bigcustomer_id: $("#bigcustomer_id").val(), indicator_id: this.id, is_add: is_add },
        	success: function(result) {
        		if (result.success == false) {
        			alert(result.message);
        		}
        	}
        });
	});
});
(function() {
  var context = this;

  (function() {
    (function() {
      var slice = [].slice;

      this.ActionCable = {
        INTERNAL: {
          "message_types": {
            "welcome": "welcome",
            "ping": "ping",
            "confirmation": "confirm_subscription",
            "rejection": "reject_subscription"
          },
          "default_mount_path": "/cable",
          "protocols": ["actioncable-v1-json", "actioncable-unsupported"]
        },
        WebSocket: window.WebSocket,
        logger: window.console,
        createConsumer: function(url) {
          var ref;
          if (url == null) {
            url = (ref = this.getConfig("url")) != null ? ref : this.INTERNAL.default_mount_path;
          }
          return new ActionCable.Consumer(this.createWebSocketURL(url));
        },
        getConfig: function(name) {
          var element;
          element = document.head.querySelector("meta[name='action-cable-" + name + "']");
          return element != null ? element.getAttribute("content") : void 0;
        },
        createWebSocketURL: function(url) {
          var a;
          if (url && !/^wss?:/i.test(url)) {
            a = document.createElement("a");
            a.href = url;
            a.href = a.href;
            a.protocol = a.protocol.replace("http", "ws");
            return a.href;
          } else {
            return url;
          }
        },
        startDebugging: function() {
          return this.debugging = true;
        },
        stopDebugging: function() {
          return this.debugging = null;
        },
        log: function() {
          var messages, ref;
          messages = 1 <= arguments.length ? slice.call(arguments, 0) : [];
          if (this.debugging) {
            messages.push(Date.now());
            return (ref = this.logger).log.apply(ref, ["[ActionCable]"].concat(slice.call(messages)));
          }
        }
      };

    }).call(this);
  }).call(context);

  var ActionCable = context.ActionCable;

  (function() {
    (function() {
      var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };

      ActionCable.ConnectionMonitor = (function() {
        var clamp, now, secondsSince;

        ConnectionMonitor.pollInterval = {
          min: 3,
          max: 30
        };

        ConnectionMonitor.staleThreshold = 6;

        function ConnectionMonitor(connection) {
          this.connection = connection;
          this.visibilityDidChange = bind(this.visibilityDidChange, this);
          this.reconnectAttempts = 0;
        }

        ConnectionMonitor.prototype.start = function() {
          if (!this.isRunning()) {
            this.startedAt = now();
            delete this.stoppedAt;
            this.startPolling();
            document.addEventListener("visibilitychange", this.visibilityDidChange);
            return ActionCable.log("ConnectionMonitor started. pollInterval = " + (this.getPollInterval()) + " ms");
          }
        };

        ConnectionMonitor.prototype.stop = function() {
          if (this.isRunning()) {
            this.stoppedAt = now();
            this.stopPolling();
            document.removeEventListener("visibilitychange", this.visibilityDidChange);
            return ActionCable.log("ConnectionMonitor stopped");
          }
        };

        ConnectionMonitor.prototype.isRunning = function() {
          return (this.startedAt != null) && (this.stoppedAt == null);
        };

        ConnectionMonitor.prototype.recordPing = function() {
          return this.pingedAt = now();
        };

        ConnectionMonitor.prototype.recordConnect = function() {
          this.reconnectAttempts = 0;
          this.recordPing();
          delete this.disconnectedAt;
          return ActionCable.log("ConnectionMonitor recorded connect");
        };

        ConnectionMonitor.prototype.recordDisconnect = function() {
          this.disconnectedAt = now();
          return ActionCable.log("ConnectionMonitor recorded disconnect");
        };

        ConnectionMonitor.prototype.startPolling = function() {
          this.stopPolling();
          return this.poll();
        };

        ConnectionMonitor.prototype.stopPolling = function() {
          return clearTimeout(this.pollTimeout);
        };

        ConnectionMonitor.prototype.poll = function() {
          return this.pollTimeout = setTimeout((function(_this) {
            return function() {
              _this.reconnectIfStale();
              return _this.poll();
            };
          })(this), this.getPollInterval());
        };

        ConnectionMonitor.prototype.getPollInterval = function() {
          var interval, max, min, ref;
          ref = this.constructor.pollInterval, min = ref.min, max = ref.max;
          interval = 5 * Math.log(this.reconnectAttempts + 1);
          return Math.round(clamp(interval, min, max) * 1000);
        };

        ConnectionMonitor.prototype.reconnectIfStale = function() {
          if (this.connectionIsStale()) {
            ActionCable.log("ConnectionMonitor detected stale connection. reconnectAttempts = " + this.reconnectAttempts + ", pollInterval = " + (this.getPollInterval()) + " ms, time disconnected = " + (secondsSince(this.disconnectedAt)) + " s, stale threshold = " + this.constructor.staleThreshold + " s");
            this.reconnectAttempts++;
            if (this.disconnectedRecently()) {
              return ActionCable.log("ConnectionMonitor skipping reopening recent disconnect");
            } else {
              ActionCable.log("ConnectionMonitor reopening");
              return this.connection.reopen();
            }
          }
        };

        ConnectionMonitor.prototype.connectionIsStale = function() {
          var ref;
          return secondsSince((ref = this.pingedAt) != null ? ref : this.startedAt) > this.constructor.staleThreshold;
        };

        ConnectionMonitor.prototype.disconnectedRecently = function() {
          return this.disconnectedAt && secondsSince(this.disconnectedAt) < this.constructor.staleThreshold;
        };

        ConnectionMonitor.prototype.visibilityDidChange = function() {
          if (document.visibilityState === "visible") {
            return setTimeout((function(_this) {
              return function() {
                if (_this.connectionIsStale() || !_this.connection.isOpen()) {
                  ActionCable.log("ConnectionMonitor reopening stale connection on visibilitychange. visbilityState = " + document.visibilityState);
                  return _this.connection.reopen();
                }
              };
            })(this), 200);
          }
        };

        now = function() {
          return new Date().getTime();
        };

        secondsSince = function(time) {
          return (now() - time) / 1000;
        };

        clamp = function(number, min, max) {
          return Math.max(min, Math.min(max, number));
        };

        return ConnectionMonitor;

      })();

    }).call(this);
    (function() {
      var i, message_types, protocols, ref, supportedProtocols, unsupportedProtocol,
        slice = [].slice,
        bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
        indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

      ref = ActionCable.INTERNAL, message_types = ref.message_types, protocols = ref.protocols;

      supportedProtocols = 2 <= protocols.length ? slice.call(protocols, 0, i = protocols.length - 1) : (i = 0, []), unsupportedProtocol = protocols[i++];

      ActionCable.Connection = (function() {
        Connection.reopenDelay = 500;

        function Connection(consumer) {
          this.consumer = consumer;
          this.open = bind(this.open, this);
          this.subscriptions = this.consumer.subscriptions;
          this.monitor = new ActionCable.ConnectionMonitor(this);
          this.disconnected = true;
        }

        Connection.prototype.send = function(data) {
          if (this.isOpen()) {
            this.webSocket.send(JSON.stringify(data));
            return true;
          } else {
            return false;
          }
        };

        Connection.prototype.open = function() {
          if (this.isActive()) {
            ActionCable.log("Attempted to open WebSocket, but existing socket is " + (this.getState()));
            return false;
          } else {
            ActionCable.log("Opening WebSocket, current state is " + (this.getState()) + ", subprotocols: " + protocols);
            if (this.webSocket != null) {
              this.uninstallEventHandlers();
            }
            this.webSocket = new ActionCable.WebSocket(this.consumer.url, protocols);
            this.installEventHandlers();
            this.monitor.start();
            return true;
          }
        };

        Connection.prototype.close = function(arg) {
          var allowReconnect, ref1;
          allowReconnect = (arg != null ? arg : {
            allowReconnect: true
          }).allowReconnect;
          if (!allowReconnect) {
            this.monitor.stop();
          }
          if (this.isActive()) {
            return (ref1 = this.webSocket) != null ? ref1.close() : void 0;
          }
        };

        Connection.prototype.reopen = function() {
          var error;
          ActionCable.log("Reopening WebSocket, current state is " + (this.getState()));
          if (this.isActive()) {
            try {
              return this.close();
            } catch (error1) {
              error = error1;
              return ActionCable.log("Failed to reopen WebSocket", error);
            } finally {
              ActionCable.log("Reopening WebSocket in " + this.constructor.reopenDelay + "ms");
              setTimeout(this.open, this.constructor.reopenDelay);
            }
          } else {
            return this.open();
          }
        };

        Connection.prototype.getProtocol = function() {
          var ref1;
          return (ref1 = this.webSocket) != null ? ref1.protocol : void 0;
        };

        Connection.prototype.isOpen = function() {
          return this.isState("open");
        };

        Connection.prototype.isActive = function() {
          return this.isState("open", "connecting");
        };

        Connection.prototype.isProtocolSupported = function() {
          var ref1;
          return ref1 = this.getProtocol(), indexOf.call(supportedProtocols, ref1) >= 0;
        };

        Connection.prototype.isState = function() {
          var ref1, states;
          states = 1 <= arguments.length ? slice.call(arguments, 0) : [];
          return ref1 = this.getState(), indexOf.call(states, ref1) >= 0;
        };

        Connection.prototype.getState = function() {
          var ref1, state, value;
          for (state in WebSocket) {
            value = WebSocket[state];
            if (value === ((ref1 = this.webSocket) != null ? ref1.readyState : void 0)) {
              return state.toLowerCase();
            }
          }
          return null;
        };

        Connection.prototype.installEventHandlers = function() {
          var eventName, handler;
          for (eventName in this.events) {
            handler = this.events[eventName].bind(this);
            this.webSocket["on" + eventName] = handler;
          }
        };

        Connection.prototype.uninstallEventHandlers = function() {
          var eventName;
          for (eventName in this.events) {
            this.webSocket["on" + eventName] = function() {};
          }
        };

        Connection.prototype.events = {
          message: function(event) {
            var identifier, message, ref1, type;
            if (!this.isProtocolSupported()) {
              return;
            }
            ref1 = JSON.parse(event.data), identifier = ref1.identifier, message = ref1.message, type = ref1.type;
            switch (type) {
              case message_types.welcome:
                this.monitor.recordConnect();
                return this.subscriptions.reload();
              case message_types.ping:
                return this.monitor.recordPing();
              case message_types.confirmation:
                return this.subscriptions.notify(identifier, "connected");
              case message_types.rejection:
                return this.subscriptions.reject(identifier);
              default:
                return this.subscriptions.notify(identifier, "received", message);
            }
          },
          open: function() {
            ActionCable.log("WebSocket onopen event, using '" + (this.getProtocol()) + "' subprotocol");
            this.disconnected = false;
            if (!this.isProtocolSupported()) {
              ActionCable.log("Protocol is unsupported. Stopping monitor and disconnecting.");
              return this.close({
                allowReconnect: false
              });
            }
          },
          close: function(event) {
            ActionCable.log("WebSocket onclose event");
            if (this.disconnected) {
              return;
            }
            this.disconnected = true;
            this.monitor.recordDisconnect();
            return this.subscriptions.notifyAll("disconnected", {
              willAttemptReconnect: this.monitor.isRunning()
            });
          },
          error: function() {
            return ActionCable.log("WebSocket onerror event");
          }
        };

        return Connection;

      })();

    }).call(this);
    (function() {
      var slice = [].slice;

      ActionCable.Subscriptions = (function() {
        function Subscriptions(consumer) {
          this.consumer = consumer;
          this.subscriptions = [];
        }

        Subscriptions.prototype.create = function(channelName, mixin) {
          var channel, params, subscription;
          channel = channelName;
          params = typeof channel === "object" ? channel : {
            channel: channel
          };
          subscription = new ActionCable.Subscription(this.consumer, params, mixin);
          return this.add(subscription);
        };

        Subscriptions.prototype.add = function(subscription) {
          this.subscriptions.push(subscription);
          this.consumer.ensureActiveConnection();
          this.notify(subscription, "initialized");
          this.sendCommand(subscription, "subscribe");
          return subscription;
        };

        Subscriptions.prototype.remove = function(subscription) {
          this.forget(subscription);
          if (!this.findAll(subscription.identifier).length) {
            this.sendCommand(subscription, "unsubscribe");
          }
          return subscription;
        };

        Subscriptions.prototype.reject = function(identifier) {
          var i, len, ref, results, subscription;
          ref = this.findAll(identifier);
          results = [];
          for (i = 0, len = ref.length; i < len; i++) {
            subscription = ref[i];
            this.forget(subscription);
            this.notify(subscription, "rejected");
            results.push(subscription);
          }
          return results;
        };

        Subscriptions.prototype.forget = function(subscription) {
          var s;
          this.subscriptions = (function() {
            var i, len, ref, results;
            ref = this.subscriptions;
            results = [];
            for (i = 0, len = ref.length; i < len; i++) {
              s = ref[i];
              if (s !== subscription) {
                results.push(s);
              }
            }
            return results;
          }).call(this);
          return subscription;
        };

        Subscriptions.prototype.findAll = function(identifier) {
          var i, len, ref, results, s;
          ref = this.subscriptions;
          results = [];
          for (i = 0, len = ref.length; i < len; i++) {
            s = ref[i];
            if (s.identifier === identifier) {
              results.push(s);
            }
          }
          return results;
        };

        Subscriptions.prototype.reload = function() {
          var i, len, ref, results, subscription;
          ref = this.subscriptions;
          results = [];
          for (i = 0, len = ref.length; i < len; i++) {
            subscription = ref[i];
            results.push(this.sendCommand(subscription, "subscribe"));
          }
          return results;
        };

        Subscriptions.prototype.notifyAll = function() {
          var args, callbackName, i, len, ref, results, subscription;
          callbackName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
          ref = this.subscriptions;
          results = [];
          for (i = 0, len = ref.length; i < len; i++) {
            subscription = ref[i];
            results.push(this.notify.apply(this, [subscription, callbackName].concat(slice.call(args))));
          }
          return results;
        };

        Subscriptions.prototype.notify = function() {
          var args, callbackName, i, len, results, subscription, subscriptions;
          subscription = arguments[0], callbackName = arguments[1], args = 3 <= arguments.length ? slice.call(arguments, 2) : [];
          if (typeof subscription === "string") {
            subscriptions = this.findAll(subscription);
          } else {
            subscriptions = [subscription];
          }
          results = [];
          for (i = 0, len = subscriptions.length; i < len; i++) {
            subscription = subscriptions[i];
            results.push(typeof subscription[callbackName] === "function" ? subscription[callbackName].apply(subscription, args) : void 0);
          }
          return results;
        };

        Subscriptions.prototype.sendCommand = function(subscription, command) {
          var identifier;
          identifier = subscription.identifier;
          return this.consumer.send({
            command: command,
            identifier: identifier
          });
        };

        return Subscriptions;

      })();

    }).call(this);
    (function() {
      ActionCable.Subscription = (function() {
        var extend;

        function Subscription(consumer, params, mixin) {
          this.consumer = consumer;
          if (params == null) {
            params = {};
          }
          this.identifier = JSON.stringify(params);
          extend(this, mixin);
        }

        Subscription.prototype.perform = function(action, data) {
          if (data == null) {
            data = {};
          }
          data.action = action;
          return this.send(data);
        };

        Subscription.prototype.send = function(data) {
          return this.consumer.send({
            command: "message",
            identifier: this.identifier,
            data: JSON.stringify(data)
          });
        };

        Subscription.prototype.unsubscribe = function() {
          return this.consumer.subscriptions.remove(this);
        };

        extend = function(object, properties) {
          var key, value;
          if (properties != null) {
            for (key in properties) {
              value = properties[key];
              object[key] = value;
            }
          }
          return object;
        };

        return Subscription;

      })();

    }).call(this);
    (function() {
      ActionCable.Consumer = (function() {
        function Consumer(url) {
          this.url = url;
          this.subscriptions = new ActionCable.Subscriptions(this);
          this.connection = new ActionCable.Connection(this);
        }

        Consumer.prototype.send = function(data) {
          return this.connection.send(data);
        };

        Consumer.prototype.connect = function() {
          return this.connection.open();
        };

        Consumer.prototype.disconnect = function() {
          return this.connection.close({
            allowReconnect: false
          });
        };

        Consumer.prototype.ensureActiveConnection = function() {
          if (!this.connection.isActive()) {
            return this.connection.open();
          }
        };

        return Consumer;

      })();

    }).call(this);
  }).call(this);

  if (typeof module === "object" && module.exports) {
    module.exports = ActionCable;
  } else if (typeof define === "function" && define.amd) {
    define(ActionCable);
  }
}).call(this);
// Action Cable provides the framework to deal with WebSockets in Rails.
// You can generate new channels where WebSocket features live using the `rails generate channel` command.
//




(function() {
  this.App || (this.App = {});

  App.cable = ActionCable.createConsumer();

}).call(this);
(function() {
  $(document).ready(function() {
    jQuery(function() {});
    return $('#carcolors').dataTable({
      bJQueryUI: true,
      bProcessing: true,
      bServerSide: true,
      responsive: true,
      ajax: {
        source: "/system/tables/carcolors.json",
        headers: {
          Authorization: 'Bearer ' + $('#carcolors').attr('data-toc')
        }
      },
      oLanguage: {
        "sUrl": "/assets/datatables/datatables_spanish.json"
      }
    });
  });

}).call(this);
$( document ).on('turbolinks:load', function() {

    $('#cards_active').DataTable( {
        "processing": true,
        "serverSide": true,
        "pageLength": 25,
        "cache": false,
        "language": { "url": "/assets/datatables/datatables_spanish.json" },
        "ajax": {
            'url': "/cards/list/1.json",
            'type': 'GET',
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+$('#cards_active').attr('data-toc'));
            }
        }
    } );

    $('#cards_inactive').DataTable( {
        "processing": true,
        "serverSide": true,
        "pageLength": 25,
        "cache": false,
        "language": { "url": "/assets/datatables/datatables_spanish.json" },
        "ajax": {
            'url': "/cards/list/2.json",
            'type': 'GET',
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+$('#cards_inactive').attr('data-toc'));
            }
        }
    } );


    $('#card_driver').on('autocompletechange change', function (event, ui) {
        if(typeof ui !== 'undefined'){
            if(typeof ui.item !== 'undefined'){
                $('#driver_id').val(ui.item.id);
            }
        }
    }).change();

    $.widget( "custom.catcomplete", $.ui.autocomplete, {
        _renderMenu: function( ul, items ) {
            var self = this,
            currentCategory = "";
            $.each( items, function( index, item ) {
                if ( item.category != currentCategory ) {
                    ul.append( "<li class='ui-autocomplete-category'>" + item.category + "</li>" );
                    currentCategory = item.category;
                }
                self._renderItem( ul, item );
            });
        }
    });

    $(".toggle-password").on('click', function() {
        var input = $("#card_cvc, #transaction_password")
        if (input.attr("type") == "password") {
            $(this).removeClass("glyphicon glyphicon-eye-open");
            $(this).addClass("glyphicon glyphicon-eye-close");
            input.attr("type", "text");
        } else {
            $(this).removeClass("glyphicon glyphicon-eye-close");
            $(this).addClass("glyphicon glyphicon-eye-open");
            input.attr("type", "password");
        }
    });

    $(".toggle-password2").on('click', function() {
        var input = $("#password_received")
        if (input.attr("type") == "password") {
            $(this).removeClass("glyphicon glyphicon-eye-open");
            $(this).addClass("glyphicon glyphicon-eye-close");
            input.attr("type", "text");
        } else {
            $(this).removeClass("glyphicon glyphicon-eye-close");
            $(this).addClass("glyphicon glyphicon-eye-open");
            input.attr("type", "password");
        }
    });

    $("#transaction_type").on('change', function(){
        val = $(this).val()
        if (val == 1){
            $("#type_advance").removeClass("ocultar");           
            $("#request").removeClass("ocultar");
            $("#value_advance").addClass("ocultar");
            $("#advance_type").val(''); 
        }
        else if (val == 3){
            $("#type_advance").addClass("ocultar");           
            $("#request").removeClass("ocultar"); 
            $("#value_advance").removeClass("ocultar");          
        }
        else if (val == 4){
            $("#request").removeClass("ocultar");
            $("#value_advance").removeClass("ocultar");
            $("#advance_type").val(''); 
            $("#type_advance").addClass("ocultar");      
        }
        else if (val == 5 || val == 10){
            $("#request").removeClass("ocultar");
            $("#value_advance").removeClass("ocultar");
            $("#advance_type").val(''); 
            $("#type_advance").addClass("ocultar");      
        }
        else if (val == 6){
            $("#request").removeClass("ocultar");
            $("#type_advance").addClass("ocultar");
            $("#value_advance").removeClass("ocultar");
        }
        else if (val == 8){
            $("#type_advance").removeClass("ocultar");     
            $("#request").removeClass("ocultar");
            $("#value_advance").addClass("ocultar");
            $("#advance_type").val('');     
        }
        else {
           $("#type_advance").addClass("ocultar");
           $("#request").addClass("ocultar");
           $("#value_advance").addClass("ocultar"); 
       }
   })

    $("#advance_type").on('change', function(){
        val = $(this).val()
        if (val == 1){
            $("#value_advance").addClass("ocultar");    
        }
        else {
            $("#value_advance").removeClass("ocultar");           
        }
    })

 
   //Get the value of Start and End of Week
  $('#DateWeek').on('dp.change', function (e) {
      var value = $("#weeklyDatePicker").val();
      var firstDate = moment(value, "YYYY-MM-DD").day(1).format("YYYY-MM-DD");
      var lastDate =  moment(value, "YYYY-MM-DD").day(7).format("YYYY-MM-DD");
      $("#weeklyDatePicker").val(firstDate + " - " + lastDate);
      send_date("/cards/get_sum_transaction_specific_week/",value,".value_week");
  });



    $(function () {
        $('#change_select_day').datetimepicker({ locale: 'es', collapse: false, format: "YYYY-MM-DD" }).on('dp.change', function (e) {
            send_date("/cards/get_sum_transaction_specific_day/",$("#day_selected").val(),".value_day");
        });
    });

    $(function () {
        $('#change_select_month').datetimepicker({ locale: 'es', collapse: false, format: "YYYY-MM" }).on('dp.change', function (e) {
            send_date("/cards/get_sum_transaction_specific_month/",$("#month_selected").val(),".value_month");
        });
    });

    $(function () {
        $('#change_select_year').datetimepicker({ locale: 'es', collapse: false, format: "YYYY" }).on('dp.change', function (e) {
            send_date("/cards/get_sum_transaction_specific_year/",$("#year_selected").val(),".value_year");
        });
    });

    function send_date(url, date, class_show){
        $.ajax(url + date, {
          type: 'GET',
          dataType: 'json',
          beforeSend: function(request) {
            return request.setRequestHeader('Authorization', 'Bearer ' + data_toc);
          },
          success: function(data, textStatus) {
            if (data.success){
                data = data.data;
                $(class_show).text(data["total"])
            }
          }
        });
    }

    if ($("#NextBathHour").length > 0){
        var netx_batch_hour = function(){
        var this1 = $("#NextBathHour");
        $.ajax("/cards/send_next_batch.json", {
                  type: 'GET',
                  dataType: 'json',
                  success: function(data) {
                    $(this1).html('Próximo lote se envía a las: '+data.data);
                }
            })
        }
        $(function(){
            setInterval(netx_batch_hour, (60 * 1  * 1000));
        });
    }

});
$(document).on('turbolinks:load', function() {
  setTimeout(
    function() 
    {
      if (typeof polypoints !== "undefined" && polypoints !== null) {
        if (polypoints !== "") {
          removePolyline();
          map.setCenter({lat: parseFloat(polypoints[0].lat), lng: parseFloat(polypoints[0].lng)});
          map.setZoom(15);
          var polygonCoords = [];
          polypoints.forEach(function(polypoint) {
            polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
          })
          var polygon = new google.maps.Polygon({
            paths: polygonCoords,
            strokeColor: '#000000',
            fillColor: '#FF0000',
            fillOpacity: 0.4,
            editable: true
          });
          currentPolygon = polygon;
          polygon.setMap(map);
          // drawingManager.setDrawingMode(google.maps.drawing.OverlayType.CIRCLE);
          $("#reDraw").removeClass("hide");
          drawingManager.setDrawingMode(null);
          $("#checkpoint_route_point").val(setPolypointsGeo(polypoints));
        }
      }

    }, 1000);

});

// https://developers.google.com/maps/documentation/javascript/examples/drawing-tools
var map, drawingManager, currentPolygon;

function initMapCheckpoint() {
  map = new google.maps.Map(document.getElementById('map_checkpoint'), {
    center: {lat: parseFloat($("#address_lat").val()) || 4.678457, lng: parseFloat($("#address_lng").val()) || -74.059232},
    zoom: 18
  });

  drawingManager = new google.maps.drawing.DrawingManager({
    drawingMode: google.maps.drawing.OverlayType.POLYGON,
    drawingControl: false,
    polygonOptions: {
    	fillColor: '#FF0000',
    	fillOpacity: 0.4,
    	strokeColor: "#000000",
    	strokeWeight: 3,
    	clickable: false,
    	editable: true
    }
  });
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setMap(map);
  }

  if ($("#address_lat").val() && $("#checkpoint_route_point").val()) {
  	drawingManager.setDrawingMode(null);
    $("#reDraw").removeClass("hide");

    var polygonCoords = [];
    JSON.parse($("#checkpoint_route_point").val()).forEach(function(polypoint) {
      polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
    })
    var polygon = new google.maps.Polygon({
     paths: polygonCoords,
     strokeColor: '#000000',
     fillColor: '#FF0000',
     fillOpacity: 0.4,
     editable: true
   });
    currentPolygon = polygon;
    google.maps.event.addListener(polygon.getPath(), 'set_at', function() {
     $("#checkpoint_route_point").val(setPolypoints(polygon.getPath()));
   });
    google.maps.event.addListener(polygon.getPath(), 'insert_at', function() {
     $("#checkpoint_route_point").val(setPolypoints(polygon.getPath()));
   });
    polygon.setMap(map);


  }
  google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
  	if (event.type == 'polygon') {
  		$("#reDraw").removeClass("hide");
  		$("#checkpoint_route_point").val(setPolypoints(event.overlay.getPath()));
  		currentPolygon = event.overlay;
      google.maps.event.addListener(event.overlay.getPath(), 'set_at', function() {
        $("#checkpoint_route_point").val(setPolypoints(event.overlay.getPath()));
      });
      google.maps.event.addListener(event.overlay.getPath(), 'insert_at', function() {
        $("#checkpoint_route_point").val(setPolypoints(event.overlay.getPath()));
      });
      drawingManager.setDrawingMode(null);
    }
  });
  google.maps.event.addDomListener(window, 'load', initializeReverseAddress);
}

function removePolyline() {
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
  }
  if (currentPolygon) {
    currentPolygon.setMap(null);
    currentPolygon = null;
  }

  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setMap(map);
  }
  $("#reDraw").addClass("hide");
}

function getBoundsForPoly(poly) {
	var bounds = new google.maps.LatLngBounds;
	poly.getPath().forEach(function(latLng) {
		bounds.extend(latLng);
	});
	return bounds;
}

function setPolypoints(path){
	var polypoints = [];
	path.forEach(function(element) {
    polypoints.push('{"lat": "' + element.lat() + '", "lng": "' + element.lng() + '"}');
  })
  return '['+polypoints.toString()+']';
}

function setPolypointsGeo(path){
  var polypoints = [];
  $.each(path, function(k, element) {
    polypoints.push('{"lat": "' + element.lat + '", "lng": "' + element.lng + '"}');
  });
  return '['+polypoints.toString()+']';
}

// function validPolyline(){
//   if ($("#address_status").val() == true){
//     if (!currentPolygon || !$("#checkpoint_route_point").val()){
//       alert('Debe agregar una polígono en el mapa para continuar');
//       return false;
//     }
//   }
// }

function initializeReverseAddress() {
  function geocodeAddress(geocoder, resultsMap) {
    var address = $('#checkpoint_address_text').val()+", "+$('#checkpoint_city_id option:selected').text()+", "+$('#department_id option:selected').text();
    geocoder.geocode({'address': address}, function(results, status) {
      if (status === 'OK') {
       resultsMap.setCenter(results[0].geometry.location);
       marker = new google.maps.Marker({
         map: resultsMap,
         position: results[0].geometry.location,
         draggable: true
       });

       document.getElementById("checkpoint_lat").value = marker.getPosition().lat().toString();
       document.getElementById("checkpoint_lng").value = marker.getPosition().lng().toString();

       google.maps.event.addListener(marker,'dragend',function(event) {
        document.getElementById("checkpoint_lat").value = this.getPosition().lat().toString();
        document.getElementById("checkpoint_lng").value = this.getPosition().lng().toString();
      });
     } else {
       alert('Geocode was not successful for the following reason: ' + status);
     }
   });
  }
  var geocoder = new google.maps.Geocoder();
  if (document.getElementById('checkpoint_address_text') != null ){
    document.getElementById('checkpoint_address_text').addEventListener('change', function() {
      geocodeAddress(geocoder, map);
    });
  }


  if ((document.getElementById('checkpoint_lng') != null )&&(document.getElementById('checkpoint_lat') != null )){
    document.getElementById('checkpoint_lng').addEventListener('change', function() {
      map.setCenter({lat: parseFloat(document.getElementById('checkpoint_lat').value), lng: parseFloat(document.getElementById('checkpoint_lng').value)});
      marker.setPosition({lat: parseFloat(document.getElementById('checkpoint_lat').value), lng: parseFloat(document.getElementById('checkpoint_lng').value)});
    });
    document.getElementById('checkpoint_lat').addEventListener('change', function() {
      map.setCenter({lat: parseFloat(document.getElementById('checkpoint_lat').value), lng: parseFloat(document.getElementById('checkpoint_lng').value)});
      marker.setPosition({lat: parseFloat(document.getElementById('checkpoint_lat').value), lng: parseFloat(document.getElementById('checkpoint_lng').value)});
    });
  }


}

;
$(document).on('change', 'select.dep', function() {
  var id_value_string = $(this).val();
  var select = $('select.city');
  if (id_value_string == "") {
     $(select).children('option').remove();
     var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
     $(row).appendTo(select);
  } else {
    $.ajax({
      dataType: "json",
      cache: false,
      url: '/system/tables/cities/getcitiesbydepartment/' + id_value_string,
      timeout: 10000,
      error: function(XMLHttpRequest, errorTextStatus, error) {
        alert("Failed to submit : " + errorTextStatus + " ;" + error);
      },
      success: function(data) {
        if (data.success){
          $(select).children('option').remove();
          var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
          $(row).appendTo(select);
          $.each(data.data, function(i, j) {
            row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
            $(row).appendTo(select);
          });
        }
      }
    });
  }
});
(function() {


}).call(this);
$( document ).on('turbolinks:load', function() {

    $('#bigcustomers').DataTable( {
        "processing": true,
        "serverSide": true,
        "pageLength": 25,
        "cache": false,
        "language": { "url": "/assets/datatables/datatables_spanish.json" },
        "ajax": {
            'url': "/commercial/bigcustomers.json",
            'type': 'GET',
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+$('#bigcustomers').attr('data-toc') );
            }
        }
    } );

    $('#bookings_all').DataTable( {
        "processing": true,
        "serverSide": true,
        "pageLength": 25,
        "cache": false,
        "language": { "url": "/assets/datatables/datatables_spanish.json" },
        "ajax": {
            'url': "/commercial/bookings/get_all/"+$('#bookings_all').attr('data-id')+".json",
            'type': 'GET',
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+$('#bookings_all').attr('data-toc') );
            }
        }
    })
;});

function seeDetailGolNewVehicles(button, see) {
    if (see) {
        button.innerHTML = "Ocultar detalle";
        $('#cartypes-new-vehicles').removeClass('hide');
        button.setAttribute('onclick', 'seeDetailGolNewVehicles(this, false)');
    }else{
        button.innerHTML = "Ver detalle";
        $('#cartypes-new-vehicles').addClass('hide');
        button.setAttribute('onclick', 'seeDetailGolNewVehicles(this, true)');
    }
}

function seeDetailGolUseAppVehicles(button, see) {
    if (see) {
        button.innerHTML = "Ocultar detalle";
        $('#cartypes-use-app-vehicles').removeClass('hide');
        button.setAttribute('onclick', 'seeDetailGolUseAppVehicles(this, false)');
    }else{
        button.innerHTML = "Ver detalle";
        $('#cartypes-use-app-vehicles').addClass('hide');
        button.setAttribute('onclick', 'seeDetailGolUseAppVehicles(this, true)');
    }
}

;

// solo si estoy en el formulario
$( document ).on('turbolinks:load', function() {
    $('#commercial_booking_booking_type_id').on('change',function(){
        if ($(this).val()==2){
         setTimeout(function(){
            check_first_and_last_return();
            set_value_isfirst_return();        
        },1000)
     }
 });
    check_first_and_last();
    set_value_isfirst();

    check_first_and_last_return();
    set_value_isfirst_return();

    $("#addNewAddress").click(function(){
        $("#addresees").append($("#new_address").html());
        check_first_and_last();
        set_value_isfirst();
        load = $('#addresees').find('.loadaddresscheck:last').is(':checked');
        unload = $('#addresees').find('.unloadaddresscheck:last').is(':checked');
        select = $('#addresees').find('.addresses_booking:last');
        getAddresses(load,unload,select);
        
        var total = $('#addresees').find(".dirlocal").length
        $('#addresees').find(".dirlocal").each(function(index,html){
            if (index == 0 || index == (total-1)){
                load = $(this).find('.loadaddresscheck:last').is(':checked');
                unload = $(this).find('.unloadaddresscheck:last').is(':checked');
                select = $(this).find('.addresses_booking:last');
                valor = select.val();
                var valor = $(select).children("option:selected").val();
                
            } else {
                load = true
                unload = true
                select = $(this).find('.addresses_booking:last');
                var valor = $(select).children("option:selected").val();
            }
            getAddresses(load,unload,select,valor);
        });
    });

    $("#addNewAddressNewBooking").click(function(){
        $("#addresees").append($("#new_address").html());
        check_first_and_last();
        set_value_isfirst();
        bigcustomer_id = $('#commercial_booking_bigcustomer_id').val()
        businessroute_id = $('#commercial_booking_businessroute_id').val()
        load = $('#addresees').find('.loadaddresscheck:last').is(':checked');
        unload = $('#addresees').find('.unloadaddresscheck:last').is(':checked');
        select = $('#addresees').find('.addresses_booking:last');
        getAddressesNewBooking(bigcustomer_id,businessroute_id,load,unload,select);
        
        var total = $('#addresees').find(".dirlocal").length
        $('#addresees').find(".dirlocal").each(function(index,html){
            if (index == 0 || index == (total-1)){
                load = $(this).find('.loadaddresscheck:last').is(':checked');
                unload = $(this).find('.unloadaddresscheck:last').is(':checked');
                select = $(this).find('.addresses_booking:last');
                valor = select.val();
                var valor = $(select).children("option:selected").val();
                
            } else {
                load = true
                unload = true
                select = $(this).find('.addresses_booking:last');
                var valor = $(select).children("option:selected").val();
            }
            getAddressesNewBooking(bigcustomer_id,businessroute_id,load,unload,select);
        });
    });

    $("#addNewAddressNewBookingReturn").click(function(){
        $("#addresees_return").append($("#new_address_return").html());
        check_first_and_last_return();
        set_value_isfirst_return();
        bigcustomer_id = $('#commercial_booking_bigcustomer_id').val()
        businessroute_id = $('#route2').val()
        load = $('#addresees_return').find('.loadaddresscheck:last').is(':checked');
        unload = $('#addresees_return').find('.unloadaddresscheck:last').is(':checked');
        select = $('#addresees_return').find('.addresses_booking:last');
        getAddressesNewBooking(bigcustomer_id,businessroute_id,load,unload,select);
        
        var total = $('#addresees_return').find(".dirlocal").length
        $('#addresees_return').find(".dirlocal").each(function(index,html){
            if (index == 0 || index == (total-1)){
                load = $(this).find('.loadaddresscheck:last').is(':checked');
                unload = $(this).find('.unloadaddresscheck:last').is(':checked');
                select = $(this).find('.addresses_booking:last');
                valor = select.val();
                var valor = $(select).children("option:selected").val();
                
            } else {
                load = true
                unload = true
                select = $(this).find('.addresses_booking:last');
                var valor = $(select).children("option:selected").val();
            }
            getAddressesNewBooking(bigcustomer_id,businessroute_id,load,unload,select);
        });
    });    

    function get_product_new(businessproduct_id, carconfig_id, select){
        var thisselect = select
        var is_for_travel = $('#is_for_travel').val();
        if (businessproduct_id !== "" && is_for_travel == "false") {
            $.ajax({
                dataType: "json",
                cache: false,
                url: '/commercial/businessproducts/get_product_new/' + businessproduct_id + '/' + carconfig_id,
                timeout: 5000,
                error: function(XMLHttpRequest, errorTextStatus, error) {
                    console.log("Failed to submit : " + errorTextStatus + " ;" + error);
                },
                success: function(data) {
                    if (data.success){
                        max_peso = data.max
                        if (data.data['unit_of_measurement'] == 0){
                            jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Kilos');
                        } else if (data.data['unit_of_measurement'] == 1) {
                            jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Volumen (Gal)')
                        } else if (data.data['unit_of_measurement'] == 2) {
                            jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Volumen (Mtr3)')
                        }
                        jQuery(thisselect).parent().parent().parent().find('.quantity').attr({
                           "max" : data.max,        // substitute your own
                           "min" : 1          // values (or variables) here
                       });
                        jQuery(thisselect).parent().parent().parent().find('.quantity').val(data.peso)
                    }
                }
            });
        }
    }

    $('#commercial_booking_businessroute_id').on('change', function(){
        check_first_and_last();
        set_value_isfirst();
        bigcustomer_id = $('#commercial_booking_bigcustomer_id').val()
        businessroute_id = $('#commercial_booking_businessroute_id').val()
        load = $('#addresees').find('.loadaddresscheck:last').is(':checked');
        unload = $('#addresees').find('.unloadaddresscheck:last').is(':checked');
        select = $('#addresees').find('.addresses_booking:first');
        select2 = $('#addresees').find('.addresses_booking:last');
        var element = $(this).find('option:selected'); 
        var apply_chargue = element.attr("data-chargue"); 
        is_for_travel = false
        if (apply_chargue == 1){
            is_for_travel = false;
        } else{
            is_for_travel = true;
        }
        if (is_for_travel == true){
            $('.quantity_field_div').addClass('ocultar')
        } else {
            $('.quantity_field_div').removeClass('ocultar')
        }
        $("#is_for_travel").val(is_for_travel)

        getAddressesNewBooking(bigcustomer_id,businessroute_id,true,false,select);

        var total = $('#addresees').find(".dirlocal").length
        $('#addresees').find(".dirlocal").each(function(index,html){
            if (index == 0 || index == (total-1)){
                load = $(this).find('.loadaddresscheck:last').is(':checked');
                unload = $(this).find('.unloadaddresscheck:last').is(':checked');
                select = $(this).find('.addresses_booking:last');
                valor = select.val();
                var valor = $(select).children("option:selected").val();
                
            } else {
                load = true
                unload = true
                select = $(this).find('.addresses_booking:last');
                var valor = $(select).children("option:selected").val();
            }
            getAddressesNewBooking(bigcustomer_id,businessroute_id,load,unload,select);
            
        });
        setTimeout(
            function() 
            {
                set_items();
                update_change_route();
            }, 1000);
        
    });

    $('#route2').on('change', function(){
        check_first_and_last_return();
        set_value_isfirst_return();
        bigcustomer_id = $('#commercial_booking_bigcustomer_id').val()
        businessroute_id = $('#route2').val()
        load = $('#addresees_return').find('.loadaddresscheck:last').is(':checked');
        unload = $('#addresees_return').find('.unloadaddresscheck:last').is(':checked');
        select = $('#addresees_return').find('.addresses_booking:first');
        select2 = $('#addresees_return').find('.addresses_booking:last');
        var element = $(this).find('option:selected'); 
        var apply_chargue = element.attr("data-chargue"); 
        is_for_travel= false
        if (apply_chargue == 1){
            is_for_travel = false;
        } else{
            is_for_travel = true;
        }
        if (is_for_travel == true){
            $('.quantity_field_div_return').addClass('ocultar')
        } else {
            $('.quantity_field_div_return').removeClass('ocultar')
        }
        $("#is_for_travel2").val(is_for_travel)

        getAddressesNewBooking(bigcustomer_id,businessroute_id,true,false,select);

        var total = $('#addresees_return').find(".dirlocal").length
        $('#addresees_return').find(".dirlocal").each(function(index,html){
            if (index == 0 || index == (total-1)){
                load = $(this).find('.loadaddresscheck:last').is(':checked');
                unload = $(this).find('.unloadaddresscheck:last').is(':checked');
                select = $(this).find('.addresses_booking:last');
                valor = select.val();
                var valor = $(select).children("option:selected").val();
                
            } else {
                load = true
                unload = true
                select = $(this).find('.addresses_booking:last');
                var valor = $(select).children("option:selected").val();
            }
            getAddressesNewBooking(bigcustomer_id,businessroute_id,load,unload,select);
            
        });
        setTimeout(
            function() 
            {
                set_items_return();
                update_change_route();
            }, 1000);
        
    })


    function update_change_route(){
        $('.change_product_new').each(function(index){
            carconfig_id = $('#commercial_booking_carconfig_id').val();
            get_product_new($(this).val(), carconfig_id,  $(this))
        });
    }

    function set_items(){

        var plants = []
        var total = $('#addresees').find(".dirlocal").length
        sessionStorage.removeItem("plants");
        $('.plants').empty()
        $('#addresees').find(".dirlocal").each(function(index,html){
            s = $(this).find('.loadaddresscheck:last').is(':checked');
            if (s == true){
                select = $(this).find('.addresses_booking:last')
                val = select.val()
                name = select.find('option:selected').text()
                if (sessionStorage.getItem("plants") !== null) {
                    plants = JSON.parse(sessionStorage.getItem("plants"));
                }                
                json = {id: val, name: name}
                result = exist(plants, val)
                if (result === false) {
                    plants.push(json)
                }
                sessionStorage.setItem('plants', JSON.stringify(plants))

            }
        });
        if (sessionStorage.getItem("plants") !== null) {
            plants = JSON.parse(sessionStorage.getItem("plants"));
            $('.plants').empty()
            options = ''
            $.each(plants, function(index, data){
                options += '<option value='+data.id+'>'+data.name+'</option>' ;
            });
            $('.plants').append(options)
        } 
    }

    function set_items_return(){

        var plants_return = []
        var total = $('#addresees_return').find(".dirlocal").length
        sessionStorage.removeItem("plants_return");
        $('.plants_return').empty()
        $('#addresees_return').find(".dirlocal").each(function(index,html){
            s = $(this).find('.loadaddresscheck:last').is(':checked');
            if (s == true){
                select = $(this).find('.addresses_booking:last')
                val = select.val()
                name = select.find('option:selected').text()
                if (sessionStorage.getItem("plants_return") !== null) {
                    plants_return = JSON.parse(sessionStorage.getItem("plants_return"));
                }                
                json = {id: val, name: name}
                result = exist(plants_return, val)
                if (result === false) {
                    plants_return.push(json)
                }
                sessionStorage.setItem('plants_return', JSON.stringify(plants_return))

            }
        });
        if (sessionStorage.getItem("plants_return") !== null) {
            plants_return = JSON.parse(sessionStorage.getItem("plants_return"));
            $('.plants_return').empty()
            options = ''
            $.each(plants_return, function(index, data){
                options += '<option value='+data.id+'>'+data.name+'</option>' ;
            });
            $('.plants_return').append(options)
        } 
    }


    $("#addresees").on('change', '.addresses_booking',  function(){        
        set_items();
    });

    $("#addresees_return").on('change', '.addresses_booking',  function(){
        set_items_return();
    })

    $('.loadaddresscheck').on('change', function(){
        if ($(this).is(':checked')) {
            set_items();    
        }        
    });


    $("#addNewAddressReturn").click(function(){
        $("#addresees_return").append($("#new_address_retu").html());
        check_first_and_last_return();
        set_value_isfirst_return();
        load = $('#addresees_return').find('.loadaddresscheck:last').is(':checked');
        unload = $('#addresees_return').find('.unloadaddresscheck:last').is(':checked');
        select = $('#addresees_return').find('.addresses_booking:last');
        getAddressesReturn(load,unload,select);

        var total = $('#addresees_return').find(".dirlocal").length
        $('#addresees_return').find(".dirlocal").each(function(index,html){
            if (index == 0 || index == (total-1)){
                load = $(this).find('.loadaddresscheck:last').is(':checked');
                unload = $(this).find('.unloadaddresscheck:last').is(':checked');
                select = $(this).find('.addresses_booking:last');
                valor = select.val();
                var valor = $(select).children("option:selected").val();

            } else {
                load = true
                unload = true
                select = $(this).find('.addresses_booking:last');
                var valor = $(select).children("option:selected").val();
            }
            getAddressesReturn(load,unload,select,valor);

        });


    });


    $("#addresees").on('click', '.delete_address',  function(){
        $(this).parent().parent().parent().parent().remove()
        check_first_and_last();
        set_value_isfirst();
        return false
    })

    $("#addresees").on('click', '.delete_address_new',  function(){
        $(this).parent().parent().remove()
        check_first_and_last();
        set_value_isfirst();
        bigcustomer_id = $('#commercial_booking_bigcustomer_id').val()
        businessroute_id = $('#commercial_booking_businessroute_id').val()
        load = $('#addresees').find('.loadaddresscheck:last').is(':checked');
        unload = $('#addresees').find('.unloadaddresscheck:last').is(':checked');
        select = $('#addresees').find('.addresses_booking:last');
        getAddressesNewBooking(bigcustomer_id,businessroute_id,load,unload,select);
        
        var total = $('#addresees').find(".dirlocal").length
        $('#addresees').find(".dirlocal").each(function(index,html){
            if (index == 0 || index == (total-1)){
                load = $(this).find('.loadaddresscheck:last').is(':checked');
                unload = $(this).find('.unloadaddresscheck:last').is(':checked');
                select = $(this).find('.addresses_booking:last');
                valor = select.val();
                var valor = $(select).children("option:selected").val();
                
            } else {
                load = true
                unload = true
                select = $(this).find('.addresses_booking:last');
                var valor = $(select).children("option:selected").val();
            }
            getAddressesNewBooking(bigcustomer_id,businessroute_id,load,unload,select);
        });
        return false
    })


    $("#addresees_return").on('click', '.delete_address_return',  function(){
        $(this).parent().parent().parent().parent().remove()
        check_first_and_last_return();
        set_value_isfirst_return();
        return false
    })

    $("#addresees_return").on('click', '.delete_address_return_new',  function(){
        $(this).parent().parent().remove()
        check_first_and_last_return();
        set_value_isfirst_return();
        bigcustomer_id = $('#commercial_booking_bigcustomer_id').val()
        businessroute_id = $('#route2').val()
        load = $('#addresees_return').find('.loadaddresscheck:last').is(':checked');
        unload = $('#addresees_return').find('.unloadaddresscheck:last').is(':checked');
        select = $('#addresees_return').find('.addresses_booking:last');
        getAddressesNewBooking(bigcustomer_id,businessroute_id,load,unload,select);
        
        var total = $('#addresees_return').find(".dirlocal").length
        $('#addresees_return').find(".dirlocal").each(function(index,html){
            if (index == 0 || index == (total-1)){
                load = $(this).find('.loadaddresscheck:last').is(':checked');
                unload = $(this).find('.unloadaddresscheck:last').is(':checked');
                select = $(this).find('.addresses_booking:last');
                valor = select.val();
                var valor = $(select).children("option:selected").val();
                
            } else {
                load = true
                unload = true
                select = $(this).find('.addresses_booking:last');
                var valor = $(select).children("option:selected").val();
            }
            getAddressesNewBooking(bigcustomer_id,businessroute_id,load,unload,select);
        });
        return false
    })

    function check_first_and_last(){
    	//uncheck ultimo carga
    	jQuery('#addresees').find('.loadaddresscheck:checkbox:last').prop("checked", false);

    	//Habilitar Check
    	jQuery('#addresees').find('.loadaddresscheck:checkbox').prop("disabled", false);
    	jQuery('#addresees').find('.unloadaddresscheck:checkbox').prop("disabled", false);

    	//DesHabilitar Primer Check
    	jQuery('#addresees').find('.loadaddresscheck:checkbox:first').prop("disabled", true);
    	jQuery('#addresees').find('.unloadaddresscheck:checkbox:first').prop("disabled", true);

    	//Check Primero Carga
    	jQuery('#addresees').find('.loadaddresscheck:checkbox:first').prop("checked", true);

    	//Check ultimo descarga
    	jQuery('#addresees').find('.unloadaddresscheck:checkbox:last').prop("checked", true);

    	//Uncheck primero descarga
    	jQuery('#addresees').find('.unloadaddresscheck:checkbox:first').prop("checked", false);


    	//DesHabilitar Ultimo Check
    	jQuery('#addresees').find('.loadaddresscheck:checkbox:last').prop("disabled", true);
    	jQuery('#addresees').find('.unloadaddresscheck:checkbox:last').prop("disabled", true);

    	

    }


    function check_first_and_last_return(){
        //uncheck ultimo carga
        jQuery('#addresees_return').find('.loadaddresscheck:checkbox:last').prop("checked", false);

        //Habilitar Check
        jQuery('#addresees_return').find('.loadaddresscheck:checkbox').prop("disabled", false);
        jQuery('#addresees_return').find('.unloadaddresscheck:checkbox').prop("disabled", false);

        //DesHabilitar Primer Check
        jQuery('#addresees_return').find('.loadaddresscheck:checkbox:first').prop("disabled", true);
        jQuery('#addresees_return').find('.unloadaddresscheck:checkbox:first').prop("disabled", true);

        //Check Primero Carga
        jQuery('#addresees_return').find('.loadaddresscheck:checkbox:first').prop("checked", true);

        //Check ultimo descarga
        jQuery('#addresees_return').find('.unloadaddresscheck:checkbox:last').prop("checked", true);

        //Uncheck primero descarga
        jQuery('#addresees_return').find('.unloadaddresscheck:checkbox:first').prop("checked", false);


        //DesHabilitar Ultimo Check
        jQuery('#addresees_return').find('.loadaddresscheck:checkbox:last').prop("disabled", true);
        jQuery('#addresees_return').find('.unloadaddresscheck:checkbox:last').prop("disabled", true);

        

    }

    function set_value_isfirst(){
        jQuery('#addresees').find('.is_first').prop("value", false);
        jQuery('#addresees').find('.is_first:first').prop("value", true);
    }

    function set_value_isfirst_return(){
        jQuery('#addresees_return').find('.is_first').prop("value", false);
        jQuery('#addresees_return').find('.is_first:first').prop("value", true);
    }


    function enable() {
        $('input:disabled, select:disabled').each(function () {
            $(this).removeAttr('disabled');
            $(this).removeAttr('readonly');
        });
    }
    function validate_num_address(){
        var NumAddress = $('#addresees').find(".direcciones").length
        if (NumAddress >= 2) {
            return true
        } else {
            return false
        }
    }

    function validate_num_address_return(){
        var NumAddress = $('#addresees_return').find(".direcciones_return").length
        if (NumAddress >= 2) {
            return true
        } else {
            return false
        }
    }

    function distinc_load_unload(){
        var response = true
        var address = [];
        $('#addresees').find(".direcciones").each(function(index){
            id = $( this ).find('#booking_address__address_id').val()
            address.push(id);
        });
        var num = address.length-1;
        $.each( address, function( key, value ) {
            if (key < num){
                if (value == address[key+1]){
                    response = false;
                    return false;
                }
            }
        });
        return response;
        // return false;
    }


    function distinc_load_unload_return(){
        var response = true
        var address = [];
        $('#addresees_return').find(".direcciones").each(function(index){
            id = $( this ).find('#booking_address2__address_id').val()
            address.push(id);
        });
        var num = address.length-1;
        $.each( address, function( key, value ) {
            if (key < num){
                if (value == address[key+1]){
                    response = false;
                    return false;
                }
            }
        });
        return response;
        // return false;
    }

    function getAddressesNewBooking(bigcustomer_id,businessroute_id,load,unload,select,valor='') {
        try{

            if (parseInt(businessroute_id) > 0){
                $.ajax({
                    dataType: "json",
                    cache: false,
                    url: '/commercial/booking_addresses/get_adresses_new_booking/'+bigcustomer_id+'/'+businessroute_id+'/'+load+'/'+unload+'.json',
                    timeout: 5000,
                    beforeSend: function (request) {
                        request.setRequestHeader("Authorization", 'Bearer <%= current_user.token %>');
                    },
                    error: function(XMLHttpRequest, errorTextStatus, error) {
                        console.log("Failed to submit : " + errorTextStatus + " ;" + error);
                    },
                    success: function(data) { 
                        if (data.success){
                            $(select).children().remove();
                            count = 1;
                            $.each(data.data, function(i, j) {
                                if (count == 1) {
                                    aux = ' selected'
                                } else {
                                    aux = ( valor == ''+j.id ? ' selected' : '')    
                                }
                                
                                $(select).append( "<option value=\"" + j.id + "\""+aux+">" + j.name + "</option>" );
                                count++

                            });
                            // $(select).focus()
                        }
                    }
                });
            }
            
        } catch (error) {

        }
    }

    function getAddresses(load,unload,select,valor='') {
        $.ajax({
            dataType: "json",
            cache: false,
            url: '/commercial/booking_addresses/get_adresses/'+$("#booking_id").val()+'/'+load+'/'+unload+'.json',
            timeout: 5000,
            beforeSend: function (request) {
                request.setRequestHeader("Authorization", 'Bearer <%= current_user.token %>');
            },
            error: function(XMLHttpRequest, errorTextStatus, error) {
                alert("Failed to submit : " + errorTextStatus + " ;" + error);
            },
            success: function(data) { 
                if (data.success){
                    $(select).children().remove();
                    $.each(data.data, function(i, j) {
                        aux = ( valor == ''+j.id ? ' selected' : '')
                        $(select).append( "<option value=\"" + j.id + "\""+aux+">" + j.name + "</option>" );
                    });
                    $(select).focus()
                }
            }
        });
    }

    function getAddressesReturn(load,unload,select,valor='') {
        $.ajax({
            dataType: "json",
            cache: false,
            url: '/commercial/booking_addresses/get_adresses/'+$("#booking_id2").val()+'/'+load+'/'+unload+'.json',
            timeout: 5000,
            beforeSend: function (request) {
                request.setRequestHeader("Authorization", 'Bearer <%= current_user.token %>');
            },
            error: function(XMLHttpRequest, errorTextStatus, error) {
                alert("Failed to submit : " + errorTextStatus + " ;" + error);
            },
            success: function(data) { 
                if (data.success){
                    $(select).children().remove();
                    $.each(data.data, function(i, j) {
                        aux = ( valor == ''+j.id ? ' selected' : '')
                        $(select).append( "<option value=\"" + j.id + "\""+aux+">" + j.name + "</option>" );
                    });
                    $(select).focus()
                }
            }
        });
    }

    $('#booking_address').submit(function() {
        // return false
        if (validate_num_address() == false){
            alert('Debe agregar por lo menos una dirección de carga y una de descarga ')
            return false
        }

        if (distinc_load_unload() == false){
            alert('No debe haber direcciones consecutivas que sean iguales')
            return false
        }

        if ($('#addresees_return').length){
            if (validate_num_address_return() == false){
                alert('Debe agregar por lo menos una dirección de carga y una de descarga en la segunda solicitud')
                return false
            }

            if (distinc_load_unload_return() == false){
                alert('No debe haber direcciones consecutivas que sean iguales en segunda solicitud')
                return false
            }
        }
        enable();
    });

    function exist(obj,value){
        // iterate over each element in the array
        for (var i = 0; i < obj.length; i++){
            if (obj[i].id == value){ // look for the entry with a matching `code` value
                return i
            }   
        }
        return false
    }
});

$( document ).on('turbolinks:load', function() {
	var max_peso = 37500
	var max_peso2 = 37500
	var restante = 37500

	function getBusinessproducts(businessroute_id,select,valor='') {
		$.ajax({
			dataType: "json",
			cache: false,
			url: '/commercial/businessproducts/getbusinessproductsfrombussiness/'+businessroute_id+'.json',
			timeout: 5000,
			beforeSend: function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+data_toc);
			},
			error: function(XMLHttpRequest, errorTextStatus, error) {
				console.log("Failed to submit : " + errorTextStatus + " ;" + error);
			},
			success: function(data) { 
				if (data.success){
					$(select).children().remove();
					$.each(data.data, function(i, j) {
						aux = ( valor == ''+j.id ? ' selected' : '')
						$(select).append( "<option value=\"" + j.id + "\""+aux+">" + j.name + "</option>" );
					});
                    // $(select).focus()
                }
                
            }
        });
	}

	
	function get_product_new(businessproduct_id, carconfig_id, select){
		var thisselect = select
		var is_for_travel = $('#is_for_travel').val();
		if (businessproduct_id !== "") {
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/commercial/businessproducts/get_product_new/' + businessproduct_id + '/' + carconfig_id,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) {
					if (data.success){
						max_peso = data.max
						if (data.data['unit_of_measurement'] == 0){
							jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Kilos');
						} else if (data.data['unit_of_measurement'] == 1) {
							jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Volumen (Gal)')
						} else if (data.data['unit_of_measurement'] == 2) {
							jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Volumen (Mtr3)')
						}
						jQuery(thisselect).parent().parent().parent().find('.quantity').attr({
					       "max" : data.max,        // substitute your own
					       "min" : 1 ,         // values (or variables) here
					       "value" : data.peso          // values (or variables) here
					   });
					}
				}
			});
		}
	}
	$("#addNewProduct").click(function(){
		$("#productos").append($("#new_product").html());
	});

	$("#addNewProductReturn").click(function(){
		$("#productos_return").append($("#new_product_return").html());
	});

	$("#addNewBookingProductReturn").click(function(){
		$("#productos_return").append($("#new_product_return").html());
	});	

	$("#productos").on('click', '.delete_product_new',  function(){
		$(this).parent().parent().parent().remove()
		return false
	})

	$("#productos_return").on('click', '.delete_product',  function(){
		$(this).parent().parent().parent().parent().remove()
		return false
	})

	$("#productos_return").on('click', '.delete_product_new_booking',  function(){
		$(this).parent().parent().parent().remove()
		return false
	})


	$("#productos").on('change', '.select_product',  function(e){
		var id_value_string = $(this).val();
		var thisselect = $(this);
		var boking_id = $(this).attr('booking');
		if (typeof boking_id === "undefined"){
			booking_undefined = true
		} else {
			booking_undefined = false
		}
		var is_for_travel = $('#is_for_travel').val();
		if (id_value_string !== "" && is_for_travel == "false" && booking_undefined == false) {
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/commercial/businessproducts/get_product/' + id_value_string +'/'+ boking_id,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) {
					if (data.success){
						max_peso = data.max
						if (data.data['unit_of_measurement'] == 0){
							jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Kilos');
						} else if (data.data['unit_of_measurement'] == 1) {
							jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Volumen (Gal)')
						} else if (data.data['unit_of_measurement'] == 2) {
							jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Volumen (Mtr3)')
						}
						jQuery(thisselect).parent().parent().parent().find('.quantity').attr({
					       "max" : data.max,        // substitute your own
					       "min" : 1          // values (or variables) here
					   });
						jQuery(thisselect).parent().parent().parent().find('.quantity').val(data.peso)
					}
				}
			});
		}
	})

	$("#productos_return").on('change', '.select_product',  function(e){
		var id_value_string = $(this).val();
		var thisselect = $(this);
		var boking_id = $(this).attr('booking');
		var is_for_travel = $('#is_for_travel2').val();
		if (typeof boking_id === "undefined"){
			booking_undefined = true
		} else {
			booking_undefined = false
		}
		if (id_value_string !== "" && is_for_travel == "false" && booking_undefined == false) {
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/commercial/businessproducts/get_product/' + id_value_string +'/'+ boking_id,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) {
					if (data.success){
						max_peso2 = data.max
						if (data.data['unit_of_measurement'] == 0){
							jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Kilos');
						} else if (data.data['unit_of_measurement'] == 1) {
							jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Volumen (Gal)')
						} else if (data.data['unit_of_measurement'] == 2) {
							jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Volumen (Mtr3)')
						}
						jQuery(thisselect).parent().parent().parent().find('.quantity').attr({
					       "max" : data.max,        // substitute your own
					       "min" : 1          // values (or variables) here
					   });
						jQuery(thisselect).parent().parent().parent().find('.quantity').val(data.peso)
					}
				}
			});
		}
	});

	function get_product_new_return(businessproduct_id, carconfig_id, select){
		var thisselect = select
		var is_for_travel = $('#is_for_travel2').val();
		if (businessproduct_id !== "") {
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/commercial/businessproducts/get_product_new/' + businessproduct_id + '/' + carconfig_id,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) {
					if (data.success){
						console.log(data)
						max_peso = data.max
						if (data.data['unit_of_measurement'] == 0){
							jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Kilos');
						} else if (data.data['unit_of_measurement'] == 1) {
							jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Volumen (Gal)')
						} else if (data.data['unit_of_measurement'] == 2) {
							jQuery(thisselect).parent().parent().parent().find('.unitymeasure').text('Volumen (Mtr3)')
						}
						jQuery(thisselect).parent().parent().parent().find('.quantity').attr({
					       "max" : data.max,        // substitute your own
					       "min" : 1,          // values (or variables) here
					       "value" : data.peso          // values (or variables) here
					   });
					}
				}
			});
		}
	}

	$('#commercial_booking_business_id').on('change', function(){
		select = $('.select_product');
		getBusinessproducts($(this).val(),select);
	});

	$('.change_product_new').on('change', function(){
		carconfig_id = $('#commercial_booking_carconfig_id').val();
		get_product_new($(this).val(), carconfig_id,  $(this))

	});

	$('.change_product_new_return').on('change', function(){
		carconfig_id = $('#carconfig_id2').val();
		get_product_new_return($(this).val(), carconfig_id,  $(this))
	});

	$("#products_booking").submit(function() {
		var sum = 0;
		$("#productos").find(".max_quantity").each(function(){
			sum += +$(this).val();
		});
		var is_for_travel = $('#is_for_travel').val();
		if (max_peso < sum && is_for_travel == "false"){
			alert('El peso supera el máximo ('+max_peso+') para la configuración del vehículo seleccionado ')
			return false
		}
		if (booking_type == 2){
			var sum2 = 0;
			$("#productos_return").find(".max_quantity").each(function(){
				sum2 += +$(this).val();
			});
			var is_for_travel2 = $('#is_for_travel2').val();
			if (max_peso2 < sum2 && is_for_travel2){
				alert('El peso de los productos de retorno supera el máximo ('+max_peso2+') para la configuración del vehículo seleccionado ')
				return false
			}
		}
	});
});

var choose_sider1 = false;
var choose_sider2 = false;
var max_peso = 35000
var max_peso2 = 35000
var restante = 35000
var has_border_stamp = false
var has_border_stamp2 = false
// solo si estoy en el formulario
$( document ).on('turbolinks:load', function() {
    function enable() {
        $('#addresees :input:disabled, #addresees select:disabled').each(function () {
            $(this).removeAttr('disabled');
            $(this).removeAttr('readonly');
        });

        $('#addresees_return :input:disabled, #addresees_return select:disabled').each(function () {
            $(this).removeAttr('disabled');
            $(this).removeAttr('readonly');
        });
    }
    function validate_num_address_new(){
        var NumAddress = $('#addresees').find(".direcciones").length
        if (NumAddress >= 2) {
            return true
        } else {
            return false
        }
    }

    function validate_num_address_return(){
        var NumAddress = $('#addresees_return').find(".direcciones_return").length
        if (NumAddress >= 2) {
            return true
        } else {
            return false
        }
    }

    function distinc_load_unload(){
        var response = true
        var address = [];
        $('#addresees').find(".direcciones").each(function(index){
            id = $( this ).find('#booking_address__address_id').val()
            address.push(id);
        });
        var num = address.length-1;
        $.each( address, function( key, value ) {
            if (key < num){
                if (value == address[key+1]){
                    response = false;
                    return false;
                }
            }
        });
        return response;
        // return false;
    }


    function distinc_load_unload_return(){
        var response = true
        var address = [];
        $('#addresees_return').find(".direcciones").each(function(index){
            id = $( this ).find('#booking_address2__address_id').val()
            address.push(id);
        });
        var num = address.length-1;
        $.each( address, function( key, value ) {
            if (key < num){
                if (value == address[key+1]){
                    response = false;
                    return false;
                }
            }
        });
        return response;
        // return false;
    }
    $('#booking_form_new').submit(function() {
        // return false
        if (validate_num_address_new() == false){
            alert('Debe agregar por lo menos una dirección de carga y una de descarga ')
            return false
        }

        if (distinc_load_unload() == false){
            alert('No debe haber direcciones consecutivas que sean iguales')
            return false
        }

        if ($('#addresees_return').length){
            if (validate_num_address_return() == false){
                alert('Debe agregar por lo menos una dirección de carga y una de descarga en la segunda solicitud')
                return false
            }

            if (distinc_load_unload_return() == false){
                alert('No debe haber direcciones consecutivas que sean iguales en segunda solicitud')
                return false
            }
        }
        var sum = 0;
        $("#productos").find(".max_quantity").each(function(){
            sum += +$(this).val();
        });
        var is_for_travel = $('#is_for_travel').val();
        if (max_peso < sum && is_for_travel == "false"){
            alert('El peso supera el máximo ('+max_peso+') para la configuración del vehículo seleccionado ')
            return false
        }
        if ($('#commercial_booking_booking_type_id').val() == 2){
            var sum2 = 0;
            $("#productos_return").find(".max_quantity").each(function(){
                sum2 += +$(this).val();
            });
            var is_for_travel2 = $('#is_for_travel2').val();
            if (max_peso2 < sum2 && is_for_travel2){
                alert('El peso de los productos de retorno supera el máximo ('+max_peso2+') para la configuración del vehículo seleccionado ')
                return false
            }
        }
        enable();
        // return false;
    });
    function verificateSelectUnique(element) {
        if (element == 'commercial_booking_business_id'){
            document.getElementById(element).selectedIndex = 1;
            var event = new Event('change');
            document.getElementById(element).dispatchEvent(event);
        } else if ( document.getElementById(element).length  == 2 ) {
            document.getElementById(element).selectedIndex = 1;
        }
    }
    function getRoutes(valor='') {
        $.ajax({
            dataType: "json",
            cache: false,
            url: '/commercial/businessroutes/getroutes/'+$("#commercial_booking_business_id").val()+'.json',
            timeout: 5000,
            beforeSend: function (request) {
                request.setRequestHeader("Authorization", 'Bearer <%= current_user.token %>');
            },
            error: function(XMLHttpRequest, errorTextStatus, error) {
                console.log("Failed to submit : " + errorTextStatus + " ;" + error);
            },
            success: function(data) {
                if (data.length){
                    $("#commercial_booking_businessroute_id").children().remove();
                    $("#commercial_booking_businessroute_id").append('<option value="">Seleccione:</option>');
                    // Viaje Redondo
                    $("#route2").children().remove();
                    $("#route2").append('<option value="">Seleccione:</option>');

                    $.each(data, function(i, j) {
                        aux = ( valor == ''+j.id ? ' selected' : '')
                        $("#commercial_booking_businessroute_id").append( "<option data-chargue=\"" + j.negotiatedby_id + "\" value=\"" + j.id + "\""+aux+">" + j.id + " -  " + j.name + "</option>" );
                        $("#route2").append( "<option  data-chargue=\"" + j.negotiatedby_id + "\" value=\"" + j.id + "\""+aux+">" + j.id + " -  " + j.name + "</option>" );
                    });
                    $("#commercial_booking_businessroute_id").focus()


                }
            }
        });
    }
    function getCarConfig(valor='') {
        $.ajax({
            dataType: "json",
            cache: false,
            url: '/system/tables/carconfigs/'+valor+'.json',
            timeout: 5000,
            beforeSend: function (request) {
                request.setRequestHeader("Authorization", 'Bearer <%= current_user.token %>');
            },
            error: function(XMLHttpRequest, errorTextStatus, error) {
                console.log("Failed to submit : " + errorTextStatus + " ;" + error);
            },
            success: function(data) {
                console.log(data)
                if (data.success){
                    $("#productos").find(".max_quantity").each(function(){
                        $(this).prop('max', data.data.capacity)
                        $(this).prop('value', data.data.capacity)
                        max_peso = data.data.capacity
                    });

                }
            }
        });
    }
    function getRoutesDoble(valor='') {
        $.ajax({
            dataType: "json",
            cache: false,
            url: '/commercial/businessroutes/getroutes/'+$("#business_id2").val()+'.json',
            timeout: 5000,
            beforeSend: function (request) {
                request.setRequestHeader("Authorization", 'Bearer <%= current_user.token %>');
            },
            error: function(XMLHttpRequest, errorTextStatus, error) {
                console.log("Failed to submit : " + errorTextStatus + " ;" + error);
            },
            success: function(data) {
                if (data.length){
                    $("#route2").children().remove();
                    $("#route2").append('<option value="">Seleccione:</option>');

                    $.each(data, function(i, j) {
                        aux = ( valor == ''+j.id ? ' selected' : '')
                        $("#route2").append( "<option value=\"" + j.id + "\""+aux+">" + j.id + " -  " + j.name + "</option>" );
                    });
                    $("#route2").focus()


                }
            }
        });
    }
    function getFlagsBusiness() {
        $.ajax({
            dataType: "json",
            cache: false,
            url: '/commercial/businesses/getbusinessfromid/'+$("#commercial_booking_business_id").val()+'.json',
            timeout: 5000,
            beforeSend: function (request) {
                request.setRequestHeader("Authorization", 'Bearer <%= current_user.token %>');
            },
            error: function(XMLHttpRequest, errorTextStatus, error) {
                console.log("Failed to submit : " + errorTextStatus + " ;" + error);
            },
            success: function(data) {
                if (data.success){
                    var exclusive_fleet;
                    var choose_sider;
                    var is_circuit
                    var is_roundtrip
                    if (data.data.exclusive_fleet == true){
                        exclusive_fleet = true;
                        $('#exclusive_fleet').removeClass('display-none');
                        $('#exclusive_fleet2').removeClass('display-none');
                    } else {
                        exclusive_fleet = false;
                        $('#exclusive_fleet').addClass('display-none');
                        $('#exclusive_fleet2').addClass('display-none');
                    }
                    if (data.data.choose_sider == true){
                        choose_sider1 = true
                        choose_sider2 = true
                        $('#sider_type').removeClass('display-none');
                        $('body').find('.sider_type1').removeClass('display-none');
                        $('body').find('.sider_type_id_input1').attr("disabled",false);
                        $('#commercial_booking_sider_type_id').attr("disabled",false);
                        // Viaje Retorno
                        $('#sider_type2').removeClass('display-none');
                        $('#commercial_booking2_sider_type_id').attr("required",true)
                        $('#commercial_booking2_sider_type_id').attr("disabled",false)
                        $('body').find('.sider_type2').removeClass('display-none');
                        $('body').find('.sider_type_id_input2').attr("disabled",false);
                    } else {
                        choose_sider1 = false
                        choose_sider2 = false
                        $('#sider_type').addClass('display-none');
                        $('#commercial_booking_sider_type_id').attr("disabled",true)
                        $('body').find('.sider_type1').addClass('display-none');
                        $('body').find('.sider_type_id_input1').attr("disabled",true);
                        // Viaje Retorno
                        $('#sider_type2').addClass('display-none');
                        $('#commercial_booking2_sider_type_id').attr("required",false)
                        $('#commercial_booking2_sider_type_id').attr("disabled",true)
                        $('body').find('.sider_type2').addClass('display-none');
                        $('body').find('.sider_type_id_input2').attr("disabled",true);
                    }
                    if (data.data.is_circuit == true){
                        is_circuit = true;
                        $('#circuit').removeClass('display-none');
                        $('#commercial_booking_is_circuit').attr("disabled",false)
                        // Viaje Retorno
                        $('#circuit2').removeClass('display-none');
                        $('#commercial_booking2_is_circuit').attr("disabled",false)
                    } else {
                        is_circuit = false;
                        $('#circuit').addClass('display-none');
                        $('#commercial_booking_is_circuit').attr("disabled",true)
                        // Viaje Retorno
                        $('#circuit2').addClass('display-none');
                        $('#commercial_booking2_is_circuit').attr("disabled",true)
                    }
                    if (data.data.manage_paper == true){
                        $('#manage_paper').removeClass('display-none');
                    } else {
                        $('#manage_paper').addClass('display-none');

                    }
                    if (data.data.round_trip == true){
                        is_roundtrip = true;
                        $('#commercial_booking_booking_type_id').attr("disabled",false)
                        $("#booking_round :input").prop("disabled", true);
                        var element = $("#carconfig_id2").find('option:selected'); 
                        var apply_cartype = element.attr("data-apply_cartype"); 
                        if (apply_cartype == 'false'){
                            $("#cartype_id2").prop('disabled', true);
                        } else {
                            $("#cartype_id2").prop("disabled", false)
                        }
                    } else {
                        is_roundtrip = false;
                        $('#commercial_booking_booking_type_id option[value="1"]').attr("selected", "selected");
                        $('#booking_round').addClass('display-none');
                        $('#commercial_booking_booking_type_id').val(1)
                        $('#commercial_booking_booking_type_id').attr("disabled",true)
                        $("#booking_round :input").prop("disabled", true);
                    }
                    $('#commercial_booking_exclusive_fleet option[value="'+exclusive_fleet+'"]').attr("selected", "selected");
                    $('#commercial_booking_is_circuit option[value="'+is_circuit+'"]').attr("selected", "selected");
                    $('#commercial_booking_booking_type_id').trigger('change');
                }
            }
        });
}
function getFlagsBusinessDoble() {
    $.ajax({
        dataType: "json",
        cache: false,
        url: '/commercial/businesses/getbusinessfromid/'+$("#business_id2").val()+'.json',
        timeout: 5000,
        beforeSend: function (request) {
            request.setRequestHeader("Authorization", 'Bearer <%= current_user.token %>');
        },
        error: function(XMLHttpRequest, errorTextStatus, error) {
            console.log("Failed to submit : " + errorTextStatus + " ;" + error);
        },
        success: function(data) {
            if (data.success){
                var exclusive_fleet;
                var choose_sider;
                var is_circuit
                var is_roundtrip
                var manage_paper
                if (data.data.exclusive_fleet == true){
                    exclusive_fleet = true;
                    $('#exclusive_fleet2').removeClass('display-none');
                } else {
                    exclusive_fleet = false;
                    $('#exclusive_fleet2').addClass('display-none');
                }
                if (data.data.choose_sider == true){
                    choose_sider2 = true
                    $('#sider_type2').removeClass('display-none');
                    $('#commercial_booking2_sider_type_id').attr("required",true)
                    $('#commercial_booking2_sider_type_id').attr("disabled",false)
                    $('body').find('.sider_type2').removeClass('display-none');
                    $('body').find('.sider_type_id_input2').attr("disabled",false)
                } else {
                    choose_sider2 = false
                    $('#sider_type2').addClass('display-none');
                    $('#commercial_booking2_sider_type_id').attr("required",false)
                    $('#commercial_booking2_sider_type_id').attr("disabled",true)
                    $('body').find('.sider_type2').addClass('display-none');
                    $('body').find('.sider_type_id_input2').attr("disabled",true);

                }
                if (data.data.is_circuit == true){
                    is_circuit = true;
                    $('#circuit2').removeClass('display-none');
                    $('#commercial_booking2_is_circuit').attr("disabled",false)
                } else {
                    is_circuit = false;
                    $('#circuit2').addClass('display-none');
                    $('#commercial_booking2_is_circuit').attr("disabled",true)
                }
                if (data.data.manage_paper == true){
                    $('#manage_paper2').removeClass('display-none');
                } else {
                    $('#manage_paper2').addClass('display-none');

                }

                $('#commercial_booking2_exclusive_fleet option[value="'+exclusive_fleet+'"]').attr("selected", "selected");
                $('#commercial_booking2_is_circuit option[value="'+is_circuit+'"]').attr("selected", "selected");
            }
        }
    });
}
if ( $("#commercial_booking_carconfig_id").length ) {
    verificateSelectUnique('commercial_booking_carconfig_id')
    verificateSelectUnique('commercial_booking_business_id')
    if ($("#commercial_booking_business_id").val() != "") getRoutes(business_id)
        if ($("#commercial_booking_business_id").val() != "") getFlagsBusiness(business_id)
            $("#commercial_booking_business_id").change(function(){
                getRoutes();
                getFlagsBusiness();
                getConfigVale($(this).val(), false);
            });

        $("#commercial_booking_carconfig_id").on("change", function(){
            getCarConfig($(this).val());
        });    
    }

    if ( $("#carconfig_id2").length ) {
        verificateSelectUnique('carconfig_id2')
        verificateSelectUnique('business_id2')
        verificateSelectUnique('emite2')
        verificateSelectUnique('recibe2')
        verificateSelectUnique('paga2')
        if ($("#business_id2").val() != "") getRoutesDoble(business_id)
            if ($("#business_id2").val() != "") getFlagsBusinessDoble(business_id)
                $("#business_id2").change(function(){
                    getRoutesDoble();
                    getFlagsBusinessDoble();
                    getConfigVale($(this).val(), true);
                });
        }

        if ($('#commercial_booking_has_a_loading_date').length){
            var val = $('#commercial_booking_has_a_loading_date').val();
            if (val == 'true'){
                $("#hasloadingdate").removeClass('display-none');
                $("#nohasloadingdate").addClass('display-none');
                $("#commercial_booking_loading_time_slot").prop('required',false);
                $("#commercial_booking_date2").prop('disabled',true);
                $("#commercial_booking_date").prop('disabled',false);
            }
            else {
                $("#hasloadingdate").addClass('display-none');
                $("#nohasloadingdate").removeClass('display-none');
                $("#commercial_booking_loading_time_slot").prop('required',true);
                $("#commercial_booking_date2").prop('disabled',false);
                $("#commercial_booking_date").prop('disabled',true);
            }
        }
        $('#commercial_booking_has_a_loading_date').on('change', function(){
            var val = $(this).val();
            if (val == 'true'){
                $("#hasloadingdate").removeClass('display-none');
                $("#nohasloadingdate").addClass('display-none');
                $("#commercial_booking_loading_time_slot").prop('required',false);
                $("#commercial_booking_date2").prop('disabled',true);
                $("#commercial_booking_date").prop('disabled',false);
            }
            else {
                $("#hasloadingdate").addClass('display-none');
                $("#nohasloadingdate").removeClass('display-none');
                $("#commercial_booking_loading_time_slot").prop('required',true);
                $("#commercial_booking_date2").prop('disabled',false);
                $("#commercial_booking_date").prop('disabled',true);
            }

        });

    //Viajere Retorno
    if ($('#commercial_booking2_has_a_loading_date').length){
        var val = $('#commercial_booking2_has_a_loading_date').val();
        if (val == 'true'){
            $("#hasloadingdate2").removeClass('display-none');
            $("#nohasloadingdate2").addClass('display-none');
            $("#commercial_booking2_loading_time_slot").prop('required',false);
            $("#commercial_booking2_date2").prop('disabled',true);
            $("#commercial_booking2_date").prop('disabled',false);
        }
        else {
            $("#hasloadingdate2").addClass('display-none');
            $("#nohasloadingdate2").removeClass('display-none');
            $("#commercial_booking2_loading_time_slot").prop('required',true);
            $("#commercial_booking2_date2").prop('disabled',false);
            $("#commercial_booking2_date").prop('disabled',true);
        }
    }
    $('#commercial_booking2_has_a_loading_date').on('change', function(){
        var val = $(this).val();
        if (val == 'true'){
            $("#hasloadingdate2").removeClass('display-none');
            $("#nohasloadingdate2").addClass('display-none');
            $("#commercial_booking2_loading_time_slot").prop('required',false);
            $("#commercial_booking2_date2").prop('disabled',true);
            $("#commercial_booking2_date").prop('disabled',false);
        }
        else {
            $("#hasloadingdate2").addClass('display-none');
            $("#nohasloadingdate2").removeClass('display-none');
            $("#commercial_booking2_loading_time_slot").prop('required',true);
            $("#commercial_booking2_date2").prop('disabled',false);
            $("#commercial_booking2_date").prop('disabled',true);
        }

    });
    $('#commercial_booking_booking_type_id').on('change', function () {
        val = $(this).val();
        if (val == 2) {
            $('#booking_round').removeClass('display-none');
            $("#booking_round :input").prop("disabled", false);
            var element = $("#commercial_booking_carconfig_id").find('option:selected'); 
            var apply_cartype = element.attr("data-apply_cartype"); 
            if (apply_cartype == 'false'){
                $("#cartype_id2").prop('disabled', true);
            } else {
                $("#cartype_id2").prop("disabled", false)
            }
        } else {
            $('#booking_round').addClass('display-none');
            $("#booking_round :input").prop("disabled", true);
            var element = $("#commercial_booking_carconfig_id").find('option:selected'); 
            var apply_cartype = element.attr("data-apply_cartype"); 
            if (apply_cartype == 'false'){
                $("#cartype_id2").prop('disabled', true);
            } else {
                $("#cartype_id2").prop("disabled", false)
            }
        }

    });
    $('#commercial_booking_cantcar, #commercial_booking_cartype_id, #commercial_booking_carconfig_id, #commercial_booking_business_id, #commercial_booking_businessroute_id, #commercial_booking_emite_id, #commercial_booking_recibe_id, #commercial_booking_paga_id, #commercial_booking_charge_for, #commercial_booking_trip_id').on('change', function () {
        val = $(this).val();
        id = $(this).attr("id");
        copies_values(id)

    });
    $('#cantcar2, #route2').on('change', function () {
        val = $(this).val();
        id = $(this).attr("id");
         try {
            if (parseInt($('#route2').val()) > 0){
                if ($('#commercial_booking_booking_type_id').val() == 2){
                    businessroute_id2 = $('#route2').val();
                    border_stamp_return(businessroute_id2);
                    if ($('#borderonly').length){
                        addinputFieldsOnlyBorderStamp2($('#cantcar2').val());
                    }
                }
            }
        } catch (error) {
            // console.error(error);
        }    

    });
    $('.business_new_form_booking').on('change', function () {
        setTimeout(
            function(){
                values = ['commercial_booking_cartype_id', 'commercial_booking_carconfig_id', 'commercial_booking_businessroute_id', 'commercial_booking_emite_id', 'commercial_booking_recibe_id', 'commercial_booking_paga_id', 'commercial_booking_charge_for', 'commercial_booking_trip_id']
                $.each(values, function( index, value ) {
                  copies_values(value)
              });
            }
            , 1000);
    });
    $(document).on('click', '.IsBookingModel', function() {
        var booking_id = $(this).attr('booking_id');
        var check = $(this).attr('is_model');
        $('#booking_id').val(booking_id)
        $('#booking_is_model').val(check)
    });
    $(document).on('click', '.check_is_model', function() {
        var booking =  $(this).attr('booking_id');
        var model = $(this).is(':checked');
        $.ajax({
            url: '/commercial/bookings/update_is_model.json',
            type: "POST",
            async: false,
            data: {booking_id: booking, is_model: model},
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+token );
            },
            success: function(result) {
                if (result.success == false) {
                    alert(result.message);
                }
            }
        });
    });

    function addDays(current_date, days) {
        var dat = new Date(current_date);
        dat.setDate(dat.getDate() + days);
        return dat;
    }

    function getDates(startDate, stopDate) {
        startDate = startDate +' 00:00:00'
        stopDate = stopDate +' 00:00:00'
        var dateArray = new Array();
        var currentDate = new Date(startDate);
        stopDate = new Date(stopDate);
        while (currentDate <= stopDate) {
            dateArray.push(moment(currentDate).format('YYYY-MM-DD'));
            currentDate = addDays(currentDate,1);
        }
        return dateArray;
    }

    function fill_date_planning_booking(dates){
        $.each( booking_ids, function( key, value ) {
            var day = 0;
            $( ".boking_date_" +value ).each(function( i ) {
                var data = [];
                var actual = $(this)
                $.ajax({
                    url: '/commercial/bookings/get_data_day/'+value+'/'+dates[day]+'.json',
                    type: "GET",
                    async: false,
                    'beforeSend': function (request) {
                        request.setRequestHeader("Authorization", 'Bearer '+token );
                    },
                    success: function(result) {
                        if (result.success == true) {
                            data = result.data
                            $(actual).val(data.number_vehicles);
                            $(actual).attr("disabled",data.is_splitted);
                        } else {
                            $(actual).val(0);
                            $(actual).attr("disabled",false);
                        }
                    }
                });
                $(this).attr("date",dates[day]);
                day ++;
            });

        });
    }

    function sum_all_vehicles() {
        var sumv = (r, a) => r.map((b, i) => a[i] + b);
        var data = [];
        $.each( booking_ids, function( key, value ) {
            var arr2 = [];
            var sum_b = 0;
            $( ".boking_date_" +value ).each(function( i ) {
                v = parseInt($(this).val());
                sum_b = sum_b + v
                arr2.push(v);

            });
            $('#booking_total_'+value).html(sum_b);
            $('#vehicles_b'+value).html(sum_b);
            data.push(arr2);
        });
        tota_dates = data.reduce(sumv);
        total = 0;
        $.each( tota_dates, function( key, value ) {
            total = total + value
            $('#total_date_'+key).html(value);
        });
        $('#total_date_all').html(total);
    }


    //Get the value of Start and End of Week
    $('#DateWeekModel').on('dp.change', function (e) {
        var value = $("#weeklyDatePickerModel").val();
        var firstDate = moment(value, "YYYY-MM-DD").day(1).format("YYYY-MM-DD");
        var lastDate =  moment(value, "YYYY-MM-DD").day(7).format("YYYY-MM-DD");
        $("#weeklyDatePickerModel").val(firstDate + " - " + lastDate);
        dates = getDates(firstDate,lastDate)
        fill_date_planning_booking(dates);
        sum_all_vehicles();
    });
    $(document).on('focusin', '.number_vehicle', function() {
        $(this).attr('val', $(this).val());
    });


    $(document).on('change', '.number_vehicle', function() {
        var booking =  $(this).attr('booking_id');
        var fecha = $(this).attr('date');
        var nroVehicle = $(this).val();
        var prev = $(this).attr('val');
        var actual = $(this);
        $.ajax({
            url: '/commercial/bookings/save_bookings_date.json',
            type: "POST",
            async: false,
            data: {booking_id: booking, date: fecha, number_vehicles: nroVehicle},
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+token );
            },
            success: function(result) {
                if (result.success == false) {
                    alert(result.message);
                    $(actual).val(prev);
                }else {
                    sum_all_vehicles();
                }
            }
        });
    });


});
$(function() {
    $("select#booking_change_city").on("change", function() {
        $.ajax({
            url: "addresses/by_city",
            type: "GET",
            data: { city_id: $("select#booking_change_city").val() }
        });
    });
});

function validBookingChangeDestinyForm(){
    var isSuccess = false;
    var booking_change_city = $("#booking_change_city");
    if (booking_change_city.val()) {
        booking_change_city.css("borderColor", "#ccc");
        isSuccess = true;
    }else{
        booking_change_city.css("borderColor", "#ff3c3a");
        $('#err_form').html("Campos incompletos");
        isSuccess = false;
    }
    var booking_change_address = $('#booking_change_address');
    if (booking_change_address.val()) {
        booking_change_address.css("borderColor", "#ccc");
        isSuccess = true;
    }else{
        booking_change_address.css("borderColor", "#FF3C3A");
        $('#err_form').html("Campos incompletos");
        isSuccess = false;
    }
    return isSuccess;
}

function validBookingSelectBigCustomerForm(){
    var booking_big_customer = $("#booking_big_customer");
    var booking_type = $("#booking_type");
    if (booking_big_customer.val() && booking_type.val()) {
        if (booking_type.val() == 1) {
            window.location.href = "../../../commercial/bookings/new/"+booking_big_customer.val();
        } else if (booking_type.val() == 2) {
            window.location.href = "../../../commercial/bookings/new_booking/"+booking_big_customer.val();
        }
    }else{
        booking_big_customer.css("borderColor", "#FF3C3A");
        $('#err_form').html("Campos incompletos");
    }
    return false;
}

function validBookingSelectBigCustomerFormPlanning(){
    var booking_big_customer = $("#booking_big_customer");
    if (booking_big_customer.val()) {
        window.location.href = "/commercial/bookings/planner/"+booking_big_customer.val();
    }else{
        booking_big_customer.css("borderColor", "#FF3C3A");
        $('#err_form').html("Campos incompletos");
    }
    return false;
}

function copies_values(id){
    switch(id) {
        case 'commercial_booking_cantcar':
        $('#cantcar2').val($('#'+id).val())
        try {
            if (parseInt($('#commercial_booking_businessroute_id').val()) > 0){
                businessroute_id = $('#commercial_booking_businessroute_id').val();
                border_stamp(businessroute_id);
            }
        } catch (error) {
            // console.error(error);
        }
        try {
            if ($('#commercial_booking_booking_type_id').val() == 2){
                if (parseInt($('#route2').val()) > 0){
                    businessroute_id2 = $('#route2').val();
                    border_stamp_return(businessroute_id2);
                }
            }            
        } catch (error) {

        }
        addinputFields($('#'+id).val());
        addinputFieldsReturn($('#'+id).val());
        break;
        case 'commercial_booking_cartype_id':
        $('#cartype_id2').val($('#'+id).val())
        break;
        case 'commercial_booking_carconfig_id':
        $('#carconfig_id2').val($('#'+id).val())
        break;
        case 'commercial_booking_business_id':
        $('#business_id2').val($('#'+id).val())
        break;
        case 'commercial_booking_businessroute_id':
        $.ajax({
            dataType: "json",
            cache: false,
            url: '/commercial/businessroutes/reverse_route/'+$('#'+id).val()+'.json',
            timeout: 5000,
            beforeSend: function (request) {
                request.setRequestHeader("Authorization", 'Bearer ' + data_toc);
            },
            error: function(XMLHttpRequest, errorTextStatus, error) {
                console.log("Failed to submit : " + errorTextStatus + " ;" + error);
            },
            success: function(data) {
                if (data.success){
                    $('#route2').val(data.data.id)
                    $('#route2').select2().trigger('change');
                } else {
                    $('#route2').val('')
                    $('#route2').select2().trigger('change');
                }
            }
        });
        try {
            if (parseInt($('#commercial_booking_businessroute_id').val()) > 0){
                businessroute_id = $('#commercial_booking_businessroute_id').val();
                border_stamp(businessroute_id);
                if ($('#borderonly').length){
                    addinputFieldsOnlyBorderStamp($('#commercial_booking_cantcar').val());
                }
            }
        } catch (error) {
            // console.error(error);
        }
        try {
            if (parseInt($('#route2').val()) > 0){
                if ($('#commercial_booking_booking_type_id').val() == 2){
                    if (parseInt($('#route2').val()) > 0){
                        businessroute_id2 = $('#route2').val();
                        border_stamp_return(businessroute_id2);
                        if ($('#borderonly').length){
                            addinputFieldsOnlyBorderStamp2($('#cantcar2').val());
                        }
                    }
                }
            }
        } catch (error) {
            // console.error(error);
        }    
        break;
        case 'commercial_booking_emite_id':
        $('#emite2').val($('#'+id).val())
        break;
        case 'commercial_booking_recibe_id':
        $('#recibe2').val($('#'+id).val())
        break;
        case 'commercial_booking_paga_id':
        $('#paga2').val($('#'+id).val())
        break;
        case 'commercial_booking_charge_for':
        $('#charge_for2').val($('#'+id).val())
        break;
        case 'commercial_booking_trip_id':
        $('#trip_id2').val($('#'+id).val())
        break;

        default:

    }


}

function border_stamp(businessroute_id){
    $.ajax({
        dataType: "json",
        cache: false,
        url: '/commercial/businessroutes/route_border_stamp/'+businessroute_id+'.json',
        timeout: 5000,
        beforeSend: function (request) {
            request.setRequestHeader("Authorization", 'Bearer ' + data_toc);
        },
        error: function(XMLHttpRequest, errorTextStatus, error) {
            console.log("Failed to submit : " + errorTextStatus + " ;" + error);
        },
        success: function(data) {
            if (data.success){
                has_border_stamp = true
                addinputFieldsOnlyBorderStamp($('#commercial_booking_cantcar').val());
            } else {
                has_border_stamp = false
                addinputFieldsOnlyBorderStamp(0);
            }
        }
    });
}

function border_stamp_return(businessroute_id){
    $.ajax({
        dataType: "json",
        cache: false,
        url: '/commercial/businessroutes/route_border_stamp/'+businessroute_id+'.json',
        timeout: 5000,
        beforeSend: function (request) {
            request.setRequestHeader("Authorization", 'Bearer ' + data_toc);
        },
        error: function(XMLHttpRequest, errorTextStatus, error) {
            console.log("Failed to submit : " + errorTextStatus + " ;" + error);
        },
        success: function(data) {
            if (data.success){
                has_border_stamp2 = true
                addinputFieldsOnlyBorderStamp2($('#cantcar2').val());
            } else {
                has_border_stamp2 = false
                addinputFieldsOnlyBorderStamp2($('#cantcar2').val());
            }
        }
    });
}

function getConfigVale(business_id, is_round = false) {
    $.ajax({
        dataType: "json",
        cache: false,
        url: '/commercial/config_bookings/get_default_value_business/'+business_id+'.json',
        timeout: 5000,
        beforeSend: function (request) {
            request.setRequestHeader("Authorization", 'Bearer ' + token);
        },
        error: function(XMLHttpRequest, errorTextStatus, error) {
            console.log("Failed to submit : " + errorTextStatus + " ;" + error);
        },
        success: function(data) {
            if (data.success){

                if (is_round == false){
                    setTimeout(
                        function() 
                        {
                            info = data.data
                            $('#commercial_booking_businessroute_id').val(info['config']['route_id']);
                            $('#commercial_booking_businessroute_id').select2().trigger('change');
                            $('#commercial_booking_emite_id').val(info['config']['emite_id']);
                            $('#commercial_booking_recibe_id').val(info['config']['recibe_id']);
                            $('#commercial_booking_paga_id').val(info['config']['paga_id']);                            
                            $('#commercial_booking_carconfig_id').val(info['config']['carconfig_id']);
                            $('#commercial_booking_cartype_id').val(info['config']['cartype_id']);
                            $('#first_product').val(info['config']['businessproduct_id']);
                            setTimeout(
                                function() 
                                {
                                    info = data.data
                                    $('.plant_origin').val(info['config']['plant_load_id']);
                                    $('.plant_destination').val(info['config']['plant_unload_id']);
                                    $('.plant_origin').trigger('change');
                                    $('.plant_destination').trigger('change');

                                }, 200);

                        }, 200);
                } else {
                    setTimeout(
                        function() 
                        {
                            info = data.data
                            $('#route2').val(info['config']['route_id']);
                            $('#route2').select2().trigger('change');
                            $('#carconfig_id2').val(info['config']['carconfig_id']);
                            $('#cartype_id2').val(info['config']['cartype_id']);
                            $('#emite2').val(info['config']['emite_id']);
                            $('#recibe2').val(info['config']['recibe_id']);
                            $('#paga2').val(info['config']['paga_id']);                            
                            $('#first_product_return').val(info['config']['businessproduct_id']);
                            setTimeout(
                                function() 
                                {
                                    info = data.data
                                    $('#plant_origin_return').val(info['config']['plant_load_id']);
                                    $('#plant_destination_return').val(info['config']['plant_unload_id']);
                                    $('#plant_origin_return').trigger('change');
                                    $('#plant_destination_return').trigger('change');

                                }, 200);

                        }, 200);
                }
                
            }
        }
    });
}

function addinputFields(number){
    try {
        document.getElementById("additional_inputs_from_booking").innerHTML = "";
    } catch(error) {

    }
    document.getElementById("additional_inputs_from_booking_border_stamp").innerHTML = "";
    for (i=0;i<number;i++){
        var $el = $('#additional_inputs_from_booking_html').clone();
        var $border = $('#additional_inputs_from_booking_border_stamp_html').clone();
        $el.removeClass('display-none');
        $el.removeAttr( "id" );
        $el.find('.number_load_id').html(i+1)
        $el.find('.load_id_input').attr('name', 'commercial_booking[booking_detail_attributes]['+i+'][load_id]');
        $el.find('.load_id_input').attr('id', 'load_id_'+i);
        $el.find('#load_id_val').remove();
        $el.find('.trip_id_input').attr('name', 'commercial_booking[booking_detail_attributes]['+i+'][trip_id]');
        $el.find('.trip_id_input').attr('id', 'trip_id_'+i);
        $el.find('#trip_id_val').remove();
        $el.find('.sider_type_id_input').attr('name', 'commercial_booking[booking_detail_attributes]['+i+'][sider_type_id]');
        $el.find('.sider_type_id_input').attr('id', 'sider_type_id_'+i);
        if (choose_sider1==true){
            $el.find('#sider_type').removeClass('display-none');
            $el.find('.sider_type_id_input').attr("disabled",false)
        } else{
            $el.find('#sider_type').addClass('display-none');
            $el.find('.sider_type_id_input').attr("disabled",true)
        }
        try {
            $('#additional_inputs_from_booking').append($el);
        } catch(error) { }
        if (has_border_stamp == true) {
            $('#div_border_stam').removeClass('display-none');
            $border.removeClass('display-none');
            $border.removeAttr( "id" );
            $border.find('.number_border').html(i+1)
            $border.find('.border_stamp_number').attr('name', 'commercial_booking[booking_detail_attributes]['+i+'][border_stamp]');
            $border.find('.border_stamp_number').attr('id', 'border_stamp_'+i);
            $border.find('#border_stamp_number_val').remove();
            $('#additional_inputs_from_booking_border_stamp').append($border);    
        } else {
            $('#div_border_stam').addClass('display-none');
        }
        
    }
    $(document).trigger('refresh_autonumeric');
}

function addinputFieldsReturn(number){
    try {
        document.getElementById("additional_inputs_from_booking2").innerHTML = "";
    } catch (e) {}
    document.getElementById("additional_inputs_from_booking_border_stamp2").innerHTML = "";
    for (i=0;i<number;i++){
        var $el = $('#additional_inputs_from_booking_html2').clone();
        var $border = $('#additional_inputs_from_booking_border_stamp_html').clone();
        $el.removeClass('display-none');
        $el.removeAttr( "id" );
        $el.find('.number_load_id').html(i+1)
        $el.find('.load_id_input').attr('name', 'commercial_booking2[booking_detail_attributes]['+i+'][load_id]');
        $el.find('.load_id_input').attr('id', 'load_id2_'+i);
        $el.find('#load_id_val').remove();
        $el.find('.trip_id_input').attr('name', 'commercial_booking2[booking_detail_attributes]['+i+'][trip_id]');
        $el.find('.trip_id_input').attr('id', 'trip_id2_'+i);
        $el.find('#trip_id_val').remove();
        $el.find('.sider_type_id_input').attr('name', 'commercial_booking2[booking_detail_attributes]['+i+'][sider_type_id]');
        $el.find('.sider_type_id_input').attr('id', 'sider_type_id2_'+i);
        if (choose_sider2==true){
            $el.find('#sider_type').removeClass('display-none');
            $el.find('.sider_type_id_input').attr("disabled",false)
        } else{
            $el.find('#sider_type').addClass('display-none');
            $el.find('.sider_type_id_input').attr("disabled",true)
        }
        if ($('#commercial_booking_booking_type_id').val() != 2){
            $el.find(":input").prop("disabled", true);
        } 
        try {
            $('#additional_inputs_from_booking2').append($el);
        } catch(e){}
        if (has_border_stamp2 == true) {
            $('#div_border_stam2').removeClass('display-none');
            $border.removeClass('display-none');
            $border.removeAttr( "id" );
            $border.find('.number_border').html(i+1)
            $border.find('.border_stamp_number').attr('name', 'commercial_booking2[booking_detail_attributes]['+i+'][border_stamp]');
            $border.find('.border_stamp_number').attr('id', 'border_stamp2_'+i);
            $border.find('#border_stamp_number_val').remove();
            $('#additional_inputs_from_booking_border_stamp2').append($border);    
        } else {
            $('#div_border_stam2').addClass('display-none');
        }
    }


    $(document).trigger('refresh_autonumeric');
}

function addinputFieldsOnlyBorderStamp(number){
    document.getElementById("additional_inputs_from_booking_border_stamp").innerHTML = "";
    $('#div_border_stam').addClass('display-none');
    for (i=0;i<number;i++){
        var $border = $('#additional_inputs_from_booking_border_stamp_html').clone();
        if (has_border_stamp == true) {
            $('#div_border_stam').removeClass('display-none');
            $border.removeClass('display-none');
            $border.removeAttr( "id" );
            $border.find('.number_border').html(i+1)
            $border.find('.border_stamp_number').attr('name', 'commercial_booking[booking_detail_attributes]['+i+'][border_stamp]');
            $border.find('.border_stamp_number').attr('id', 'border_stamp_'+i);
            $border.find('#border_stamp_number_val').remove();
            $('#additional_inputs_from_booking_border_stamp').append($border);    
        } else {
            $('#div_border_stam').addClass('display-none');
        }
        
    }
    $(document).trigger('refresh_autonumeric');
}
function addinputFieldsOnlyBorderStamp2(number){
    console.log('0=====000')
    console.log(number)
    console.log(has_border_stamp2)
    document.getElementById("additional_inputs_from_booking_border_stamp2").innerHTML = "";
    $('#div_border_stam2').addClass('display-none');
    for (i=0;i<number;i++){
        var $border = $('#additional_inputs_from_booking_border_stamp_html').clone();
        if (has_border_stamp2 == true) {
            $('#div_border_stam2').removeClass('display-none');
            $border.removeClass('display-none');
            $border.removeAttr( "id" );
            $border.find('.number_border').html(i+1)
            $border.find('.border_stamp_number').attr('name', 'commercial_booking2[booking_detail_attributes]['+i+'][border_stamp]');
            $border.find('.border_stamp_number').attr('id', 'border_stamp2_'+i);
            $border.find('#border_stamp_number_val').remove();
            $('#additional_inputs_from_booking_border_stamp2').append($border);    
        } else {
            $('#div_border_stam2').addClass('display-none');
        }
        
    }
    $(document).trigger('refresh_autonumeric');
}
;
function standbyvalueRequired(value) {
    if (value == "1") { 
        $('#commercial_business_standbyvalue').attr("required", "true")
    } else {
        $('#commercial_business_standbyvalue').attr("required", false)
    }
}

$( document ).on('turbolinks:load', function() {
    $('#commercial_business_standby').attr("required", "true")
    standbyvalueRequired( $('#commercial_business_standby').val() )

    $("#commercial_business_standby").change(function(){
        standbyvalueRequired( $('#commercial_business_standby').val() )
    });

    $('#bookings').DataTable( {
        "processing": true,
        "serverSide": true,
        "pageLength": 25,
        "cache": false,
        "language": { "url": "/assets/datatables/datatables_spanish.json" },
        "ajax": {
            'url': "/commercial/bookings/get/"+$('#bookings').attr('data-id')+".json",
            'type': 'GET',
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+$('#bookings').attr('data-toc') );
            }
        }
    } );
});
function getContent (element, valor='') {
    if (element=="#productcharacter_val") {
        type = 1
        url = '/productclassifications/get_productclassification_by_productcharacters/'+$(element).val()+".json"
        element2 = "#productclassification_val"
    } else {
        type = 2
        aux = ( $(element).val() != '' ? $(element).val() : productcode_id_val)
        url = '/productcodes/get_code_by_productclassification/'+aux+".json"
        element2 = "#commercial_businessproduct_productcode_id"
    }
    $.ajax({
        dataType: "json",
        cache: false,
        url: url,
        timeout: 5000,
        beforeSend: function (request) {
            request.setRequestHeader("Authorization", 'Bearer <%= current_user.token %>');
        },
        error: function(XMLHttpRequest, errorTextStatus, error) {
            alert("Failed to submit : " + errorTextStatus + " ;" + error);
        },
        success: function(data) { 
            if (data.success){
                if (element=="#productcharacter_val") {
                    $("#commercial_businessproduct_productcode_id").children().remove();
                    $("#commercial_businessproduct_productcode_id").append('<option value="">Seleccione:</option>');
                }
                $(element2).children().remove();
                $(element2).append('<option value="">Seleccione:</option>');
                $.each(data.data, function(i, j) {
                    aux = ( valor == ''+j.id ? ' selected' : '')
                    value = (type == 1) ?  j.name + " " + j.value :  j.code + " " + j.value
                    $(element2).append( "<option value=\"" + j.id + "\" "+aux+">" + value + "</option>" );
                });
                $(element2).focus()
            }
        }
    });
}


    

$( document ).on('turbolinks:load', function() {

    $('#businessproducts').DataTable( {
        "processing": true,
        "serverSide": true,
        "pageLength": 25,
        "cache": false,
        "language": { "url": "/assets/datatables/datatables_spanish.json" },
        "ajax": {
            'url': "/commercial/businessproducts/get/"+$('#businessproducts').attr('data-id')+".json",
            'type': 'GET',
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+$('#businessproducts').attr('data-toc') );
            }
        }
    } );

    function inicializar() {
        element2 = "#productclassification_val"
        $.ajax({
            dataType: "json",
            cache: false,
            url: '/productclassifications/get_productclassification_by_productcharacters/'+$("#productcharacter_val").val()+".json",
            timeout: 5000,
            beforeSend: function (request) {
                request.setRequestHeader("Authorization", 'Bearer <%= current_user.token %>');
            },
            error: function(XMLHttpRequest, errorTextStatus, error) {
                alert("Failed to submit : " + errorTextStatus + " ;" + error);
            },
            success: function(data) { 
                if (data.success){
                    $.each(data.data, function(i, j) {
                        $(element2).append( "<option value=\"" + j.id + "\""+( productclassification_val == ''+j.id ? ' selected' : '')+">" + j.value + "</option>" );
                    });
                    element2 = "#commercial_businessproduct_productcode_id"
                    $.ajax({
                        dataType: "json",
                        cache: false,
                        url: '/productcodes/get_code_by_productclassification/'+$("#productclassification_val").val()+".json",
                        timeout: 5000,
                        beforeSend: function (request) {
                            request.setRequestHeader("Authorization", 'Bearer <%= current_user.token %>');
                        },
                        error: function(XMLHttpRequest, errorTextStatus, error) {
                            alert("Failed to submit : " + errorTextStatus + " ;" + error);
                        },
                        success: function(data) { 
                            if (data.success){
                                $.each(data.data, function(i, j) {
                                    $(element2).append( "<option value=\"" + j.id + "\""+( productcode_id_val == ''+j.id ? ' selected' : '')+">" + j.value + "</option>" );
                                });
                                $(element2).focus()
                            }
                        }
                    });
                }
            }
        });
    }
      
      
    if ( $("#productcharacter_val").length ) {
        if ( $("#productcharacter_val").val() != "" ) {
            inicializar()
        }
    
        $("#productcharacter_val").change(function(){
            getContent ("#productcharacter_val")
        });
    
        $("#productclassification_val").change(function(){
            getContent ("#productclassification_val")
        });
    }


    var getSelectInfoSiderProducts;

    getSelectInfoSiderProducts = function(name, valor) {
      var aux, url, val;
      if (valor == null) {
        valor = '';
      }
      url = (function() {
        switch (name) {
          case "business_sider_products_id":
            return '/commercial/businesses/getbusinessfrombigcustomer/' +  $('#bigcustomer_product_id').val();;
          case "businessproducts_sider":
            return '/commercial/businessproducts/getbusinessproductsfrombussiness/' + $('#business_sider_products_id').val();
        }
      })();
      val = (function() {
        switch (name) {
          case "business_sider_products_id":
            return 'description';
          case "businessproducts_sider":
            return 'name';
        }
      })();
      name = "#" + name;
      aux = '';
      valor = parseInt(valor);
      return $.ajax(url, {
        type: 'GET',
        dataType: 'json',
        beforeSend: function(request) {
          return request.setRequestHeader('Authorization', 'Bearer ' + token);
        },
        success: function(data, textStatus) {
          data = data.data;
          $.each(data, function(index, item) {
            if (valor !== "") {
              aux = '';
              if (item.id === valor) {
                aux = ' selected';
              }
            }
            return $(name).append('<option value="' + item.id + '"' + aux + '>' + item[val] + '</option>');
          });
          return $(name).focus();
        }
      });
    };

    var completeSelect;

    completeSelect = function(name, data, valor) {
        valor = parseInt(valor);
        $.each(data, function(index, item) {
            var aux;
            aux = '';
            if (item.id === valor) {
              aux = ' selected';
            }
            return $(name).append('<option value="' + item.id + '"' + aux + '>' + item.name + '</option>');
        });
        return $(name).focus();
    };

    var resetSelect;

    resetSelect = function(element) {
      $(element).children().remove();
      return $(element).append('<option value="">Seleccione:</option>');
    };


    $('#bigcustomer_product_id').on('change', function() {
      var data;
      resetSelect("#business_sider_products_id");
      resetSelect("#businessproducts_sider");
      data = $('#bigcustomer_product_id option:selected').val();
      return getSelectInfoSiderProducts('business_sider_products_id', data);
    });

    $('#business_sider_products_id').on("change", function() {
      resetSelect("#businessproducts_sider");
      return getSelectInfoSiderProducts('businessproducts_sider');
    });

    if (typeof product_id !== "undefined" && product_id !== null) {
      if (product_id !== "") {
        completeSelect("#business_sider_products_id", businesses, business);
        completeSelect("#businessproducts_sider", products, product_id);
      }
    }
    
});
(function() {
  var completeSelect, getSelectInfo, getSelectInfoEditFlet, getSelectInfoFilter, getSelectInfoSiderProducts, resetSelect;

  resetSelect = function(element) {
    $(element).children().remove();
    return $(element).append('<option value="">Seleccione:</option>');
  };

  getSelectInfo = function(name, valor) {
    var aux, url;
    if (valor == null) {
      valor = '';
    }
    url = (function() {
      switch (name) {
        case "department_rfrom":
          return '/system/tables/departments/fromroute/-1';
        case "department_rto":
          return '/system/tables/departments/fromroute/' + $('#city_from').val();
        case "city_rfrom":
          return '/system/tables/cities/fromroute/' + $('#department_rfrom').val();
        case "city_from":
          return '/system/tables/cities/fromroutelocalities/' + $('#city_rfrom').val();
        case "city_rto":
          return '/system/tables/cities/toroute/' + $('#city_from').val() + "/" + $('#department_rto').val();
        case "city_to":
          return '/system/tables/cities/toroutelocalities/' + $('#city_from').val() + "/" + $('#city_rto').val();
        case "commercial_businessroute_pathroute_id":
          return '/system/tables/pathroutes/fromroute/' + $('#city_from').val() + '/' + $('#city_to').val();
        case "sicetac1":
          return '/commercial/businessroutes/get_sicetac_costs/' + $('#city_from').val() + '/' + $('#city_to').val();
        case "sicetac2":
          return '/commercial/businessroutes/getsicetacfrombusinessroute/' + $('#businessroutes').val();
      }
    })();
    name = "#" + name;
    aux = '';
    valor = parseInt(valor);
    return $.ajax(url, {
      type: 'GET',
      dataType: 'json',
      success: function(data, textStatus) {
        data = data.data;
        $.each(data, function(index, item) {
          if (valor !== "") {
            aux = '';
            if (item.id === valor) {
              aux = ' selected';
            }
          }
          return $(name).append('<option value="' + item.id + '"' + aux + '>' + item.name + '</option>');
        });
        return $(name).focus();
      }
    });
  };

  getSelectInfoFilter = function(name, valor) {
    var aux, url;
    if (valor == null) {
      valor = '';
    }
    url = (function() {
      switch (name) {
        case "department_rfromfilter":
          return '/system/tables/departments/fromroute/-1';
        case "department_rtofilter":
          return '/system/tables/departments/fromroute/' + $('#city_fromfilter').val();
        case "city_rfromfilter":
          return '/system/tables/cities/fromroute/' + $('#department_rfromfilter').val();
        case "city_fromfilter":
          return '/system/tables/cities/fromroutelocalities/' + $('#city_rfromfilter').val();
        case "city_rtofilter":
          return '/system/tables/cities/toroute/' + $('#city_fromfilter').val() + "/" + $('#department_rtofilter').val();
        case "city_tofilter":
          return '/system/tables/cities/toroutelocalities/' + $('#city_fromfilter').val() + "/" + $('#city_rtofilter').val();
        case "commercial_businessroute_pathroute_idfilter":
          return '/system/tables/pathroutes/fromroute/' + $('#city_fromfilter').val() + '/' + $('#city_tofilter').val();
      }
    })();
    name = "#" + name;
    aux = '';
    valor = parseInt(valor);
    return $.ajax(url, {
      type: 'GET',
      dataType: 'json',
      success: function(data, textStatus) {
        data = data.data;
        $.each(data, function(index, item) {
          if (valor !== "") {
            aux = '';
            if (item.id === valor) {
              aux = ' selected';
            }
          }
          return $(name).append('<option value="' + item.id + '"' + aux + '>' + item.name + '</option>');
        });
        return $(name).focus();
      }
    });
  };

  getSelectInfoEditFlet = function(name, valor) {
    var aux, url, val;
    if (valor == null) {
      valor = '';
    }
    url = (function() {
      switch (name) {
        case "business":
          return '/commercial/businesses/getbusinessfrombigcustomer/' + valor;
        case "business_own":
          return '/commercial/businesses/getbusinessfrombigcustomerown/' + valor;
        case "businessroutes":
          return '/commercial/businessroutes/getbusinessroutesfrombussiness/' + valor;
      }
    })();
    val = (function() {
      switch (name) {
        case "business":
          return 'description';
        case "business_own":
          return 'description';
        case "businessroutes":
          return 'name';
      }
    })();
    name = "#" + name;
    aux = '';
    valor = parseInt(valor);
    return $.ajax(url, {
      type: 'GET',
      dataType: 'json',
      beforeSend: function(request) {
        return request.setRequestHeader('Authorization', 'Bearer ' + token);
      },
      success: function(data, textStatus) {
        data = data.data;
        $.each(data, function(index, item) {
          if (valor !== "") {
            aux = '';
            if (item.id === valor) {
              aux = ' selected';
            }
          }
          return $(name).append('<option value="' + item.id + '"' + aux + '>' + item[val] + '</option>');
        });
        return $(name).focus();
      }
    });
  };

  getSelectInfoSiderProducts = function(name, valor) {
    var aux, url, val;
    if (valor == null) {
      valor = '';
    }
    url = (function() {
      switch (name) {
        case "business":
          return '/commercial/businesses/getbusinessfrombigcustomer/' + valor;
        case "businessroutes":
          return '/commercial/businessroutes/getbusinessroutesfrombussiness/' + $('#business').val();
      }
    })();
    val = (function() {
      switch (name) {
        case "business":
          return 'description';
        case "businessroutes":
          return 'name';
      }
    })();
    name = "#" + name;
    aux = '';
    valor = parseInt(valor);
    return $.ajax(url, {
      type: 'GET',
      dataType: 'json',
      beforeSend: function(request) {
        return request.setRequestHeader('Authorization', 'Bearer ' + token);
      },
      success: function(data, textStatus) {
        data = data.data;
        $.each(data, function(index, item) {
          if (valor !== "") {
            aux = '';
            if (item.id === valor) {
              aux = ' selected';
            }
          }
          return $(name).append('<option value="' + item.id + '"' + aux + '>' + item[val] + '</option>');
        });
        return $(name).focus();
      }
    });
  };

  completeSelect = function(name, data, valor) {
    valor = parseInt(valor);
    $.each(data, function(index, item) {
      var aux;
      aux = '';
      if (item.id === valor) {
        aux = ' selected';
      }
      return $(name).append('<option value="' + item.id + '"' + aux + '>' + item.name + '</option>');
    });
    return $(name).focus();
  };

  $(document).on('turbolinks:load', function() {
    $('#commercial_businessroute_pathroute_id').attr("required", "true");
    if (typeof pathroute_id !== "undefined" && pathroute_id !== null) {
      if (pathroute_id !== "") {
        getSelectInfo('department_rfrom', dpto_from_id);
        completeSelect("#city_rfrom", cities_rfrom, city_rfrom_id);
        completeSelect("#city_from", cities_from, city_from_id);
        completeSelect("#department_rto", departments_to, dpto_to_id);
        completeSelect("#city_rto", cities_rto, city_rto_id);
        completeSelect("#city_to", cities_to, city_to_id);
        completeSelect("#commercial_businessroute_pathroute_id", pathroutes, pathroute_id);
        getSelectInfo('department_rfrom');
        getSelectInfo('sicetac1', '');
      } else {
        getSelectInfo('department_rfrom');
      }
    }
    if (typeof pathroutefilter_id !== "undefined" && pathroutefilter_id !== null) {
      if (pathroutefilter_id !== "") {
        getSelectInfoFilter('department_rfromfilter', dpto_from_id);
        completeSelect("#city_rfromfilter", cities_rfrom, city_rfrom_id);
        completeSelect("#city_fromfilter", cities_from, city_from_id);
        completeSelect("#department_rtofilter", departments_to, dpto_to_id);
        completeSelect("#city_rtofilter", cities_rto, city_rto_id);
        completeSelect("#city_tofilter", cities_to, city_to_id);
        completeSelect("#commercial_businessroute_pathroute_idfilter", pathroutes, pathroutefilter_id);
      } else {
        getSelectInfoFilter('department_rfromfilter');
      }
    }
    if (typeof ruta_id !== "undefined" && ruta_id !== null) {
      if (ruta_id !== "") {
        completeSelect("#business", businesses, business);
        completeSelect("#businessroutes", rutas, ruta_id);
      }
    }
    $('#department_rfrom').on("change", function() {
      resetSelect("#city_rfrom");
      resetSelect("#city_from");
      resetSelect("#department_rto");
      resetSelect("#city_to");
      resetSelect("#commercial_businessroute_pathroute_id");
      return getSelectInfo('city_rfrom');
    });
    $('#city_rfrom').on("change", function() {
      resetSelect("#city_from");
      resetSelect("#department_rto");
      resetSelect("#city_to");
      resetSelect("#commercial_businessroute_pathroute_id");
      return getSelectInfo('city_from');
    });
    $('#city_from').on("change", function() {
      resetSelect("#department_rto");
      resetSelect("#city_to");
      resetSelect("#commercial_businessroute_pathroute_id");
      return getSelectInfo('department_rto');
    });
    $('#department_rto').on("change", function() {
      resetSelect("#city_rto");
      resetSelect("#city_to");
      resetSelect("#commercial_businessroute_pathroute_id");
      return getSelectInfo('city_rto');
    });
    $('#city_rto').on("change", function() {
      resetSelect("#city_to");
      resetSelect("#commercial_businessroute_pathroute_id");
      return getSelectInfo('city_to');
    });
    $('#city_to').on("change", function() {
      resetSelect("#commercial_businessroute_pathroute_id");
      return getSelectInfo('commercial_businessroute_pathroute_id');
    });
    $('#department_rfromfilter').on("change", function() {
      resetSelect("#city_rfromfilter");
      resetSelect("#city_fromfilter");
      resetSelect("#department_rtofilter");
      resetSelect("#city_tofilter");
      resetSelect("#commercial_businessroute_pathroute_idfilter");
      return getSelectInfoFilter('city_rfromfilter');
    });
    $('#city_rfromfilter').on("change", function() {
      resetSelect("#city_fromfilter");
      resetSelect("#department_rtofilter");
      resetSelect("#city_tofilter");
      resetSelect("#commercial_businessroute_pathroute_idfilter");
      return getSelectInfoFilter('city_fromfilter');
    });
    $('#city_fromfilter').on("change", function() {
      resetSelect("#department_rtofilter");
      resetSelect("#city_tofilter");
      resetSelect("#commercial_businessroute_pathroute_idfilter");
      return getSelectInfoFilter('department_rtofilter');
    });
    $('#department_rtofilter').on("change", function() {
      resetSelect("#city_rtofilter");
      resetSelect("#city_tofilter");
      resetSelect("#commercial_businessroute_pathroute_idfilter");
      return getSelectInfoFilter('city_rtofilter');
    });
    $('#city_rtofilter').on("change", function() {
      resetSelect("#city_tofilter");
      resetSelect("#commercial_businessroute_pathroute_idfilter");
      return getSelectInfoFilter('city_tofilter');
    });
    $('#city_tofilter').on("change", function() {
      resetSelect("#commercial_businessroute_pathroute_idfilter");
      return getSelectInfoFilter('commercial_businessroute_pathroute_idfilter');
    });
    $('#bigcustomer_id').on('change', function() {
      var data;
      resetSelect("#business");
      resetSelect("#businessroutes");
      data = $('#bigcustomer_id option:selected').val();
      return getSelectInfoEditFlet('business', data);
    });
    $('#bigcustomer_id_for_own').on('change', function() {
      var data;
      resetSelect("#business_own");
      resetSelect("#businessroutes");
      data = $('#bigcustomer_id_for_own option:selected').val();
      return getSelectInfoEditFlet('business_own', data);
    });
    $('#business').on("change", function() {
      var data;
      resetSelect("#businessroutes");
      data = $('#business option:selected').val();
      return getSelectInfoEditFlet('businessroutes', data);
    });
    $('#business_own').on("change", function() {
      var data;
      resetSelect("#businessroutes");
      data = $('#business_own option:selected').val();
      return getSelectInfoEditFlet('businessroutes', data);
    });
    $('#commercial_businessroute_negotiatedby_id').on("change", function() {
      var negotiated;
      console.log('negocio' + $('#commercial_businessroute_business_id').val());
      negotiated = $('#commercial_businessroute_negotiatedby_id option:selected').val();
      return $.ajax('/commercial/businessroutes/get_value_min/' + $('#city_rfrom').val() + '/' + $('#city_rto').val() + '/' + $('#commercial_businessroute_business_id').val(), {
        type: 'GET',
        dataType: 'json',
        beforeSend: function(request) {
          return request.setRequestHeader('Authorization', 'Bearer ' + token);
        },
        success: function(data, textStatus) {
          data = data.data;
          $('#commercial_businessroute_flete_max').val(data);
          return $('#value_sicetac').val(data);
        }
      });
    });
    $('#commercial_businessroute_flete_max').on("change", function() {
      var sicetac, value_new;
      sicetac = parseFloat($('#value_sicetac').val());
      value_new = parseFloat($(this).val().replace(',', ''));
      if (value_new < sicetac) {
        alert('valor menor que tabla SiceTac ' + sicetac);
        return $('#commercial_businessroute_flete_max').val(sicetac);
      }
    });
    $('#sicetac1').on("change", function() {
      var negotiated;
      negotiated = parseFloat($('#sicetac1').val());
      console.log('cambio el valor' + negotiated);
      $('#commercial_businessroute_flete_max').val(negotiated);
      return $('#commercial_businessroute_flete_max').prop("disabled", true);
    });
    $('#flete_max').on("change", function() {
      var sicetac, value_new;
      sicetac = parseFloat($('#value_sicetac').val());
      value_new = parseFloat($(this).val().replace(',', ''));
      if (value_new < sicetac) {
        alert('valor menor que tabla SiceTac ' + sicetac);
        return $('#commercial_businessroute_flete_max').val(sicetac);
      }
    });
    $('#sicetac2').on("change", function() {
      var negotiated;
      negotiated = parseFloat($('#sicetac2').val());
      $('#flete_max').val(negotiated);
      return $('#flete_max').prop("disabled", true);
    });
    return $('#businessroutes').on('change', function() {
      return $.ajax('/commercial/businessroutes/get_value/' + $('#businessroutes').val(), {
        type: 'GET',
        dataType: 'json',
        beforeSend: function(request) {
          return request.setRequestHeader('Authorization', 'Bearer ' + token);
        },
        success: function(data, textStatus) {
          data = data.data;
          $('#flete_max').val(data);
          $('#flete_max').focus();
          resetSelect("#sicetac2");
          return getSelectInfo('sicetac2', '');
        }
      });
    });
  });

}).call(this);
$( document ).on('turbolinks:load', function() {
	function getRoutesAll(business_id, select_id, valor = '') {
		$.ajax({
			dataType: "json",
			cache: false,
			url: '/commercial/businessroutes/getroutes/'+business_id+'.json',
			timeout: 5000,
			beforeSend: function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+token);
			},
			error: function(XMLHttpRequest, errorTextStatus, error) {
				console.log("Failed to submit : " + errorTextStatus + " ;" + error);
			},
			success: function(data) { 
				console.log(data)
				if (data.length){
					$("#" + select_id).children().remove();
					$("#" + select_id).append('<option value="">Seleccione:</option>');

					$.each(data, function(i, j) {
						aux = ( valor == ''+j.id ? ' selected' : '')
						$("#" + select_id).append( "<option value=\"" + j.id + "\""+aux+">" + j.id + " -  " + j.name + "</option>" );
						
					});

				}
			}
		});
	}

	function getProductsAll(business_id, select_id, valor = '') {
		$.ajax({
			dataType: "json",
			cache: false,
			url: '/commercial/businessproducts/getbusinessproductsfrombussiness/'+business_id+'.json',
			timeout: 5000,
			beforeSend: function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+token);
			},
			error: function(XMLHttpRequest, errorTextStatus, error) {
				console.log("Failed to submit : " + errorTextStatus + " ;" + error);
			},
			success: function(data) { 
				console.log(data)
				if (data.success){
					$("#" + select_id).children().remove();
					$("#" + select_id).append('<option value="">Seleccione:</option>');

					$.each(data.data, function(i, j) {
						aux = ( valor == ''+j.id ? ' selected' : '')
						$("#" + select_id).append( "<option value=\"" + j.id + "\""+aux+">" + j.id + " -  " + j.name + "</option>" );
						
					});

				}
			}
		});
	}

	function getPlantsConfig(businessroute_id, bigcustomer_id, select_id_load, select_id_unload, valorload = '', valorunload = '') {
		$.ajax({
			dataType: "json",
			cache: false,
			url: '/commercial/config_bookings/get_plants_config/'+businessroute_id+'/'+bigcustomer_id+'.json',
			timeout: 5000,
			beforeSend: function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+token);
			},
			error: function(XMLHttpRequest, errorTextStatus, error) {
				console.log("Failed to submit : " + errorTextStatus + " ;" + error);
			},
			success: function(data) {
				var plant_loads = data.data.plant_loads
				var plant_unloads = data.data.plant_unloads
				if (data.success){
					console.log('21345654543')
					$("#" + select_id_load).children().remove();
					$("#" + select_id_load).append('<option value="">Seleccione:</option>');
					$("#" + select_id_unload).children().remove();
					$("#" + select_id_unload).append('<option value="">Seleccione:</option>');


					$.each(plant_loads, function(i, j) {
						aux = ( valorload == ''+j.id ? ' selected' : '')
						$("#" + select_id_load).append( "<option value=\"" + j.id + "\""+aux+">" + j.id + " -  " + j.name + "</option>" );
						
					});

					$.each(plant_unloads, function(i, j) {
						aux = ( valorunload == ''+j.id ? ' selected' : '')
						$("#" + select_id_unload).append( "<option value=\"" + j.id + "\""+aux+">" + j.id + " -  " + j.name + "</option>" );
						
					});

				}
			}
		});
	}

	if ($("#business_id_config_booking").length){
		getRoutesAll($("#business_id_config_booking").val(), 'config_route_id', route);
		getProductsAll($("#business_id_config_booking").val(), 'config_businessproduct_id', product);
		getPlantsConfig(route, $('#commercial_config_booking_bigcustomer_id').val(), 'plant_load_id', 'plant_unload_id', plant_load ,plant_unload) 
	}

	if ($('#config_route_id').length){
		$('#config_route_id').on('change', function(){
			getPlantsConfig($("#config_route_id").val(), $('#commercial_config_booking_bigcustomer_id').val(), 'plant_load_id', 'plant_unload_id', $('#plant_load_id').val(), $('#plant_unload_id').val()) 
		});
	}

	if ($('#carconfig_id_config_booking').length){
		$('#carconfig_id_config_booking').on('change', function(){
			var element = $("#carconfig_id_config_booking").find('option:selected'); 
            var apply_cartype = element.attr("data-apply_cartype"); 
            if (apply_cartype == 'false'){
                $("#cartype_id_config_booking").val('237')
                $("#cartype_id_config_booking").prop('disabled', true);
            } else {
                $("#cartype_id_config_booking").prop("disabled", false)
                $("#cartype_id_config_booking").val("")
            }
		})
	}
});

$(document).on('turbolinks:load', function() {

	$('#AddEvent').on('show.bs.modal', function (e) {
		if ($('#doc_externo_request_data').length){
			if ($.trim($('#doc_externo_request_data').val()).length == 0){
				e.preventDefault();
				alert('Debe llenar la información del documento externo y guardar los datos')
			}
		}
	});
	$("#comply_detail_cost_change").on('change',function(){
		var element = $(this).find('option:selected'); 
		var cost = element.attr("data-cost"); 
		var provider_id = element.attr("data-provider");
		$('#comply_detail_cost_form').val(cost); 
		$('#comply_detail_provider_form').val(provider_id); 
	});

	$("#comply_detail_toll_cost_change").on('change',function(){ 
		var element = $(this).find('option:selected'); 
		var cost = element.attr("data-cost"); 
		$('#comply_detail_toll_cost_form').val(cost); 
	});

	// Cambio de peso Cargado
	$("#load_weight_comply, #download_weight_comply, #merma_comply").on('focusin', 'input', function(){
		console.log("Saving value " + $(this).val());
		$('#load_weight_comply').data('val', $('#load_weight_comply').val());
		$('#download_weight_comply').data('val', $('#download_weight_comply').val());
		$('#merma_comply').data('val', $('#merma_comply').val());
	})

	// Cambio de peso Cargado
	$("#load_weight_comply_create").on('change',function(){ 
		load = parseInt($(this).val().replace(/\,/g, ''));
		download = parseInt($('#download_weight_comply_create').val().replace(/\,/g, ''));
		missing = download - load 
		if (download > (load + 2000)){
			alert('Peso descargado no puede ser mayor a peso cargado');
			$('#download_weight_comply_create').val(formatNumber(load));
			$('#download_weight_comply_create').attr("value", load);	
			$('#download_weight_comply_val_create').attr("value", load);	
			$('#merma_comply_create').attr("value", "0");
			$('#merma_comply_val_create').attr("value", "0");
			$('#merma_comply_create').val("0")
		} else {
			$('#download_weight_comply_create').val(formatNumber(download));
			$('#download_weight_comply_create').attr("value", download);	
			$('#download_weight_comply_val_create').attr("value", download);	
			$('#merma_comply_create').attr("value", missing);
			$('#merma_comply_val_create').attr("value", missing);
			$('#merma_comply_create').val(formatNumber(missing))
		}
	});

	// Cambio de Peso Descargado
	$("#download_weight_comply_create").on('change',function(){ 
		load = parseInt($('#load_weight_comply_create').val().replace(/\,/g, ''));
		download = parseInt($(this).val().replace(/\,/g, ''));
		missing = download - load
		if (download > (load + 2000)){
			alert('Peso descargado no puede ser mayor a peso cargado');
			$('#download_weight_comply_create').val(formatNumber(load));
			$('#download_weight_comply_create').attr("value", load);	
			$('#download_weight_comply_create_val').attr("value", load);	
			$('#merma_comply_create').attr("value", "0");
			$('#merma_comply_create_val').attr("value", "0");
			$('#merma_comply_create').val("0")
		} else {
			$('#download_weight_comply_create').val(formatNumber(download));
			$('#download_weight_comply_create').attr("value", download);	
			$('#download_weight_comply_val_create').attr("value", download);	
			$('#merma_comply_create').attr("value", missing);
			$('#merma_comply_val_create').attr("value", missing);
			$('#merma_comply_create').val(formatNumber(missing))
		}
	});

	// Cambio de Peso Merma
	$("#merma_comply_create").on('change',function(){
		load = parseInt($('#load_weight_comply_create').val().replace(/\,/g, ''));
		missing = parseInt($('#merma_comply_create').val().replace(/\,/g, ''));
		download = load - missing;
		if ((missing - 2000) > load){
			alert('faltantes no puede ser mayor a peso cargado');
			$('#download_weight_comply_create').val(load);
			$('#download_weight_comply_create').attr("value", load);	
			$('#download_weight_comply_create_val').attr("value", load);	
			$('#merma_comply_create').attr("value", "0");
			$('#merma_comply_create_val').attr("value", "0");
			$('#merma_comply_create').val("0")
		} else {
			$('#download_weight_comply_create').val(formatNumber(download));
			$('#download_weight_comply_create').attr("value", download);	
			$('#download_weight_comply_create_val').attr("value", download);	
			$('#merma_comply_create').attr("value", missing);
			$('#merma_comply_create_val').attr("value", missing);
			$('#merma_comply_create').val(formatNumber(missing))
		}
	});

	$('#charge_missing_create').on('change',function(){
		missing_create = $(this).is(':checked');
		if (missing_create){
			$('#tolerance_comply_create').attr('disabled', false);
		} else {
			$('#tolerance_comply_create').attr('disabled', true);
		}
	});

	$('.attach_invoice').on('click', function(e){
		e.preventDefault();
		url = $(this).data('url')
		$('#invoice_attachment_form').attr('action', '/comply' + url )
	});

	
	optionText = 'Agregar gasto'; 
	optionValue = 'otro'; 
	$('#comply_detail_cost_change').append($('<option>').val(optionValue).text(optionText));

	$("#comply_detail_cost_change").on('change',function(){ 
		var option = $(this).val()
		if (option == 'otro'){
			var txt;
			var r = confirm("¿Desea crear un nuevo gasto?");
			if (r == true) {
				$('#CreateExpense').modal('show');
				// $(location).attr('href', '/preliquidated/details/new/'+category_id+'?redirect_to='+category_id)
			}
		}
	});

	optionText = 'Crear otro proveedor'; 
	optionValue = 'otro'; 
	// $('#provider_detail_id').append(`<option value="${optionValue}"> ${optionText} </option>`);
	$('#liquidation_detail_provider_id').append($('<option>').val(optionValue).text(optionText));

	$("#liquidation_detail_provider_id").on('change',function(){ 
		var option = $(this).val()
		var detail = $(this).attr('complydetail_id');
		if (option == 'otro'){
			var txt;
			var r = confirm("¿Desea crear un nuevo proveedor?");
			if (r == true) {
				$('#CreateProvider').modal('show')
				$('#modal_create_provider_detail').val(detail);
				// $(location).attr('href', '/preliquidated/providers/new')
			}
		}
	});
	$('#CreateExpense').on('shown.bs.modal', function() {
		$('#liquidation_detail_provider_id').trigger('focus');
	});
	function formatNumber(num) {
		// return num.toString().replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1,')
		return num.toString()
	}

});
(function() {


}).call(this);
var dbRef = null;
$(document).ready(function() {
	if ($(".detail-chat").length) {
		loadDriverDetail()
	}
	$("#idHdr").before($("#panic-alert"));

	if ($(".container-dashboard").length) {
		dbRef = firebase.database().ref(document.getElementById("table_fr").value);
		dbRef.once('value', function(vehicles) {
			load_markers_gmap(vehicles, false);
			if ($("#dashboard-tbody-vehicles").length) {
				document.getElementById("inputVehicle").setAttribute("onkeyup", "search_vehicle("+JSON.stringify(vehicles)+")");
				load_table_vehicles(vehicles);
				if($(".container-dashboard-trafico").length) {
					$('.dashboard-select-vehicle-map').change(function() {
						var update_map = true;
						var vehicles_filter = [];
						if (this.value == "all_vehicles") {
							update_map = false;
							vehicles_filter = vehicles;
						}else if(this.value == "own"){
							vehicles.forEach(function(vehicle) {
								if(vehicle.val().is_own) {
									vehicles_filter.push(vehicle.val());
								}
							});
						}else if(this.value == "third_parties") {
							vehicles.forEach(function(vehicle) {
								if(!vehicle.val().is_own) {
									vehicles_filter.push(vehicle.val());
								}
							});
						}
						load_markers_gmap(vehicles_filter, update_map);
					});
				}
			}
		});
		if($("#dashboard-tbody-vehicles").length) {
			// https://stackoverflow.com/questions/17067294/html-table-with-100-width-with-vertical-scroll-inside-tbody
			// Change the selector if needed
			var $table = $('table'),
			$bodyCells = $table.find('tbody tr:first').children(),
			colWidth;

			// Get the tbody columns width array
			colWidth = $bodyCells.map(function() {
				return $(this).width();
			}).get();

			// Set the width of thead columns
			$table.find('thead tr').children().each(function(i, v) {
				$(v).width(colWidth[i]);
			});
		}
		if($(".container-dashboard-transer").length) {
			get_analytics_indicators(null);
			$('.dashboard-select-customer').change(function() {
				$(".analytics-indicators").children('.row').remove();
				if (this.value == "all_customers") {
					get_analytics_indicators(null);
				}else{
					get_analytics_indicators(this.value);
				}
			});
		}
		if($(".container-dashboard-customer").length) {
			get_analytics_indicators($("#currentUserBigCustomerId").val());
		}
	}
});

function search_vehicle(vehicles) {
	var vehicles_filter = [];
	var input, filter, table, tr, td, i, txtValue;
	input = document.getElementById("inputVehicle");
	filter = input.value.toUpperCase();
	table = document.getElementById("tableVehicles");
	tr = table.getElementsByTagName("tr");
	for (i = 0; i < tr.length; i++) {
		td = tr[i].getElementsByTagName("td")[0];
		if (td) {
			txtValue = td.textContent || td.innerText;
			if (txtValue.toUpperCase().indexOf(filter) > -1) {
				tr[i].style.display = "";
			} else {
				tr[i].style.display = "none";
			}
		}
	}
	for (var key in vehicles){
		if (vehicles[key].placa) {
			if (vehicles[key].placa.toUpperCase().indexOf(filter) > -1){
				vehicles_filter.push(vehicles[key]);
			}
		}
	}
	if (vehicles_filter.length > 0) load_markers_gmap(vehicles_filter, true);
}

function get_analytics_indicators(bigCustomerId) {
	$.ajax({
		url: '../../commercial/bigcustomers/indicators',
		type: "GET",
		dataType: "json",
		data: {
			big_customer_id: bigCustomerId
		},
		success: function(result) {
			var contentIndicators = $(".analytics-indicators");
			for (var i = 0; i < result.indicators.length; i++) {
				let obj = result.indicators[i];
				let texts = "";
				for (var j = obj.texts.length - 1; j >= 0; j--) {
					texts += '<h5>'+obj.texts[j]+'</h5>';
				}
				if (bigCustomerId) {
					var title = '<h5><a href="../commercial/bigcustomers/'+bigCustomerId+'/graphics/detail_indicator/?graphic_title='+obj.title+'&type='+obj.type+'"><strong>'+obj.title+'</strong></a></h5>'
				}else{
					var title = '<h5><strong>'+obj.title+'</strong></h5>'
				}
				if (i % 4 === 0) {
					if (i != 0) { contentIndicators.append(row); }
					var row = document.createElement("div");
					row.setAttribute("class", "row");
				}
				row.innerHTML += '<div class="col-md-3" style="padding-bottom: 8px;">'+
				'<div class="analytics-indicator-main" style="height:310px;">'+
				'<div class="analytics-indicator-title">'+
				'<div class="col-md-8 content-value">'+
				title+
				'</div>'+
				'<div class="col-md-4 content-value">'+
				'<h6 class="text-right" style="'+obj.colour+'"><strong>'+obj.value+'</strong></h6>'+
				'</div>'+
				'</div>'+
				'<div class="row analytics-indicator-body">'+
				'<div class="col-md-12 content-body">'+
				'<hr>'+
				texts+
				'</div>'+
				'</div>'+
				'<div class="analytics-indicator-footer" style="bottom: 0;">'+
				'<h5><strong>'+obj.footnote+'</strong></h5>'+
				'</div>'+
				'</div>'+
				'</div>';
				if (result.indicators.length == 1) {
					contentIndicators.append(row);
				}else{
					if (i % 6 != 0 && (i+1) === result.indicators.length) { contentIndicators.append(row); }
				}
			}
		}
	});
}

function validate_risk_area(vehicles, many){
	var data = { vehicles: vehicles }
	if (!many){
		data = { vehicle: vehicles }
	}
	$.ajax({
		url: '../dashboard/risk_areas/validate',
		type: "POST",
		dataType: "json",
		data: data,
		success: function(result) {
			// if (result.success == false) console.log(result);
		},
		error: function(result){
			console.log(result);
		}
	})
}

function load_markers_gmap(vehicles, update_map){
	// map = Gmaps.build('Google');
	// map.buildMap({ provider: { mapTypeControl: false }, internal: {id: 'map'}}, function(){
	// 	markers = map.addMarkers(vehicles);
	// 	map.bounds.extendWith(markers);
	// 	setInterval(function(){
	// 		console.log("Starting setInterval map...");
	// 		let vehicles = set_vehicles();
	// 		for (var i = 0; i < markers.length; i++) {
	// 			markers[i].setMap(null);
	// 			map.removeMarkers(markers);
	// 		}
	// 		markers = [];
	// 		markers = map.addMarkers(vehicles.to_map);
	// 		map.bounds.extendWith(markers);
	// 		console.log("Finish setInterval map.");
	// 	}, 500000);
	// 	map.fitMapToBounds();
	// 	map.getMap().setZoom(4);
	// });

	var map = new google.maps.Map(document.getElementById('map'), {
		center: {lat: 4, lng: -72},
		zoom: 4
	});

	var infowindow = new google.maps.InfoWindow();

	google.maps.event.addListener(map, 'click', function() {
		infowindow.close();
	});

	var markers = []

	var markerCluster = new markerClusterer.MarkerClusterer({ map: map, markers: markers });

	if (!update_map) {
		dbRef.on('child_changed', function(car) {
			var car = car.val();
			$('img[src="'+getUrlIcon(car)+"?placa="+car.placa+'"]').css({
				'transform': 'rotate(' + car.rotacion + 'deg)',
				'transform-origin': '33px 33px'
			});
			if (markers[car.placa] != null && car.latitud && car.longitud) {
				if (car.driver) {
					if (car.driver.chat_traffic) {
						var messages = JSON.parse(car.driver.chat_traffic.note);
						var contentMessagesChatDriver = document.getElementById("contentMessagesChatDriver");
						contentMessagesChatDriver.innerHTML = null;
						for (var i = 0; i < messages.length; i++) {
							var messageDriver = document.createElement("div");
							if (messages[i].user_id == $("#currentUserId").val()) {
								var date = moment(messages[i].date_time).format('DD-MMM-YYYY HH:MM:SS');
								messageDriver.setAttribute("class", "row message-body");
								messageDriver.innerHTML = 
								'<div class="col-sm-12 message-main-sender">' +
								'<div class="sender">' +
								'<div class="message-text">'	+
								messages[i].message +
								'</div>' +
								'<span class="message-time pull-right">' +
								date +
								'</span>' +
								'</div>' +
								'</div>';


							}else{
								var date = moment(messages[i].date_time).format('DD-MMM-YYYY HH:MM:SS');
								messageDriver.setAttribute("class", "row message-body");
								messageDriver.innerHTML = 
								'<div class="col-sm-12 message-main-receiver">' +
								'<div class="receiver">' +
								'<div class="message-text">'	+
								messages[i].message +
								'</div>' +
								'<span class="message-time pull-right">' +
								date +
								'</span>' +
								'</div>' +
								'</div>';
							}
							// var messageChat = document.createElement("p");
							// messageChat.setAttribute("class", "messageChat");
							// messageChat.innerHTML = messages[i].message;
							// messageDriver.append(messageChat);
							contentMessagesChatDriver.append(messageDriver);
						}
						document.getElementById("btnChat"+car.driver.id).setAttribute("onclick", "seeChatDriver("+JSON.stringify(car.driver)+", '/notifications/wall/messages/add/comments', 'traffic')");
						document.getElementById("btnSendMessageChat").setAttribute("onclick", "sendMessageChat("+JSON.stringify(car.driver)+", '/notifications/wall/messages/add/comments', 'traffic')");
					}
				}
				markers[car.placa].setPosition({lat: car.latitud, lng: car.longitud});
				markers[car.placa].setIcon({
					url: getUrlIcon(car)+"?placa="+car.placa,
					anchor: new google.maps.Point(33, 33),
					size: new google.maps.Size(64, 64),
					scaledSize: new google.maps.Size(50, 50)
				})
			} else {
				createMarker(map, markers, car, infowindow, markerCluster);
			}
			validate_risk_area(car, false);
		});
	}
	if (update_map) {
		for(var i = vehicles.length - 1; i >= 0; i--) {
			if(vehicles[i].latitud && vehicles[i].longitud) createMarker(map, markers, vehicles[i], infowindow, markerCluster);
		}
	}else{
		validate_risk_area(JSON.stringify(vehicles), true);
		vehicles.forEach(function(car) {
			if (car.val().latitud && car.val().longitud) createMarker(map, markers, car.val(), infowindow, markerCluster);
		});
	}
}

function createMarker(map, markers, car, infowindow, markerCluster) {
	if (car.last_update  !== undefined ){
		if (car.last_update.slice(-2) == 'AM'){
			var date_str = car.last_update.split("A").join(":A")
		} else if (car.last_update.slice(-2) == 'PM') {
			var date_str = car.last_update.split("P").join(":P")
		}
		var last_update = toTimestamp(date_str)
		var now_72 = new Date() - 172800000
		if (now_72 < last_update) {

			var estados = "Sin estado"
			if (car.estados) estados = car.estados
				let local_params = {
					license_plate: car.placa,
					driver_name: car.actor_vial_nombre+" "+muestra(car.actor_vial_apellidos),
					evento_nombre: car.evento_nombre,
					vehicle_id: muestra(car.vehicle_id),
					estados: estados,
					driver_service: muestra(car.driver_service),
					status_badge: muestra(car.status_badge),
					icon_carconf: muestra(car.icon_carconf),
					type_vehicle: muestra(car.type_vehicle),
					is_own: car.is_own,
					last_update: car.last_update
				}

				var marker = new google.maps.Marker({
					position: {lat: car.latitud, lng: car.longitud},
					map: map,
					optimized: false,
					icon: {
						url: getUrlIcon(car)+"?placa="+car.placa,
						anchor: new google.maps.Point(33, 33),
						size: new google.maps.Size(64, 64),
						scaledSize: new google.maps.Size(50, 50)
					},
					title: "Placa: "+car.placa
				});

				marker.addListener('click', function() {
					infowindow.setContent(getInfoView(local_params));
					infowindow.open(map, marker);
				});

				markerCluster.addMarker(marker);
				markers[car.placa] = marker;

				google.maps.event.addListenerOnce(map, 'idle', function() {
					setTimeout(function() {
						$('img[src="'+getUrlIcon(car)+"?placa="+car.placa+'"]').css({
							'transform': 'rotate(' + car.rotacion + 'deg)',
							'transform-origin': '33px 33px'
						});
					}, 10000);
				})
			}

		}

	}

	function set_badge_info_w_map(status_badge) {
		var type = "danger"
		var color = "FF3C3A"
		if (status_badge == 1) {
			type = "success"
			color = "70AFA0"
		}
		return '<span class="badge badge-pill badge-'+type+'" style="background-color: #'+color+'; font-size: 5px;"><label></label></span>'
	}

	function getInfoView(vars) {
		var vehiclePresent = ""
		if (vars.vehicle_id != "") {
			vehiclePresent = vars.icon_carconf+'\
			<h5>'+vars.type_vehicle+'</h5> \
			<hr> \
			<a href="/dashboards/vehicles/'+vars.vehicle_id+'/detail" class="btn-info-gmap">Ver detalle</a>'
		} else {
			vehiclePresent = '<hr> \
			<br> \
			<br> \
			<style type="text/css"> \
			.gm-ui-hover-effect { \
				top: 200px !important; \
			} \
			</style>'
		}
		return '<div class="content-main-info-gmap">  \
		<h3>'+vars.driver_service+' '+set_badge_info_w_map(vars.status_badge)+'</h3> \
		<hr> \
		<h4>Hora último reporte ubicación</h4> \
		<h5>'+vars.last_update+'</h5> \
		<h4>Conductor</h4> \
		<h5>'+vars.driver_name+'</h5> \
		<h4>Estado</h4> \
		<h5>'+vars.estados+'</h5> \
		<h4>Última alerta</h4> \
		<h5>'+vars.evento_nombre+'</h5>'
		+ vehiclePresent +
		'</div>'
	}

	function muestra(variable) {
		if ((variable === null && typeof variable === "object") || variable === undefined) {
			return ""
		} else {
			return variable
		}
	}

	function getUrlIcon(car){
		let url_marker = "/assets/ic_tractomula_transer.svg"
		if (car.type_vehicle) {
			if(car.type_vehicle == "11")
				url_marker = "/assets/ic_motorcycle.svg"
			else if(car.type_vehicle == "12")
				url_marker = "/assets/ic_van_car.svg"
			else if(car.type_vehicle == "13")
				url_marker = "/assets/ic_pickup.svg"
			else if(car.is_own)
				if(car.type_vehicle == "2" || car.type_vehicle == "3")
					url_marker = "/assets/ic_truck_turbo_transer.svg"
				else if(car.type_vehicle == "2s2" || car.type_vehicle == "3s2")
					url_marker = "/assets/ic_truck_transer.svg"
				else
					url_marker = "/assets/ic_tractomula.svg"
				if(car.type_vehicle == "2" || car.type_vehicle == "3")
					url_marker = "/assets/ic_truck_turbo.svg"
				else if(car.type_vehicle == "2s2" || car.type_vehicle == "3s2")
					url_marker = "/assets/ic_truck.svg"
			}
			return url_marker
		}

		function load_table_vehicles(vehicles) {
			var tbody = $("#dashboard-tbody-vehicles");

			var icon1 = document.createElement("img");
			icon1.src = "../assets/ic_message.svg";

			var icon2 = document.createElement("img");
			icon2.src = "../assets/ic_fire_truck.svg";

			var icon3 = document.createElement("img");
			icon3.src = "../assets/ic_albulance.svg";

			var icon4 = document.createElement("img");
			icon4.src = "../assets/ic_police.svg";

			var icon5 = document.createElement("img");
			icon5.src = "../assets/ic_breakdown.svg";

			var icon6 = document.createElement("img");
			icon6.src = "../assets/ic_mechanical_workshop.svg";

			var icon7 = document.createElement("img");
			icon7.src = "../assets/ic_more_vert.svg";

			vehicles.forEach(function(vehicle){
				vehicle = vehicle.val();
				var driver_surname = vehicle.actor_vial_apellidos ? vehicle.actor_vial_apellidos : ""
				var type_vehicle = vehicle.type_vehicle ? vehicle.type_vehicle +' '+ vehicle.icon_carconf  : "Sin acceso a información"
				var driver = vehicle.driver
				if (driver) {
					var btnChat = "<td> <a id = 'btnChat"+ driver.id  + "' href='' data-toggle='modal' data-target='#chatDriverModal' onclick='seeChatDriverDetail("+JSON.stringify(driver)+', "/notifications/wall/messages/add/comments"'+', "traffic"'+")' ><img src='"+icon1.src+"'/></a></td>";
				}else{
					var btnChat = "<td></td>";
				}
				tbody.append('<tr><td width="15%">'+vehicle.placa+'</td>'+
					'<td width="30%">'+vehicle.actor_vial_nombre+' '+driver_surname+'</td>'+
					'<td width="25%">'+type_vehicle+'</td>'+
					'<td width="10%">'+vehicle.estados+'</td>'+
					btnChat+
					'<td> <a href="#"> <img src="'+icon2.src+'" /></a></td>'+
					'<td> <a href="#"> <img src="'+icon3.src+'" /></a></td>'+
					'<td> <a href="#"> <img src="'+icon4.src+'" /></a></td>'+
					'<td> <a href="#"> <img src="'+icon5.src+'" /></a></td>'+
					'<td> <a href="#"> <img src="'+icon6.src+'" /></a></td>'+
					'<td> <a href="#"> <img src="'+icon7.src+'" /></a></td></tr>');
			});

		}

		function see_full_message(id, msg) {
			process_messages(id, msg, "seeMore"+id, "seeLess"+id);
		}

		function see_last_message(id, msg) {
			process_messages(id, msg, "seeLess"+id, "seeMore"+id);
		}

		function process_messages(id, msg, tag_btn_hidden, tag_btn_show) {
			$("#seeMessage"+id).html(msg);
			$("#"+tag_btn_hidden).addClass("hide");
			$("#"+tag_btn_show).removeClass("hide");
		}

		function add_new_comment(id, current_user, current_user_id, full_comments, is_panic_or_chat, new_message, driver_id, url, chat_by){
			var currentDate = moment().format("DD-MM-YYYY HH:mm:ss");
			if (is_panic_or_chat) {
				if (new_message.length) {
					var chat_id;
					$.ajax({
						url: url,
						type: "POST",
						async: false,
						data: { message_id: id, user_id: current_user_id, last_comment: new_message, date_time: currentDate, driver_id: driver_id, is_chat: true, user_to: chat_by},
						success: function(result) {
							if (result.success == false) {
								alert(result.message);
							}else {
								chat_id = result.chat_id;
							}
						}
					});
					return chat_id;
				}
			}else{
				var inputComment = $("#inputNewComment"+id);
				var inputMsg = inputComment.val();
				if (inputMsg.length > 0) {
					inputComment.val(null);
					var seeMore = $("#seeMore"+id).attr('class');
					var newMsg = "<strong>"+inputMsg+"</strong> <span style='font-size: 11px;'>("+ current_user + " - " + currentDate +")</span>"
					if (seeMore.length == 0) {
						var newComments = newMsg;
					}else{
						if (seeMore == "hide") { $("#seeLess"+id).removeClass('hide'); }
						var currentMsgs = $("#seeMessage"+id).html();
						var newComments = currentMsgs + newMsg + "<br>";
					}
					var allMessages = full_comments + newMsg + "<br>"
			// $("#btnSeeMore"+id).attr("onclick", 'see_full_message("'+id+'", "'+allMessages+'")');
			// $("#btnSeeLess"+id).attr("onclick", 'see_last_message("'+id+'", "'+newMsg+'")');
			// $("#btnAddComment"+id).attr("onclick", 'add_new_comment("'+id+'", "'+current_user+'", "'+current_user_id+'", "'+allMessages+'", false, null, null)');
			process_messages(id, newComments, null, null);
			$.ajax({
				url: '/notifications/wall/messages/add/comments',
				type: "POST",
				data: { message_id: id, user_id: current_user_id, last_comment: inputMsg, date_time: currentDate },
				success: function(result) {
					if (result.success == false) {
						alert(result.message);
					}
				}
			});
		}
	}
}

function add_new_comment_detail(id, current_user, current_user_id, full_comments, is_panic_or_chat, new_message, driver_id, url, chat_by){
	var currentDate = moment().format("DD-MM-YYYY HH:mm:ss");
	if (is_panic_or_chat) {
		if (new_message.length) {
			var chat_id;
			$.ajax({
				url: url,
				type: "POST",
				async: false,
				data: { message_id: id, user_id: current_user_id, last_comment: new_message, date_time: currentDate, driver_id: driver_id, is_chat: true, user_to: chat_by},
				success: function(result) {
					console.log(result)
					if (result.success == false) {
						alert(result.message);
					}else {
						chat_id = result.chat_id;
					}
				}
			});
			return chat_id;
		}
	}else{
		var inputComment = $("#inputNewComment"+id);
		var inputMsg = inputComment.val();
		if (inputMsg.length > 0) {
			inputComment.val(null);
			var seeMore = $("#seeMore"+id).attr('class');
			var newMsg = "<strong>"+inputMsg+"</strong> <span style='font-size: 11px;'>("+ current_user + " - " + currentDate +")</span>"
			if (seeMore.length == 0) {
				var newComments = newMsg;
			}else{
				if (seeMore == "hide") { $("#seeLess"+id).removeClass('hide'); }
				var currentMsgs = $("#seeMessage"+id).html();
				var newComments = currentMsgs + newMsg + "<br>";
			}
			var allMessages = full_comments + newMsg + "<br>" 
			// $("#btnSeeMore"+id).attr("onclick", 'see_full_message("'+id+'", "'+allMessages+'")');
			// $("#btnSeeLess"+id).attr("onclick", 'see_last_message("'+id+'", "'+newMsg+'")');
			// $("#btnAddComment"+id).attr("onclick", 'add_new_comment("'+id+'", "'+current_user+'", "'+current_user_id+'", "'+allMessages+'", false, null, null)');
			process_messages(id, newComments, null, null);
			$.ajax({
				url: '/notifications/wall/messages/add/comments',
				type: "POST",
				data: { message_id: id, user_id: current_user_id, last_comment: inputMsg, date_time: currentDate },
				success: function(result) {
					if (result.success == false) {
						alert(result.message);
					}
				}
			});
		}
	}
}

function change_state_message(id, notify_client, solved, is_panic, confirm_text, button) {
	if (is_panic && !$("#panic-alert").attr("class").indexOf("hide") >= 0) {
		var observationsPanicAlert = $("#observationsPanicAlert");
		if (!observationsPanicAlert.val().length) {
			return false;
		}
	}
	if(confirm(confirm_text) == true ) {
		if (solved) {
			if (is_panic && !$("#panic-alert").attr("class").indexOf("hide") >= 0) {
				add_new_comment(id, null, $("#currentUserId").val(), null, true, observationsPanicAlert.val(), null, '/notifications/wall/messages/add/comments', 'traffic');
				$('#clockPanicAlert').removeClass('flip-clock-wrapper');
				$("#panic-alert").addClass("hide");
				$("#panicAlertModal").modal('hide');
			}
			$("#indicatorAlert"+id).remove();
			$("#lineMainFinalAlerts"+id).remove();
		}
		if (notify_client) { button.remove(); }
		$.ajax({
			url: '/notifications/wall/messages/change/status',
			type: "POST",
			data: { message_id: id, notify_client: notify_client, solved: solved, checked: is_panic },
			success: function(result) {
				if (result.success == false) {
					alert(result.message);
				}
			}
		});
	}
}

function toTimestamp(strDate){
	var datum = Date.parse(strDate);
	// return datum/1000;
	return datum;
}


$(document).ready(function() {
	$("#btnSeeMoreIndicators").click(function() {
		if ($("#btnSeeMoreIndicators").attr('class') == "more"){
			$(".indicator-alert-hide" ).each(function(i) {
				$(this).removeClass("hide");
			});
			$(".final-line-alert-hide" ).each(function(i) {
				$(this).removeClass("hide");
			});
			$("#btnSeeMoreIndicators").attr('class', 'less');
			$("#btnSeeMoreIndicators").text("Ver menos");
		}else{
			$(".indicator-alert-hide" ).each(function(i) {
				$(this).addClass("hide");
			});
			$(".final-line-alert-hide" ).each(function(i) {
				$(this).addClass("hide");
			});
			$("#btnSeeMoreIndicators").attr('class', 'more');
			$("#btnSeeMoreIndicators").text("Ver más");
		}
	});

	if ($("#alertsMain").length && $(".container-dashboard").length) {
		setInterval(function() {
			console.log("procesing... alerts");
			$.ajax({
				url: window.location,
				type: "GET",
				dataType: "json",
				data: { },
				success: function(events) {
					events.messages.forEach(function(message, i) {
						var spanCreatedAgo = $("#createdAgo"+message.id);
						if (spanCreatedAgo.length > 0) {
							// message already exists
							if (i >= 3 && $("#btnSeeMoreIndicators").attr('class') == "more") {
								$("#indicatorAlert"+message.id).addClass('indicator-alert-hide hide');
								$("#lineMainFinalAlerts"+message.id).addClass('final-line-alert-hide hide');
							};
							spanCreatedAgo.html(message.created_ago);
							var seeMore = $("#seeMore"+message.id).attr('class');
							var newMsg = message.last_message_to_alerts;
							if (seeMore.length == 0) {
								var newComments = newMsg;
							}else{
								var seeLess = $("#seeLess"+message.id).attr('class');
								if (seeLess == "hide") {
									var newComments = newMsg;
									if (JSON.parse(message.note).length > 1) { $("#seeMore"+message.id).removeClass('hide'); }
								}else{
									var newComments = message.all_messages_to_alerts;
								}
							}
							var allMessages = message.all_messages_to_alerts;
							$("#btnSeeMore"+message.id).attr("onclick", 'see_full_message("'+message.id+'", "'+allMessages+'")');
							$("#btnSeeLess"+message.id).attr("onclick", 'see_last_message("'+message.id+'", "'+newMsg+'")');
							$("#btnAddComment"+message.id).attr("onclick", 'add_new_comment("'+message.id+'", "'+$("#currentUserFullName").val()+'", "'+$("#currentUserId").val()+'", "'+allMessages+'", false, null, null, "/notifications/wall/messages/add/comments", "traffic")');
							process_messages(message.id, newComments, null, null);
						}else{
							// new message
							var contentAlertsMain = document.getElementById("alertsMain");
							var hideFinalLine = "";
							var hideAlert = "";
							if (i >= 3) {
								hideFinalLine = "final-line-alert-hide hide";
								hideAlert = "indicator-alert-hide hide";
							};
							var divIndicatorAlert = document.createElement('div');
							divIndicatorAlert.setAttribute("class", "indicator-alert "+hideAlert);
							divIndicatorAlert.setAttribute("id", "indicatorAlert"+message.id);
							
							var messageType = message.topico || message.messagetype.name;
							divIndicatorAlert.innerHTML = '<span style="float:right; font-size:12px;" id= "createdAgo'+ message.id + '">'+ message.created_ago +'</span>';
							var placa = "";
							if (message.vehicle) {
								placa = ' - '+message.vehicle.placa;
							}
							divIndicatorAlert.innerHTML += '<li><strong>'+messageType+placa+'</strong></li>';
							divIndicatorAlert.innerHTML += '<p id="seeMessage'+ message.id + '">'+ message.last_message_to_alerts +'</p>';

							var btnSeeMore = document.createElement('a');
							btnSeeMore.setAttribute("id", "btnSeeMore"+message.id);
							btnSeeMore.setAttribute("onclick", 'see_full_message("' + message.id + '", "' + message.all_messages_to_alerts + '")');
							btnSeeMore.innerHTML = "Ver más";

							var hideSpanBtnSeeMore = "";
							if (JSON.parse(message.note).length == 1) { hideSpanBtnSeeMore = "hide" };

							var spanBtnSeeMore = document.createElement('span');
							spanBtnSeeMore.setAttribute("class", hideSpanBtnSeeMore);
							spanBtnSeeMore.setAttribute("id", "seeMore"+message.id);

							spanBtnSeeMore.appendChild(btnSeeMore);

							var btnSeeLess = document.createElement('a');
							btnSeeLess.setAttribute("id", "btnSeeLess"+message.id);
							btnSeeLess.setAttribute("onclick", 'see_last_message("' + message.id + '", "' + message.last_message_to_alerts + '")');
							btnSeeLess.innerHTML = "Ver menos";

							var spanBtnSeeLess = document.createElement('span');
							spanBtnSeeLess.setAttribute("class", "hide");
							spanBtnSeeLess.setAttribute("id", "seeLess"+message.id);

							spanBtnSeeLess.appendChild(btnSeeLess);

							divIndicatorAlert.appendChild(spanBtnSeeMore);
							divIndicatorAlert.appendChild(spanBtnSeeLess);
							divIndicatorAlert.innerHTML += "<hr>";

							if(!$(".container-dashboard-transer").length) {
								var contentNewComment = document.createElement("div");
								contentNewComment.setAttribute("id", "newComment"+message.id);
								contentNewComment.setAttribute("class", "hide");

								var inputComment = document.createElement("input");
								inputComment.setAttribute("id", "inputNewComment"+message.id);
								inputComment.setAttribute("maxlength", "200");
								inputComment.setAttribute("placeholder", "Escribe un comentario");

								var btnCancelNewComment = document.createElement("button");
								btnCancelNewComment.setAttribute("class", "btn btn-sm btn-default");
								btnCancelNewComment.setAttribute("onclick", "$('#newComment"+ message.id +"').addClass('hide')");
								btnCancelNewComment.innerHTML = "Cancelar";

								var btnSubmitNewComment = document.createElement("button");
								btnSubmitNewComment.setAttribute("class", "btn btn-sm btn-info");
								btnSubmitNewComment.setAttribute("id", "btnAddComment"+message.id);
								btnSubmitNewComment.setAttribute("onclick", 'add_new_comment("'+message.id+'", "'+$("#currentUserFullName").val()+'", "'+$("#currentUserId").val()+'", "'+message.all_messages_to_alerts+'", false, null, null, "/notifications/wall/messages/add/comments", "traffic")');
								btnSubmitNewComment.innerHTML = "Agregar";

								contentNewComment.innerHTML = "<label>Nuevo comentario</label>";
								contentNewComment.appendChild(inputComment);
								contentNewComment.appendChild(btnCancelNewComment);
								contentNewComment.appendChild(btnSubmitNewComment);

								var contentIndicatorFooterAlert = document.createElement("div");
								contentIndicatorFooterAlert.setAttribute("class", "indicator-footer-alert");

								if (!$(".container-dashboard-customer").length) {
									var btnResolveAlert = document.createElement("a");
									btnResolveAlert.setAttribute("onclick", 'change_state_message("'+message.id+'", false, true, false, "Resolver alerta?")');
									btnResolveAlert.innerHTML = '<strong>Resuelto</strong>';
									contentIndicatorFooterAlert.appendChild(btnResolveAlert);
									if (message.request && !message.notified_to_client) {
										var btnNotifyAlert = document.createElement("a");
										btnNotifyAlert.setAttribute("onclick", 'change_state_message("'+message.id+'", true, false, false, "Notificar a cliente ?", this)');
										btnNotifyAlert.innerHTML = '<strong>Notificar a cliente</strong>';
										contentIndicatorFooterAlert.appendChild(btnNotifyAlert);
									}
								}
								var btnAddNewComment = document.createElement("a");
								btnAddNewComment.setAttribute("onclick", "$('#newComment"+ message.id +"').removeClass('hide')");
								btnAddNewComment.innerHTML = '<strong>Agregar comentario</strong>';

								contentIndicatorFooterAlert.appendChild(btnAddNewComment);
								divIndicatorAlert.appendChild(contentNewComment);
								divIndicatorAlert.appendChild(contentIndicatorFooterAlert);
							}
							contentAlertsMain.insertBefore(divIndicatorAlert, contentAlertsMain.children[4]);

							var lineFinal = document.createElement("hr");
							lineFinal.setAttribute("class", "line-main-final-alerts "+hideFinalLine);
							lineFinal.setAttribute("id", "lineMainFinalAlerts"+message.id);

							contentAlertsMain.insertBefore(lineFinal, contentAlertsMain.children[5]);
						}
						if(!$(".container-dashboard-transer").length) {
							if (!message.checked && message.vehicle && $("#panic-alert").attr("class").indexOf("hide") >= 0) {
								$("#titlePanicAlert").text(message.messagetype.name);
								$("#bodyPanicAlert").html("Notificación de alerta de <strong>"+message.messagetype.name+"</strong> de vehículo placa <strong>"+message.vehicle.placa+"</strong>, "+message.formatted_address);
								$("#panic-alert").removeClass("hide");
								$("#btn-detail").attr("onclick", 'set_modal_detail_panic_alert('+JSON.stringify(message)+')');
								$("#btnResolvePanicAlert").attr("onclick", 'change_state_message("'+message.id+'", false, true, true, "Resolver alerta ?")');
							}
						}
					});
					if(events.messages.length > 3) {
						$("#indicatorSeeMoreAlert").removeClass('hide');
					}else{
						$("#indicatorSeeMoreAlert").addClass('hide');
					}
				}
			});
			console.log("Finish.");
		}, 1000 * 60 * 2); // cada 2min
	}
})

function set_modal_detail_panic_alert(message) {
	updateTime(message.time_remaining);
	mapPanicAlert = Gmaps.build('Google');
	mapPanicAlert.buildMap({ provider: { mapTypeControl: false, clickableIcons: false }, internal: {id: 'map-panic-alert'}}, function(){
		markers = mapPanicAlert.addMarkers([
		{
			"lat": message.lat,
			"lng": message.lon,
			"picture": {
				"url": "assets/ic_position.svg",
				"width":  32,
				"height": 32
			}
		}
		]);
		mapPanicAlert.bounds.extendWith(markers);
		mapPanicAlert.fitMapToBounds();
		mapPanicAlert.getMap().setZoom(15);
	});
	$("#license_plate_panic_alert").text(message.vehicle.placa);
	var car_config_panic_alert = $("#car_config_panic_alert");
	var img_carconfig = document.createElement("img");
	img_carconfig.src = message.vehicle.carconfig.image.url;
	car_config_panic_alert.text(message.vehicle.carconfig.code);
	car_config_panic_alert.append(img_carconfig);
	// $("#panicAlertModalLabel").html("<img src='assets/round-warning-to-detail'>  Alerta vehículo ("+message.messagetype.name+")")
	// $("#type_panic_alert").text(message.messagetype.name);
	// $("#address_panic_alert").text(message.formatted_address);
}

function updateTime(time_diff) {
	if (!$('.flip-clock-wrapper').length) {
		var clock = $('.clock-panic-alert').FlipClock(time_diff, {
			clockFace: 'DailyCounter',
			language:'es-es'
		});
	}
}
$(document).on('click', '.flip-clock-wrapper', function () {
	return false;
});

jQuery(function($) {
	// autofocus first input form to modal bootstrap
	$('#chatDriverModal').on('shown.bs.modal', function (event) {
	    // autofocus first input form to modal bootstrap
	    $(this).find('[autofocus]').focus();
	})
})

function seeChatDriver(driver, add_comment_url, chat_by) {
	$("#chatDriverModalLabel").html("Chat de conductor "+driver.full_name)
	var contentMessagesChatDriver = document.getElementById("contentMessagesChatDriver");
	contentMessagesChatDriver.innerHTML = null;
	var messages = [];
	if (driver.chat_traffic && chat_by == 'traffic') {
		try {
			messages = JSON.parse(driver.chat_traffic.note);
		} catch (e) {
			messages = driver.chat_traffic.note;
		}
	}else if (driver.chat_office && chat_by == 'office') {
		try {
			messages = JSON.parse(driver.chat_office.note);
		} catch (e) {
			messages = driver.chat_office.note;
		}
	}
	for(var i = 0; i < messages.length; i++) {
		if (messages[i].user_id == $("#currentUserId").val() || messages[i].person_id == driver.id) {
			var messageDriver = document.createElement("div");
			if (messages[i].user_id == $("#currentUserId").val()) {
				messageDriver.setAttribute("class", "col-md-10 col-md-offset-2 ownMessage text-right");
			}else{
				messageDriver.setAttribute("class", "col-md-10 messageDriver");
			}
			var messageChat = document.createElement("p");
			messageChat.setAttribute("class", "messageChat");
			messageChat.innerHTML = messages[i].message;
			messageDriver.append(messageChat);
			contentMessagesChatDriver.append(messageDriver);
		}
	}
	document.getElementById("btnSendMessageChat").setAttribute("onclick", "sendMessageChat("+JSON.stringify(driver)+ ", '"+add_comment_url+"', '"+chat_by+"')");
}

function sendMessageChat(driver, add_comment_url, chat_by) {
	var contentNewMessageToDriver = $(".contentNewMessageToDriver");
	if(contentNewMessageToDriver.val()) {
		var user_id = $("#currentUserId").val();
		if (driver.chat_traffic && chat_by == 'traffic') {
			// it have chat
			chat_id = driver.chat_traffic.id;
			try {
				var new_messages = JSON.parse(driver.chat_traffic.note);
			} catch (e) {
				var new_messages = driver.chat_traffic.note;
			}
			new_messages.push(JSON.parse('{"user_id": "'+ user_id + '", "message": "' + contentNewMessageToDriver.val() + '"}'));
			driver.chat_traffic.note = new_messages;
			add_new_comment(chat_id, null, user_id, null, true, contentNewMessageToDriver.val(), driver.id, add_comment_url, chat_by);
		}else if(driver.chat_office && chat_by == 'office'){
			// it have chat
			chat_id = driver.chat_office.id;
			try {
				var new_messages = JSON.parse(driver.chat_office.note);
			} catch (e) {
				var new_messages = driver.chat_office.note;
			}
			new_messages.push(JSON.parse('{"user_id": "'+ user_id + '", "message": "' + contentNewMessageToDriver.val() + '"}'));
			driver.chat_office.note = new_messages;
			add_new_comment(chat_id, null, user_id, null, true, contentNewMessageToDriver.val(), driver.id, add_comment_url, chat_by);
		}else{
			// it have not chat
			const chat_id = add_new_comment(null, null, user_id, null, true, contentNewMessageToDriver.val(), driver.id, add_comment_url, chat_by);
			if (chat_id) {
				// return chat_id of new chat
				if (chat_by == 'traffic') {
					driver.chat_traffic = {"id": chat_id, "note": "[{\"user_id\":\""+ user_id + "\",\"message\":\"" + contentNewMessageToDriver.val() +"\"}]"};
				}else if(chat_by == 'office'){
					driver.chat_office = {"id": chat_id, "note": "[{\"user_id\":\""+ user_id + "\",\"message\":\"" + contentNewMessageToDriver.val() +"\"}]"};
				}
			}else {
				alert("No retorna chat_id, al crear nuevo chat")
			}
		}
		contentNewMessageToDriver.css("border-color", "#ccc");
		contentNewMessageToDriver.val(null);
		contentNewMessageToDriver.focus();
		// document.getElementById("btnChat"+driver.id).setAttribute("onclick", "seeChatDriver("+JSON.stringify(driver)+", '"+add_comment_url+"', '"+chat_by+"')");
		// document.getElementById("btnSendMessageChat").setAttribute("onclick", "sendMessageChat("+JSON.stringify(driver)+", '"+add_comment_url+"', '"+chat_by+"')");
	}else{
		contentNewMessageToDriver.css("border-color", "#FF3C3A");
	}
}


function sendMessageChatDetail(driver, add_comment_url, chat_by) {
	var contentNewMessageToDriver = $(".contentNewMessageToDriver");
	if(contentNewMessageToDriver.val()) {
		var user_id = $("#currentUserId").val();
		if (driver.chat_traffic && chat_by == 'traffic') {
			// it have chat
			chat_id = driver.chat_traffic.id;
			try {
				var new_messages = JSON.parse(driver.chat_traffic.note);
			} catch (e) {
				var new_messages = driver.chat_traffic.note;
			}
			new_messages.push(JSON.parse('{"user_id": "'+ user_id + '", "message": "' + contentNewMessageToDriver.val() + '"}'));
			driver.chat_traffic.note = new_messages;
			add_new_comment_detail(chat_id, null, user_id, null, true, contentNewMessageToDriver.val(), driver.id, add_comment_url, chat_by);
		}else if(driver.chat_office && chat_by == 'office'){
			// it have chat
			chat_id = driver.chat_office.id;
			try {
				var new_messages = JSON.parse(driver.chat_office.note);
			} catch (e) {
				var new_messages = driver.chat_office.note;
			}
			new_messages.push(JSON.parse('{"user_id": "'+ user_id + '", "message": "' + contentNewMessageToDriver.val() + '"}'));
			driver.chat_office.note = new_messages;
			add_new_comment(chat_id, null, user_id, null, true, contentNewMessageToDriver.val(), driver.id, add_comment_url, chat_by);
		}else{
			// it have not chat
			const chat_id = add_new_comment(null, null, user_id, null, true, contentNewMessageToDriver.val(), driver.id, add_comment_url, chat_by);
			if (chat_id) {
				// return chat_id of new chat
				if (chat_by == 'traffic') {
					driver.chat_traffic = {"id": chat_id, "note": "[{\"user_id\":\""+ user_id + "\",\"message\":\"" + contentNewMessageToDriver.val() +"\"}]"};
				}else if(chat_by == 'office'){
					driver.chat_office = {"id": chat_id, "note": "[{\"user_id\":\""+ user_id + "\",\"message\":\"" + contentNewMessageToDriver.val() +"\"}]"};
				}
			}else {
				alert("No retorna chat_id, al crear nuevo chat")
			}
		}
		contentNewMessageToDriver.css("border-color", "#ccc");
		contentNewMessageToDriver.val(null);
		contentNewMessageToDriver.focus();
		document.getElementById("btnChat"+driver.id).setAttribute("onclick", "seeChatDriverDetail("+JSON.stringify(driver)+", '"+add_comment_url+"', '"+chat_by+"')");
		// document.getElementById("btnSendMessageChat").setAttribute("onclick", "sendMessageChat("+JSON.stringify(driver)+", '"+add_comment_url+"', '"+chat_by+"')");
	}else{
		contentNewMessageToDriver.css("border-color", "#FF3C3A");
	}
}

function seeChatDriverDetail(driver, add_comment_url, chat_by) {
	$("#chatDriverModalLabel").html("Chat de conductor "+driver.full_name)
	var contentMessagesChatDriver = document.getElementById("contentMessagesChatDriver");
	contentMessagesChatDriver.innerHTML = null;
	var messages = [];
	if (driver.chat_traffic && chat_by == 'traffic') {
		try {
			messages = JSON.parse(driver.chat_traffic.note);
		} catch (e) {
			messages = driver.chat_traffic.note;
		}
	}else if (driver.chat_office && chat_by == 'office') {
		try {
			messages = JSON.parse(driver.chat_office.note);
		} catch (e) {
			messages = driver.chat_office.note;
		}
	}
	for(var i = 0; i < messages.length; i++) {
		if (messages[i].user_id == $("#currentUserId").val() || messages[i].person_id == driver.id) {
			var messageDriver = document.createElement("div");
			var date = moment(new Date(messages[i].date_time)).format('DD-MMM-YYYY h:mm:ss a');
			if (messages[i].user_id == $("#currentUserId").val()) {
				messageDriver.setAttribute("class", "row message-body");
				messageDriver.innerHTML = 
				'<div class="col-sm-12 message-main-sender">' +
				'<div class="sender">' +
				'<div class="message-text">'	+
				messages[i].message +
				'</div>' +
				'<span class="message-time pull-right">' +
				date +
				'</span>' +
				'</div>' +
				'</div>';


			}else{
				messageDriver.setAttribute("class", "row message-body");
				messageDriver.innerHTML = 
				'<div class="col-sm-12 message-main-receiver">' +
				'<div class="receiver">' +
				'<div class="message-text">'	+
				messages[i].message +
				'</div>' +
				'<span class="message-time pull-right">' +
				date +
				'</span>' +
				'</div>' +
				'</div>';
			}
			// var messageChat = document.createElement("p");
			// messageChat.setAttribute("class", "messageChat");
			// var date = moment(messages[i].date_time).format('DD-MMM-YYYY HH:MM:SS');
			// messageChat.innerHTML = messages[i].message;
			// messageDriver.append(messageChat);
			contentMessagesChatDriver.append(messageDriver);
		}

	}
	if ( $(contentMessagesChatDriver).length ) {
		setTimeout(
			function() 
			{
				scrollToBottom('contentMessagesChatDriver')
			}, 200);
	}
	document.getElementById("btnSendMessageChat").setAttribute("onclick", "sendMessageChatDetail("+JSON.stringify(driver)+ ", '"+add_comment_url+"', '"+chat_by+"')");
}

function scrollToBottom(id){
	var div = document.getElementById(id);
	div.scrollTop = div.scrollHeight - div.clientHeight;
}




function loadDriverDetail() {
	var driver_id = $('#driver_json').val()
	$.ajax({
		dataType: "json",
		cache: false,
		url: '/people/get_by_id/' + driver_id +'.json',
		timeout: 5000,
		beforeSend: function (request) {
			request.setRequestHeader("Authorization", 'Bearer <%= current_user.token %>');
		},
		error: function(XMLHttpRequest, errorTextStatus, error) {
			console.log("Failed to submit : " + errorTextStatus + " ;" + error);
		},
		success: function(data) { 
			if (data.success){
				driver = data.data
				dbRef = firebase.database().ref(document.getElementById("table_fr").value);
				dbRef.on('child_changed', function(car) {
					var car = car.val();
					if (car.driver) {
						if (car.driver.chat_traffic) {
							var messages = JSON.parse(car.driver.chat_traffic.note);
							var contentMessagesChatDriver = document.getElementById("contentMessagesChatDriver");
							contentMessagesChatDriver.innerHTML = null;
							for (var i = 0; i < messages.length; i++) {
								var messageDriver = document.createElement("div");
								var date = moment(new Date(messages[i].date_time)).format('DD-MMM-YYYY h:mm:ss a');
								if (messages[i].user_id == $("#currentUserId").val()) {
									messageDriver.setAttribute("class", "row message-body");
									messageDriver.innerHTML = 
									'<div class="col-sm-12 message-main-sender">' +
									'<div class="sender">' +
									'<div class="message-text">'	+
									messages[i].message +
									'</div>' +
									'<span class="message-time pull-right">' +
									date +
									'</span>' +
									'</div>' +
									'</div>';


								}else{
									messageDriver.setAttribute("class", "row message-body");
									messageDriver.innerHTML = 
									'<div class="col-sm-12 message-main-receiver">' +
									'<div class="receiver">' +
									'<div class="message-text">'	+
									messages[i].message +
									'</div>' +
									'<span class="message-time pull-right">' +
									date +
									'</span>' +
									'</div>' +
									'</div>';
								}
								// var messageChat = document.createElement("p");
								// messageChat.setAttribute("class", "messageChat");
								// messageChat.innerHTML = messages[i].message;
								// messageDriver.append(messageChat);
								contentMessagesChatDriver.append(messageDriver);
							}
							if ($("#btnChat"+car.driver_id).length){
								document.getElementById("btnChat"+car.driver_id).setAttribute("onclick", "seeChatDriverDetail("+JSON.stringify(car.driver)+", '/notifications/wall/messages/add/comments', 'traffic')");
							}
							document.getElementById("btnSendMessageChat").setAttribute("onclick", "sendMessageChatDetail("+JSON.stringify(car.driver)+", '/notifications/wall/messages/add/comments', 'traffic')");
							if ( $(contentMessagesChatDriver).length ) {
								setTimeout(
									function() 
									{
										scrollToBottom('contentMessagesChatDriver')
									}, 200);
							}
						}
					}


				});
				document.getElementById("btnChat"+data.data.id).setAttribute("onclick", "seeChatDriverDetail("+JSON.stringify(data.data)+", '/notifications/wall/messages/add/comments', 'traffic')");
			}
		}
	});
}
;
// ===================================================================
// Author: Matt Kruse <matt@mattkruse.com>
// WWW: http://www.mattkruse.com/
//
// NOTICE: You may use this code for any purpose, commercial or
// private, without any further permission from the author. You may
// remove this notice from your final code if you wish, however it is
// appreciated by the author if at least my web site address is kept.
//
// You may *NOT* re-distribute this code in any way except through its
// use. That means, you can include it in your product, or your web
// site, or any other form where the code is actually being used. You
// may not put the plain javascript up on your site for download or
// include it in your javascript libraries for download. 
// If you wish to share this code with others, please just point them
// to the URL instead.
// Please DO NOT link directly to my .js files from your site. Copy
// the files to your server and use them there. Thank you.
// ===================================================================

// HISTORY
// ------------------------------------------------------------------
// May 17, 2003: Fixed bug in parseDate() for dates <1970
// March 11, 2003: Added parseDate() function
// March 11, 2003: Added "NNN" formatting option. Doesn't match up
//                 perfectly with SimpleDateFormat formats, but 
//                 backwards-compatability was required.

// ------------------------------------------------------------------
// These functions use the same 'format' strings as the 
// java.text.SimpleDateFormat class, with minor exceptions.
// The format string consists of the following abbreviations:
// 
// Field        | Full Form          | Short Form
// -------------+--------------------+-----------------------
// Year         | yyyy (4 digits)    | yy (2 digits), y (2 or 4 digits)
// Month        | MMM (name or abbr.)| MM (2 digits), M (1 or 2 digits)
//              | NNN (abbr.)        |
// Day of Month | dd (2 digits)      | d (1 or 2 digits)
// Day of Week  | EE (name)          | E (abbr)
// Hour (1-12)  | hh (2 digits)      | h (1 or 2 digits)
// Hour (0-23)  | HH (2 digits)      | H (1 or 2 digits)
// Hour (0-11)  | KK (2 digits)      | K (1 or 2 digits)
// Hour (1-24)  | kk (2 digits)      | k (1 or 2 digits)
// Minute       | mm (2 digits)      | m (1 or 2 digits)
// Second       | ss (2 digits)      | s (1 or 2 digits)
// AM/PM        | a                  |
//
// NOTE THE DIFFERENCE BETWEEN MM and mm! Month=MM, not mm!
// Examples:
//  "MMM d, y" matches: January 01, 2000
//                      Dec 1, 1900
//                      Nov 20, 00
//  "M/d/yy"   matches: 01/20/00
//                      9/2/00
//  "MMM dd, yyyy hh:mm:ssa" matches: "January 01, 2000 12:30:45AM"
// ------------------------------------------------------------------

var MONTH_NAMES=new Array('January','February','March','April','May','June','July','August','September','October','November','December','Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec');
var DAY_NAMES=new Array('Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday','Sun','Mon','Tue','Wed','Thu','Fri','Sat');
function LZ(x) {return(x<0||x>9?"":"0")+x}

// ------------------------------------------------------------------
// isDate ( date_string, format_string )
// Returns true if date string matches format of format string and
// is a valid date. Else returns false.
// It is recommended that you trim whitespace around the value before
// passing it to this function, as whitespace is NOT ignored!
// ------------------------------------------------------------------
function isDate(val,format) {
  var date=getDateFromFormat(val,format);
  if (date==0) { return false; }
  return true;
  }

// -------------------------------------------------------------------
// compareDates(date1,date1format,date2,date2format)
//   Compare two date strings to see which is greater.
//   Returns:
//   1 if date1 is greater than date2
//   0 if date2 is greater than date1 of if they are the same
//  -1 if either of the dates is in an invalid format
// -------------------------------------------------------------------
function compareDates(date1,dateformat1,date2,dateformat2) {
  var d1=getDateFromFormat(date1,dateformat1);
  var d2=getDateFromFormat(date2,dateformat2);
  if (d1==0 || d2==0) {
    return -1;
    }
  else if (d1 > d2) {
    return 1;
    }
  return 0;
  }

// ------------------------------------------------------------------
// formatDate (date_object, format)
// Returns a date in the output format specified.
// The format string uses the same abbreviations as in getDateFromFormat()
// ------------------------------------------------------------------
function formatDate(date,format) {
  format=format+"";
  var result="";
  var i_format=0;
  var c="";
  var token="";
  var y=date.getYear()+"";
  var M=date.getMonth()+1;
  var d=date.getDate();
  var E=date.getDay();
  var H=date.getHours();
  var m=date.getMinutes();
  var s=date.getSeconds();
  var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
  // Convert real date parts into formatted versions
  var value=new Object();
  if (y.length < 4) {y=""+(y-0+1900);}
  value["y"]=""+y;
  value["yyyy"]=y;
  value["yy"]=y.substring(2,4);
  value["M"]=M;
  value["MM"]=LZ(M);
  value["MMM"]=MONTH_NAMES[M-1];
  value["NNN"]=MONTH_NAMES[M+11];
  value["d"]=d;
  value["dd"]=LZ(d);
  value["E"]=DAY_NAMES[E+7];
  value["EE"]=DAY_NAMES[E];
  value["H"]=H;
  value["HH"]=LZ(H);
  if (H==0){value["h"]=12;}
  else if (H>12){value["h"]=H-12;}
  else {value["h"]=H;}
  value["hh"]=LZ(value["h"]);
  if (H>11){value["K"]=H-12;} else {value["K"]=H;}
  value["k"]=H+1;
  value["KK"]=LZ(value["K"]);
  value["kk"]=LZ(value["k"]);
  if (H > 11) { value["a"]="PM"; }
  else { value["a"]="AM"; }
  value["m"]=m;
  value["mm"]=LZ(m);
  value["s"]=s;
  value["ss"]=LZ(s);
  while (i_format < format.length) {
    c=format.charAt(i_format);
    token="";
    while ((format.charAt(i_format)==c) && (i_format < format.length)) {
      token += format.charAt(i_format++);
      }
    if (value[token] != null) { result=result + value[token]; }
    else { result=result + token; }
    }
  return result;
  }
  
// ------------------------------------------------------------------
// Utility functions for parsing in getDateFromFormat()
// ------------------------------------------------------------------
function _isInteger(val) {
  var digits="1234567890";
  for (var i=0; i < val.length; i++) {
    if (digits.indexOf(val.charAt(i))==-1) { return false; }
    }
  return true;
  }
function _getInt(str,i,minlength,maxlength) {
  for (var x=maxlength; x>=minlength; x--) {
    var token=str.substring(i,i+x);
    if (token.length < minlength) { return null; }
    if (_isInteger(token)) { return token; }
    }
  return null;
  }
  
// ------------------------------------------------------------------
// getDateFromFormat( date_string , format_string )
//
// This function takes a date string and a format string. It matches
// If the date string matches the format string, it returns the 
// getTime() of the date. If it does not match, it returns 0.
// ------------------------------------------------------------------
function getDateFromFormat(val,format) {
  val=val+"";
  format=format+"";
  var i_val=0;
  var i_format=0;
  var c="";
  var token="";
  var token2="";
  var x,y;
  var now=new Date();
  var year=now.getYear();
  var month=now.getMonth()+1;
  var date=1;
  var hh=now.getHours();
  var mm=now.getMinutes();
  var ss=now.getSeconds();
  var ampm="";
  
  while (i_format < format.length) {
    // Get next token from format string
    c=format.charAt(i_format);
    token="";
    while ((format.charAt(i_format)==c) && (i_format < format.length)) {
      token += format.charAt(i_format++);
      }
    // Extract contents of value based on format token
    if (token=="yyyy" || token=="yy" || token=="y") {
      if (token=="yyyy") { x=4;y=4; }
      if (token=="yy")   { x=2;y=2; }
      if (token=="y")    { x=2;y=4; }
      year=_getInt(val,i_val,x,y);
      if (year==null) { return 0; }
      i_val += year.length;
      if (year.length==2) {
        if (year > 70) { year=1900+(year-0); }
        else { year=2000+(year-0); }
        }
      }
    else if (token=="MMM"||token=="NNN"){
      month=0;
      for (var i=0; i<MONTH_NAMES.length; i++) {
        var month_name=MONTH_NAMES[i];
        if (val.substring(i_val,i_val+month_name.length).toLowerCase()==month_name.toLowerCase()) {
          if (token=="MMM"||(token=="NNN"&&i>11)) {
            month=i+1;
            if (month>12) { month -= 12; }
            i_val += month_name.length;
            break;
            }
          }
        }
      if ((month < 1)||(month>12)){return 0;}
      }
    else if (token=="EE"||token=="E"){
      for (var i=0; i<DAY_NAMES.length; i++) {
        var day_name=DAY_NAMES[i];
        if (val.substring(i_val,i_val+day_name.length).toLowerCase()==day_name.toLowerCase()) {
          i_val += day_name.length;
          break;
          }
        }
      }
    else if (token=="MM"||token=="M") {
      month=_getInt(val,i_val,token.length,2);
      if(month==null||(month<1)||(month>12)){return 0;}
      i_val+=month.length;}
    else if (token=="dd"||token=="d") {
      date=_getInt(val,i_val,token.length,2);
      if(date==null||(date<1)||(date>31)){return 0;}
      i_val+=date.length;}
    else if (token=="hh"||token=="h") {
      hh=_getInt(val,i_val,token.length,2);
      if(hh==null||(hh<1)||(hh>12)){return 0;}
      i_val+=hh.length;}
    else if (token=="HH"||token=="H") {
      hh=_getInt(val,i_val,token.length,2);
      if(hh==null||(hh<0)||(hh>23)){return 0;}
      i_val+=hh.length;}
    else if (token=="KK"||token=="K") {
      hh=_getInt(val,i_val,token.length,2);
      if(hh==null||(hh<0)||(hh>11)){return 0;}
      i_val+=hh.length;}
    else if (token=="kk"||token=="k") {
      hh=_getInt(val,i_val,token.length,2);
      if(hh==null||(hh<1)||(hh>24)){return 0;}
      i_val+=hh.length;hh--;}
    else if (token=="mm"||token=="m") {
      mm=_getInt(val,i_val,token.length,2);
      if(mm==null||(mm<0)||(mm>59)){return 0;}
      i_val+=mm.length;}
    else if (token=="ss"||token=="s") {
      ss=_getInt(val,i_val,token.length,2);
      if(ss==null||(ss<0)||(ss>59)){return 0;}
      i_val+=ss.length;}
    else if (token=="a") {
      if (val.substring(i_val,i_val+2).toLowerCase()=="am") {ampm="AM";}
      else if (val.substring(i_val,i_val+2).toLowerCase()=="pm") {ampm="PM";}
      else {return 0;}
      i_val+=2;}
    else {
      if (val.substring(i_val,i_val+token.length)!=token) {return 0;}
      else {i_val+=token.length;}
      }
    }
  // If there are any trailing characters left in the value, it doesn't match
  if (i_val != val.length) { return 0; }
  // Is date valid for month?
  if (month==2) {
    // Check for leap year
    if ( ( (year%4==0)&&(year%100 != 0) ) || (year%400==0) ) { // leap year
      if (date > 29){ return 0; }
      }
    else { if (date > 28) { return 0; } }
    }
  if ((month==4)||(month==6)||(month==9)||(month==11)) {
    if (date > 30) { return 0; }
    }
  // Correct hours value
  if (hh<12 && ampm=="PM") { hh=hh-0+12; }
  else if (hh>11 && ampm=="AM") { hh-=12; }
  var newdate=new Date(year,month-1,date,hh,mm,ss);
  return newdate.getTime();
  }

// ------------------------------------------------------------------
// parseDate( date_string [, prefer_euro_format] )
//
// This function takes a date string and tries to match it to a
// number of possible date formats to get the value. It will try to
// match against the following international formats, in this order:
// y-M-d   MMM d, y   MMM d,y   y-MMM-d   d-MMM-y  MMM d
// M/d/y   M-d-y      M.d.y     MMM-d     M/d      M-d
// d/M/y   d-M-y      d.M.y     d-MMM     d/M      d-M
// A second argument may be passed to instruct the method to search
// for formats like d/M/y (european format) before M/d/y (American).
// Returns a Date object or null if no patterns match.
// ------------------------------------------------------------------
function parseDate(val) {
  var preferEuro=(arguments.length==2)?arguments[1]:false;
  generalFormats=new Array('y-M-d','MMM d, y','MMM d,y','y-MMM-d','d-MMM-y','MMM d');
  monthFirst=new Array('M/d/y','M-d-y','M.d.y','MMM-d','M/d','M-d');
  dateFirst =new Array('d/M/y','d-M-y','d.M.y','d-MMM','d/M','d-M');
  var checkList=new Array('generalFormats',preferEuro?'dateFirst':'monthFirst',preferEuro?'monthFirst':'dateFirst');
  var d=null;
  for (var i=0; i<checkList.length; i++) {
    var l=window[checkList[i]];
    for (var j=0; j<l.length; j++) {
      d=getDateFromFormat(val,l[j]);
      if (d!=0) { return new Date(d); }
      }
    }
  return null;
  }
;
(function() {


}).call(this);
(function() {


}).call(this);
(function() {
  var f_add_enturne, f_asignar_camiones, f_cancelar_pedido, f_create_order, f_load_data_table, f_para_append_parameters, f_para_crear_parametros, f_para_customers_ddl, f_para_guardar_cambios, f_rep_cities_ddl, f_rep_customers_ddl, f_rep_placas_ddl, f_seg_cities_ddl, f_seg_customers_ddl, f_seg_placas_ddl, f_update_businesses_ddl, f_update_cities_ddl, f_update_customers_ddl, f_update_drivers_ddl, f_update_params, f_update_plants_ddl;

  f_update_customers_ddl = function() {
    var myEle;
    myEle = document.getElementById("new-orders");
    if (!myEle) {
      return false;
    }
    $('#plant_ddl option').remove();
    $('#city_ddl option').remove();
    $('#business_ddl option').remove();
    $('#customer_ddl option').remove();
    $("#flete_inp").val("");
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/orders/update_customers.json",
      success: function(data, textStatus) {
        $(data).each(function(i, v) {
          return $("#customer_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
        });
        return f_update_businesses_ddl($("#customer_ddl").val());
      },
      error: function(data) {}
    });
  };

  f_update_businesses_ddl = function(cli) {
    $('#plant_ddl option').remove();
    $('#city_ddl option').remove();
    $('#business_ddl option').remove();
    $("#flete_inp").val("");
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/orders/update_businesses/" + cli + ".json",
      success: function(data, textStatus) {
        return $(data).each(function(i, v) {
          $("#business_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
          return f_update_cities_ddl($("#customer_ddl").val(), $("#business_ddl").val());
        });
      },
      error: function(data) {}
    });
  };

  f_update_cities_ddl = function(cli, nego) {
    $('#plant_ddl option').remove();
    $('#city_ddl option').remove();
    $("#flete_inp").val("");
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/orders/update_cities/" + cli + "/" + nego + ".json",
      success: function(data, textStatus) {
        $("#flete_inp").val(data.flete + " $");
        $(data.lista).each(function(i, v) {
          return $("#city_ddl").append($("<option></option>").attr("value", v.id).attr("data-id", v.flete).text(v.name));
        });
        return f_update_plants_ddl(cli, $("#city_ddl").val());
      },
      error: function(data) {}
    });
  };

  f_update_plants_ddl = function(cli, cit) {
    var res;
    $('#plant_ddl option').remove();
    res = cit.split('-');
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/orders/update_plants/" + cli + "/" + res[1],
      success: function(data, textStatus) {
        return $(data).each(function(i, v) {
          return $("#plant_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
        });
      },
      error: function(data) {}
    });
  };

  f_update_params = function(cli) {
    $('#tbody-params tr').remove();
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/orders/update_params/" + cli,
      success: function(data, textStatus) {
        return $(data).each(function(i, v) {
          return $("#tbody-params").append($('<tr> <th scope="row">' + v.name + '</th> <td>' + v.value + '</td> </tr>'));
        });
      },
      error: function(data) {}
    });
  };

  f_create_order = function(u_algo) {
    return $.ajax({
      type: "POST",
      dataType: 'json',
      url: "/distribucion/orders/create_order",
      data: {
        algo: u_algo
      },
      success: function(resp, textStatus) {
        if (resp.ok) {
          return window.location.replace("../orders/new");
        } else {
          return window.location.replace("../orders/new");
        }
      },
      error: function(resp) {}
    });
  };

  f_update_drivers_ddl = function(cli, cit) {
    $('#conductor_ddl option').remove();
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/orders/update_drivers",
      success: function(data, textStatus) {
        return $(data).each(function(i, v) {
          return $("#conductor_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
        });
      },
      error: function(data) {}
    });
  };

  f_load_data_table = function(id, tp, ta, te) {
    var myEle;
    myEle = document.getElementById("admin-orders");
    if (!myEle) {
      return false;
    }
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/orders/get_data_table/" + id,
      success: function(data, textStatus) {
        if (id === "p") {
          return $(data).each(function(i, v) {
            var html_button;
            html_button = '';
            if (v.estado !== "Cancelado") {
              html_button = '<button type="button" class="btn btn-danger">Cancelar</button>';
            }
            return tp.row.add([v.codigo, v.cargue, v.cliente, v.fecha, v.num_lineas, v.estado, v.pedido_celular, v.cargue_realizado, v.entrega_realizada, html_button]).draw(false);
          });
        } else if (id === "a") {
          return $(data).each(function(i, v) {
            return ta.row.add([v.cargue, v.cod_clie, v.cliente, v.ciudad, v.cantidad_esp, v.camiones_disp, '<button type="button" class="btn btn-secondary asignar-camiones">Asignar</button> | <button type="button" class="btn btn-danger">Cancelar</button>']).draw(false);
          });
        } else if (id === "e") {
          return $(data).each(function(i, v) {
            return te.row.add([v.conductor, v.fecha, v.placa, v.capacidad]).draw(false);
          });
        }
      },
      error: function(resp) {
        return console.log(resp.message);
      }
    });
  };

  f_cancelar_pedido = function(cargue, id, tabla) {
    return $.ajax({
      type: "POST",
      dataType: 'json',
      url: "/distribucion/orders/cancel_order",
      data: {
        cargue: cargue
      },
      success: function(resp, textStatus) {
        if (resp.ok) {
          if (id === 'p') {
            tabla.clear().draw();
            f_load_data_table('p', tabla, null, null);
          } else if (id === 'a') {
            tabla.clear().draw();
            f_load_data_table('a', null, tabla, null);
          }
          $('#Modal-Error').modal('show');
          return $('#msg-error-modal').text(resp.msg);
        } else {
          $('#Modal-Error').modal('show');
          return $('#msg-error-modal').text(resp.msg);
        }
      },
      error: function(resp) {}
    });
  };

  f_add_enturne = function(codigo, te) {
    return $.ajax({
      type: "POST",
      dataType: 'json',
      url: "/distribucion/orders/enturnar_conductor_manual",
      data: {
        codigo: codigo
      },
      success: function(resp, textStatus) {
        if (resp.ok) {
          te.clear().draw();
          $('#Modal-Error').modal('show');
          $('#msg-error-modal').text(resp.msg);
          return f_load_data_table('e', null, null, te);
        } else {
          $('#Modal-Error').modal('show');
          return $('#msg-error-modal').text(resp.msg);
        }
      },
      error: function(resp) {}
    });
  };

  f_asignar_camiones = function(codigo, clie, ta) {
    return $.ajax({
      type: "POST",
      dataType: 'json',
      url: "/distribucion/orders/asignar_camiones_manual",
      data: {
        codigo: codigo,
        customer: clie
      },
      success: function(resp, textStatus) {
        if (resp.ok) {
          ta.clear().draw();
          $('#Modal-Error').modal('show');
          $('#msg-error-modal').text(resp.msg);
          return f_load_data_table('a', null, ta, null);
        } else {
          $('#Modal-Error').modal('show');
          return $('#msg-error-modal').text(resp.msg);
        }
      },
      error: function(resp) {}
    });
  };

  f_para_customers_ddl = function(cod) {
    var myEle;
    myEle = document.getElementById("parametros");
    if (!myEle) {
      return false;
    }
    $('#para-cliente_ddl option').remove();
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/orders/update_customers.json",
      success: function(data, textStatus) {
        $(data).each(function(i, v) {
          return $("#para-cliente_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
        });
        return f_para_append_parameters($("#para-cliente_ddl").val());
      },
      error: function(data) {}
    });
  };

  f_para_append_parameters = function(clie) {
    $('#parametros').empty();
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/orders/update_params/" + clie + ".json",
      success: function(data, textStatus) {
        return $(data).each(function(i, v) {
          return $('#parametros').append('<div class="param-linea">' + '<div class="txt-iz"><b>' + v.name + '<b></div>' + '<div class="txt-der"><input id="' + v.id + '" value="' + v.value + '" /></div></div>');
        });
      },
      error: function(data) {}
    });
  };

  f_para_guardar_cambios = function(lista_param) {
    return $.ajax({
      type: "POST",
      dataType: 'json',
      url: "/distribucion/orders/guardar_cambios_parametros_cliente",
      data: {
        params: lista_param
      },
      success: function(resp, textStatus) {
        if (resp) {
          return alert('Los cambios fueron guardados');
        } else {
          return alert('No se pudieron guardar los cambios');
        }
      },
      error: function(resp) {}
    });
  };

  f_para_crear_parametros = function(clie) {
    console.log('click');
    return $.ajax({
      type: "POST",
      dataType: 'json',
      url: "/distribucion/orders/crear_parametros_cliente_post",
      data: {
        clie: clie
      },
      success: function(resp, textStatus) {
        if (resp) {
          alert('Los parámetros fueron creados');
          return f_para_append_parameters(clie);
        } else {
          alert('No se pudo crear los parámetros');
          return f_para_append_parameters(clie);
        }
      },
      error: function(resp) {
        return console.log('error');
      }
    });
  };

  f_rep_customers_ddl = function(cod) {
    var myEle;
    myEle = document.getElementById("reportes");
    if (!myEle) {
      return false;
    }
    if (cod === 0) {
      $('#res-cliente_ddl option:gt(0)').remove();
      $('#res-ciudad_ddl option:gt(0)').remove();
      $('#res-placa_ddl option:gt(0)').remove();
    } else if (cod === 1) {
      $('#iti-cliente_ddl option:gt(0)').remove();
      $('#iti-ciudad_ddl option:gt(0)').remove();
      $('#iti-placa_ddl option:gt(0)').remove();
    }
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/reportes/update_customers.json",
      success: function(data, textStatus) {
        $(data).each(function(i, v) {
          if (cod === 0) {
            return $("#res-cliente_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
          } else if (cod === 1) {
            return $("#iti-cliente_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
          }
        });
        return f_rep_cities_ddl(cod);
      },
      error: function(data) {}
    });
  };

  f_rep_cities_ddl = function(cod) {
    var cli;
    cli = 0;
    if (cod === 0) {
      cli = $("#res-cliente_ddl").val();
      $('#res-ciudad_ddl option:gt(0)').remove();
      $('#res-placa_ddl option:gt(0)').remove();
    } else if (cod === 1) {
      cli = $("#iti-cliente_ddl").val();
      $('#iti-ciudad_ddl option:gt(0)').remove();
      $('#iti-placa_ddl option:gt(0)').remove();
    }
    if (cli === null || cli === "") {
      return false;
    }
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/reportes/update_cities/" + cli + ".json",
      success: function(data, textStatus) {
        $(data).each(function(i, v) {
          if (cod === 0) {
            return $("#res-ciudad_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
          } else if (cod === 1) {
            return $("#iti-ciudad_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
          }
        });
        return f_rep_placas_ddl(cod, cli);
      },
      error: function(data) {}
    });
  };

  f_rep_placas_ddl = function(cod, cli) {
    var cit;
    cit = 0;
    $('#placa_ddl option:gt(0)').remove();
    if (cod === 0) {
      cit = $("#res-ciudad_ddl").val();
      $('#res-placa_ddl option:gt(0)').remove();
    } else if (cod === 1) {
      cit = $("#iti-ciudad_ddl").val();
      $('#iti-placa_ddl option:gt(0)').remove();
    }
    if (cit === null || cit === "") {
      return false;
    }
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/reportes/update_placas/" + cli + "/" + cit,
      success: function(data, textStatus) {
        return $(data).each(function(i, v) {
          if (cod === 0) {
            return $("#res-placa_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
          } else if (cod === 1) {
            return $("#iti-placa_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
          }
        });
      },
      error: function(data) {}
    });
  };

  f_seg_customers_ddl = function(iti) {
    var myEle;
    myEle = document.getElementById("seguimiento");
    if (!myEle) {
      return false;
    }
    if (!iti) {
      $("#seg-placa_ddl").find("option:not(:first)").remove();
      $("#seg-ciudad_ddl").find("option:not(:first)").remove();
      $("#seg-cliente_ddl").find("option:not(:first)").remove();
    } else {
      $("#seg-iti-placa_ddl").find("option").remove();
      $("#seg-iti-ciudad_ddl").find("option").remove();
      $("#seg-iti-cliente_ddl").find("option").remove();
    }
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/seguimientos/update_customers.json",
      success: function(data, textStatus) {
        $(data).each(function(i, v) {
          if (!iti) {
            return $("#seg-cliente_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
          } else {
            return $("#seg-iti-cliente_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
          }
        });
        return f_seg_cities_ddl($("#seg-cliente_ddl").val(), iti);
      },
      error: function(data) {}
    });
  };

  f_seg_cities_ddl = function(cli, iti) {
    if (iti) {
      $('#seg-placa_ddl option:gt(0)').remove();
      $('#seg-ciudad_ddl option:gt(0)').remove();
    } else {
      $('#seg-iti-placa_ddl option:gt(0)').remove();
      $('#seg-iti-ciudad_ddl option:gt(0)').remove();
    }
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/seguimientos/update_cities/" + cli + ".json",
      success: function(data, textStatus) {
        $(data).each(function(i, v) {
          if (iti) {
            return $("#seg-ciudad_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
          } else {
            return $("#seg-iti-ciudad_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
          }
        });
        return f_seg_placas_ddl(cli, $("#seg-ciudad_ddl").val(), iti);
      },
      error: function(data) {}
    });
  };

  f_seg_placas_ddl = function(cli, cit, iti) {
    if (iti) {
      $('#seg-placa_ddl option:gt(0)').remove();
    } else {
      $('#seg-iti-placa_ddl option:gt(0)').remove();
    }
    return $.ajax({
      type: "GET",
      dataType: 'json',
      url: "/distribucion/seguimientos/update_placas/" + cli + "/" + cit,
      success: function(data, textStatus) {
        return $(data).each(function(i, v) {
          if (iti) {
            return $("#seg-placa_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
          } else {
            return $("#seg-iti-placa_ddl").append($("<option></option>").attr("value", v.id).text(v.name));
          }
        });
      },
      error: function(data) {}
    });
  };

  $(document).ready(function() {
    var chart1, chart2, chart3, chart4, f_load_data_graphycs, myChart, ta, te, temp, tp, tr, u_algo;
    $('[data-toggle="tooltip"]').tooltip();
    u_algo = false;
    temp = [];
    f_update_customers_ddl();
    $("#customer_ddl").change(function() {
      var cli;
      cli = $(this).val();
      return f_update_businesses_ddl(cli);
    });
    $("#business_ddl").change(function() {
      var cli, nego;
      cli = $("#customer_ddl").val();
      nego = $(this).val();
      return f_update_cities_ddl(cli, nego);
    });
    $("#city_ddl").change(function() {
      var cit, cli;
      cli = $("#customer_ddl").val();
      cit = $(this).val();
      $("#flete_inp").val($(this).children("option:selected").data("id") + " $");
      return f_update_plants_ddl(cli, cit);
    });
    $('#use_algo').change(function() {
      if (this.checked) {
        return $(".right").show();
      } else {
        return $(".right").hide();
      }
    });
    $('#btn-params').click(function() {
      var cli;
      cli = $("#customer_ddl").val();
      return f_update_params(cli);
    });
    $('#btn-confirm-cancel').click(function() {
      return window.location.replace("../orders/new");
    });
    $('#btn-confirm-continue').click(function() {
      $(this).prop("disabled", true);
      return f_create_order(u_algo);
    });
    $('#btn-confirm-summary-1').click(function() {
      return $('.msg-raw').fadeOut("normal", function() {
        $('.div-btn-confirm').hide();
        $('.table-hide').fadeIn("normal");
        return $('.div-btn-continue').show();
      });
    });
    $('#btn-confirm-summary-2').click(function() {
      u_algo = true;
      $('#td-algo').html('SI');
      return $('.msg-raw').fadeOut("normal", function() {
        $('.div-btn-confirm').hide();
        $('.table-hide').fadeIn("normal");
        return $('.div-btn-continue').show();
      });
    });
    tp = $('#table-pedido').DataTable({
      "columnDefs": [
        {
          "targets": [1],
          "visible": false,
          "searchable": false
        }, {
          "orderable": false,
          "targets": 9
        }
      ],
      "language": {
        "url": "/assets/datatables/datatables_spanish.json"
      }
    });
    tr = $('#table-rutas').DataTable({
      "language": {
        "url": "/assets/datatables/datatables_spanish.json"
      }
    });
    ta = $('#table-asignacion').DataTable({
      "columnDefs": [
        {
          "orderable": false,
          "targets": 6
        }
      ],
      "language": {
        "url": "/assets/datatables/datatables_spanish.json"
      }
    });
    te = $('#table-enturne').DataTable({
      "language": {
        "url": "/assets/datatables/datatables_spanish.json"
      }
    });
    f_load_data_table('p', tp, ta, te);
    $('.nav-link-asg').click(function() {
      tp.clear().draw();
      tr.clear().draw();
      ta.clear().draw();
      te.clear().draw();
      return f_load_data_table(this.id, tp, ta, te);
    });
    $('#load_drivers').click(function() {
      return f_update_drivers_ddl();
    });
    $('#add-enturne').click(function() {
      var codigo;
      codigo = $("#conductor_ddl").val();
      return f_add_enturne(codigo, te);
    });
    $('#table-asignacion tbody').on('click', '.asignar-camiones', function() {
      var clie, codigo;
      codigo = ta.rows($(this).parents('tr')).data()[0][0];
      clie = ta.rows($(this).parents('tr')).data()[0][1];
      return f_asignar_camiones(codigo, clie, ta);
    });
    $('#table-pedido tbody').on('click', '.btn-danger', function() {
      var cargue;
      cargue = tp.rows($(this).parents('tr')).data()[0][1];
      return f_cancelar_pedido(cargue, 'p', tp);
    });
    $('#table-asignacion tbody').on('click', '.btn-danger', function() {
      var codigo;
      codigo = ta.rows($(this).parents('tr')).data()[0][0];
      return f_cancelar_pedido(codigo, 'a', ta);
    });
    f_para_customers_ddl();
    $("#para-cliente_ddl").change(function() {
      var clie;
      clie = $(this).val();
      return f_para_append_parameters(clie);
    });
    $("#para-save").click(function() {
      var lista_param;
      lista_param = [];
      $('.txt-der input').each(function(i, e) {
        temp = {
          id: e.id,
          value: e.value
        };
        return lista_param.push(temp);
      });
      return f_para_guardar_cambios(lista_param);
    });
    $("#para-create").click(function() {
      var clie;
      clie = $('#para-cliente_ddl').val();
      return f_para_crear_parametros(clie);
    });
    $("#modal-edit-params").click(function() {
      var ruta;
      ruta = '/distribucion/orders/parametros';
      return window.open(ruta, '_blank');
    });
    $("#res-cliente_ddl").change(function() {
      return f_rep_cities_ddl(0);
    });
    $("#iti-cliente_ddl").change(function() {
      return f_rep_cities_ddl(1);
    });
    $("#res-ciudad_ddl").change(function() {
      var cli;
      cli = $("#res-cliente_ddl").val();
      return f_rep_placas_ddl(0, cli);
    });
    $("#iti-ciudad_ddl").change(function() {
      var cli;
      cli = $("#iti-cliente_ddl").val();
      return f_rep_placas_ddl(1, cli);
    });
    f_rep_customers_ddl(0);
    f_rep_customers_ddl(1);
    myChart = document.getElementById("rep-graficas");
    if (myChart) {
      chart1 = new Highcharts.chart('container1', {
        chart: {
          type: 'column'
        },
        title: {
          text: ''
        },
        xAxis: {
          type: 'category'
        },
        yAxis: {
          title: {
            text: 'Porcentaje'
          }
        },
        legend: {
          enabled: false
        },
        plotOptions: {
          series: {
            borderWidth: 0,
            dataLabels: {
              enabled: true,
              format: '{point.y:.1f}%'
            }
          }
        },
        credits: {
          enabled: false
        },
        tooltip: {
          headerFormat: '<span style="font-size:11px">{series.name}</span><br>',
          pointFormat: '<span style="color:{point.color}">{point.name}</span> <b>{point.y:.2f}%</b><br/>'
        },
        series: [
          {
            name: 'Entregas',
            colorByPoint: true,
            data: []
          }
        ],
        drilldown: {
          series: []
        }
      });
      chart2 = new Highcharts.chart('container2', {
        chart: {
          type: 'column'
        },
        title: {
          text: ''
        },
        xAxis: {
          type: 'category'
        },
        yAxis: {
          title: {
            text: 'Porcentaje'
          }
        },
        legend: {
          enabled: false
        },
        plotOptions: {
          series: {
            borderWidth: 0,
            dataLabels: {
              enabled: true,
              format: '{point.y:.1f}%'
            }
          }
        },
        credits: {
          enabled: false
        },
        tooltip: {
          headerFormat: '<span style="font-size:11px">{series.name}</span><br>',
          pointFormat: '<span style="color:{point.color}">{point.name}</span> <b>{point.y:.2f}%</b><br/>'
        },
        series: [
          {
            name: 'Entregas',
            colorByPoint: true,
            data: []
          }
        ],
        drilldown: {
          series: []
        }
      });
      chart3 = new Highcharts.chart('container3', {
        chart: {
          type: 'column'
        },
        title: {
          text: ''
        },
        xAxis: {
          type: 'category'
        },
        yAxis: {
          title: {
            text: 'Porcentaje'
          }
        },
        legend: {
          enabled: false
        },
        plotOptions: {
          series: {
            borderWidth: 0,
            dataLabels: {
              enabled: true,
              format: '{point.y:.1f}%'
            }
          }
        },
        credits: {
          enabled: false
        },
        tooltip: {
          headerFormat: '<span style="font-size:11px">{series.name}</span><br>',
          pointFormat: '<span style="color:{point.color}">{point.name}</span> <b>{point.y:.2f}%</b><br/>'
        },
        series: [
          {
            name: 'Entregas',
            colorByPoint: true,
            data: []
          }
        ],
        drilldown: {
          series: []
        }
      });
      chart4 = new Highcharts.chart('container4', {
        chart: {
          type: 'packedbubble',
          height: '100%'
        },
        title: {
          text: ''
        },
        tooltip: {
          useHTML: true,
          pointFormat: '<b>{point.name}:</b> <br> <b>{point.desc}:</b> {point.value}'
        },
        plotOptions: {
          packedbubble: {
            minSize: '30%',
            maxSize: '120%',
            zMin: 0,
            zMax: 1000,
            layoutAlgorithm: {
              gravitationalConstant: 0.06,
              splitSeries: false,
              seriesInteraction: true,
              dragBetweenSeries: false
            },
            dataLabels: {
              enabled: true,
              format: '{point.name}',
              filter: {
                property: 'y',
                operator: '>',
                value: 250
              },
              style: {
                color: 'black',
                textOutline: 'none',
                fontWeight: 'normal'
              }
            }
          }
        },
        series: []
      });
      $('#des-res').bind('click', function() {
        if (!$("#res-cliente_ddl").val() || !$("#res-ciudad_ddl").val() || !$("#input-desde").val() || !$("#input-hasta").val()) {
          return false;
        }
        return $.ajax({
          type: "POST",
          dataType: 'json',
          url: "/distribucion/reportes/export_csv",
          data: {
            client: $("#res-cliente_ddl").val(),
            city: $("#res-ciudad_ddl").val(),
            placa: $("#res-placa_ddl").val(),
            estado: $("#res-estado_ddl").val(),
            desde: $("#input-desde").val(),
            hasta: $("#input-hasta").val()
          },
          success: function(data, textStatus) {
            var element;
            element = document.createElement('a');
            element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(data.reporte));
            element.setAttribute('download', 'reporte.csv');
            element.style.display = 'none';
            document.body.appendChild(element);
            element.click();
            return document.body.removeChild(element);
          },
          error: function(data) {}
        });
      });
      $('#graficar').bind('click', function() {
        f_load_data_graphycs();
      });
      f_load_data_graphycs = function() {
        if (!$("#res-cliente_ddl").val() || !$("#res-ciudad_ddl").val() || !$("#input-desde").val() || !$("#input-hasta").val()) {
          return false;
        }
        return $.ajax({
          type: "POST",
          dataType: 'json',
          url: "/distribucion/reportes/datos_graficas",
          data: {
            client: $("#res-cliente_ddl").val(),
            city: $("#res-ciudad_ddl").val(),
            placa: $("#res-placa_ddl").val(),
            estado: $("#res-estado_ddl").val(),
            desde: $("#input-desde").val(),
            hasta: $("#input-hasta").val()
          },
          success: function(data, textStatus) {
            chart1.setTitle({
              text: data.g1.titulo
            });
            chart1.series[0].update({
              data: data.g1.data
            }, false);
            chart1.redraw();
            chart2.setTitle({
              text: data.g2.titulo
            });
            chart2.series[0].update({
              data: data.g2.data
            }, false);
            chart2.redraw();
            chart3.setTitle({
              text: data.g3.titulo
            });
            chart3.series[0].update({
              data: data.g3.data
            }, false);
            chart3.redraw();
            chart4.setTitle({
              text: data.g4.titulo
            });
            if (chart4.series.length > 0) {
              $(data.g4.data).each(function(i, v) {
                return chart4.series[i].update({
                  data: v.data,
                  name: v.name
                }, false);
              });
            } else {
              $(data.g4.data).each(function(i, v) {
                return chart4.addSeries(v);
              });
            }
            return chart4.redraw();
          },
          error: function(data) {
            return console.log(data.responseText);
          }
        });
      };
      $('#des-iti').bind('click', function() {
        var city, client, desde, hasta, placa, ruta;
        if (!$("#iti-cliente_ddl").val() || !$("#iti-ciudad_ddl").val() || !$("#iti-placa_ddl").val() || !$("#input-desde").val() || !$("#input-hasta").val()) {
          return false;
        }
        client = $("#iti-cliente_ddl").val();
        city = $("#iti-ciudad_ddl").val();
        placa = $("#iti-placa_ddl").val();
        desde = $("#input-desde").val().replace(/\//g, " ");
        hasta = $("#input-hasta").val().replace(/\//g, " ");
        ruta = '/distribucion/reportes/download_pdf/' + client + '/' + city + '/' + placa + '/' + desde + '/' + hasta + '';
        window.open(ruta, '_blank');
      });
    }
    f_seg_customers_ddl(false);
    f_seg_customers_ddl(true);
    $("#seg-cliente_ddl").change(function() {
      var cli;
      cli = $(this).val();
      return f_seg_cities_ddl(cli, false);
    });
    $("#seg-ciudad_ddl").change(function() {
      var cit, cli;
      cli = $("#seg-cliente_ddl").val();
      cit = $("#seg-ciudad_ddl").val();
      return f_seg_placas_ddl(cli, cit, false);
    });
    $('#seg-consultar-iti').bind('click', function() {
      var city, clie, placa, ruta;
      clie = $("#seg-iti-cliente_ddl").val();
      city = $("#seg-iti-ciudad_ddl").val();
      placa = $("#seg-iti-placa_ddl").val();
      if (!clie || !city || !placa) {
        return;
      }
      ruta = '/distribucion/seguimientos/itinerario/' + clie + '/' + city + '/' + placa;
      window.open(ruta, '_blank');
    });
    $("#seg-iti-cliente_ddl").change(function() {
      var cli;
      cli = $(this).val();
      return f_seg_cities_ddl(cli, true);
    });
    $("#seg-iti-cliente_ddl").change(function() {
      var cit, cli;
      cli = $("#seg-iti-cliente_ddl").val();
      cit = $(this).val();
      return f_seg_placas_ddl(cli, cit, true);
    });
    $('.btn-iti-toogle').bind('click', function() {
      if ($(this).find("i").text() === 'keyboard_arrow_down') {
        return $(this).find("i").text('keyboard_arrow_left');
      } else {
        return $(this).find("i").text('keyboard_arrow_down');
      }
    });
    myChart = document.getElementById("graficas");
    if (myChart) {
      chart1 = new Highcharts.chart('container1', {
        chart: {
          type: 'column'
        },
        title: {
          text: ''
        },
        xAxis: {
          type: 'category'
        },
        yAxis: {
          title: {
            text: 'Porcentaje'
          }
        },
        legend: {
          enabled: false
        },
        plotOptions: {
          series: {
            borderWidth: 0,
            dataLabels: {
              enabled: true,
              format: '{point.y:.1f}%'
            }
          }
        },
        credits: {
          enabled: false
        },
        tooltip: {
          headerFormat: '<span style="font-size:11px">{series.name}</span><br>',
          pointFormat: '<span style="color:{point.color}">{point.name}</span> <b>{point.y:.2f}%</b><br/>'
        },
        series: [
          {
            name: 'Entregas',
            colorByPoint: true,
            data: []
          }
        ],
        drilldown: {
          series: []
        }
      });
      chart2 = new Highcharts.chart('container2', {
        chart: {
          type: 'column'
        },
        title: {
          text: ''
        },
        xAxis: {
          type: 'category'
        },
        yAxis: {
          title: {
            text: 'Porcentaje'
          }
        },
        legend: {
          enabled: false
        },
        plotOptions: {
          series: {
            borderWidth: 0,
            dataLabels: {
              enabled: true,
              format: '{point.y:.1f}%'
            }
          }
        },
        credits: {
          enabled: false
        },
        tooltip: {
          headerFormat: '<span style="font-size:11px">{series.name}</span><br>',
          pointFormat: '<span style="color:{point.color}">{point.name}</span> <b>{point.y:.2f}%</b><br/>'
        },
        series: [
          {
            name: 'Entregas',
            colorByPoint: true,
            data: []
          }
        ],
        drilldown: {
          series: []
        }
      });
      chart3 = new Highcharts.chart('container3', {
        chart: {
          type: 'column'
        },
        title: {
          text: ''
        },
        xAxis: {
          type: 'category'
        },
        yAxis: {
          title: {
            text: 'Porcentaje'
          }
        },
        legend: {
          enabled: false
        },
        plotOptions: {
          series: {
            borderWidth: 0,
            dataLabels: {
              enabled: true,
              format: '{point.y:.1f}%'
            }
          }
        },
        credits: {
          enabled: false
        },
        tooltip: {
          headerFormat: '<span style="font-size:11px">{series.name}</span><br>',
          pointFormat: '<span style="color:{point.color}">{point.name}</span> <b>{point.y:.2f}%</b><br/>'
        },
        series: [
          {
            name: 'Entregas',
            colorByPoint: true,
            data: []
          }
        ],
        drilldown: {
          series: []
        }
      });
      chart4 = new Highcharts.chart('container4', {
        chart: {
          type: 'packedbubble',
          height: '100%'
        },
        title: {
          text: ''
        },
        tooltip: {
          useHTML: true,
          pointFormat: '<b>{point.name}:</b> <br> <b>{point.desc}:</b> {point.value}'
        },
        plotOptions: {
          packedbubble: {
            minSize: '30%',
            maxSize: '120%',
            zMin: 0,
            zMax: 1000,
            layoutAlgorithm: {
              gravitationalConstant: 0.06,
              splitSeries: false,
              seriesInteraction: true,
              dragBetweenSeries: false
            },
            dataLabels: {
              enabled: true,
              format: '{point.name}',
              filter: {
                property: 'y',
                operator: '>',
                value: 250
              },
              style: {
                color: 'black',
                textOutline: 'none',
                fontWeight: 'normal'
              }
            }
          }
        },
        series: []
      });
      setInterval((function() {
        f_load_data_graphycs();
      }), 60000 * 7);
      $('#buscar').bind('click', function() {
        f_load_data_graphycs();
        f_dar_puntos_mapa();
      });
      f_load_data_graphycs = function() {
        var city, clie, estado, placa;
        clie = $("#seg-cliente_ddl").val();
        city = $("#seg-ciudad_ddl").val();
        placa = $("#seg-placa_ddl").val();
        estado = $("#seg-estado_ddl").val();
        return $.ajax({
          type: "GET",
          dataType: 'json',
          url: "/distribucion/seguimientos/datos_graficas/" + clie + "/" + city + "/" + placa + "/" + estado,
          success: function(data, textStatus) {
            var dt;
            dt = data.datos_tabla;
            $('#porcentaje-entregas_realizadas').html(dt.entregas_realizadas + ' %');
            $('#porcentaje-entregas_optimas').html(dt.entregas_optimas + ' %');
            $('#porcentaje-kg_entregados').html(dt.Kg_entregados + ' %');
            $('#porcentaje-entregas_a_tiempo').html(dt.entregas_a_tiempo + ' %');
            $('#porcentaje-unidades_entregadas').html(dt.unidades_entregadas + ' %');
            $('#porcentaje-km_recorridos').html(dt.km_recorridos + ' Km');
            chart1.setTitle({
              text: data.g1.titulo
            });
            chart1.series[0].update({
              data: data.g1.data
            }, false);
            chart1.redraw();
            chart2.setTitle({
              text: data.g2.titulo
            });
            chart2.series[0].update({
              data: data.g2.data
            }, false);
            chart2.redraw();
            chart3.setTitle({
              text: data.g3.titulo
            });
            chart3.series[0].update({
              data: data.g3.data
            }, false);
            chart3.redraw();
            chart4.setTitle({
              text: data.g4.titulo
            });
            if (chart4.series.length > 0) {
              $(data.g4.data).each(function(i, v) {
                return chart4.series[i].update({
                  data: v.data,
                  name: v.name
                }, false);
              });
            } else {
              $(data.g4.data).each(function(i, v) {
                return chart4.addSeries(v);
              });
            }
            return chart4.redraw();
          },
          error: function(data) {
            return console.log('error');
          }
        });
      };
      return f_load_data_graphycs();
    }
  });

}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
$( document ).on('turbolinks:load', function() {
    $("#epp_workshop_headquarter_id").on('change', function() {
        var headquarter = $( "#epp_workshop_headquarter_id option:selected" ).val();
        $('#epp_workshop_warehouse_id').find('option:not(:first)').remove();
        $.ajax({
            url: '/workshop/warehouses/get_warehouses_by_headquarter/'+headquarter+'.json',
            type: "get",
            async: false,
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+ token );
            },
            success: function(result) {
                if (result.success == true) {
                    (result.data);
                    warehouses = result.data
                    $.each(warehouses, function (index,item) {
                        $("#epp_workshop_warehouse_id").append('<option value="'+item.id+'">' + item.name + '</option>')
                        $("#epp_workshop_warehouse_id").focus()
                    });
                }
                else {
                }
            }
        });
    });
    
    $('#epp_minimum_inventory').on('change',function(){
		$('#epp_maximum_inventory').prop('min', parseInt($(this).val()));
	});
})
;
$(document).on('turbolinks:load', function() {

  $('#addContactExtenalWorkshop').on('click', function(){
    AddContactExternalWorshop();
  });

  function AddContactExternalWorshop(){
    var $another_contact = $("#contact_external_workshop_div").clone();
    $("#another_contact_name").append($another_contact)
  }

  $("#div_other_contacts_external_workshop").on('click', '.delete_contact_external_worshop',  function(){
    $(this).parent().parent().parent().remove()
    return false
  })


  $(document).on('change', 'select#externals_workshop_department_id', function() {
    var id_value_string = $(this).val();
    var select = $('select.city');
    if (id_value_string == "") {
     $(select).children('option').remove();
     var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
     $(row).appendTo(select);
		} else {      //Send the request and update person_city_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/system/tables/cities/getcitiesbydepartment/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					alert("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { // Clear all options from person_city_id select
					if (data.success){
						$(select).children('option').remove();  //put in a empty default line
						var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
						$(row).appendTo(select);   // Fill person_city_id select
						$.each(data.data, function(i, j) {
							row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
							$(row).appendTo(select);
						});
					}
				}
			});
		}
	});

  if ($('#externals_workshop_address_text').length) {
		// para habilitar el campo externals_workshop_address_text
		$("#externals_workshop_city_id").on('change', function() {
			if ( $("#externals_workshop_city_id").val() != "") $('#externals_workshop_address_text').removeAttr("disabled")
		});
		// Rellena el campo ciudad
		if ($("#externals_workshop_department_id").val() != "") {
			var row = "<option value=\"" + cdad_id + "\" selected >" + cdad + "</option>";
			$(row).appendTo("#externals_workshop_city_id");
		}
	}

});


function initializeExternalWorkshop() {
	function geocodeAddress(geocoder, resultsMap) {
		var address = $('#externals_workshop_address_text').val()+", "+$('#externals_workshop_city_id option:selected').text()+", "+$('#externals_workshop_department_id option:selected').text();
		geocoder.geocode({'address': address}, function(results, status) {
      if (status === 'OK') {
       resultsMap.setCenter(results[0].geometry.location);
       marker = new google.maps.Marker({
         map: resultsMap,
         position: results[0].geometry.location,
         draggable: true
       });

       document.getElementById("externals_workshop_lat").value = marker.getPosition().lat().toString();
       document.getElementById("externals_workshop_lng").value = marker.getPosition().lng().toString();

       google.maps.event.addListener(marker,'dragend',function(event) {
        document.getElementById("externals_workshop_lat").value = this.getPosition().lat().toString();
        document.getElementById("externals_workshop_lng").value = this.getPosition().lng().toString();
      });
     } else {
       alert('Geocode was not successful for the following reason: ' + status);
     }
   });
	}
	var geocoder = new google.maps.Geocoder();
  if (document.getElementById('externals_workshop_address_text') != null ){
    document.getElementById('externals_workshop_address_text').addEventListener('change', function() {
      geocodeAddress(geocoder, map);
    });
  }

}

// https://developers.google.com/maps/documentation/javascript/examples/drawing-tools
var map, drawingManager, currentPolygon;

function initMapExternalWorkshop() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: parseFloat($("#externals_workshop_lat").val()) || 4.678457, lng: parseFloat($("#externals_workshop_lng").val()) || -74.059232},
    zoom: 18
  });

  drawingManager = new google.maps.drawing.DrawingManager({
    drawingMode: google.maps.drawing.OverlayType.POLYGON,
    drawingControl: false,
    polygonOptions: {
    	fillColor: '#FF0000',
    	fillOpacity: 0.4,
    	strokeColor: "#000000",
    	strokeWeight: 3,
    	clickable: false,
    	editable: true
    }
  });
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setMap(map);
  }

  if ( $("#externals_workshop_lat").val() && parseInt( $("#externals_workshop_lat").val()) != 0  && $("#externals_workshop_polypoints").val()) {
  	drawingManager.setDrawingMode(null);
    $("#reDraw").removeClass("hide");

    var polygonCoords = [];
    JSON.parse($("#externals_workshop_polypoints").val()).forEach(function(polypoint) {
      polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
    })
    var polygon = new google.maps.Polygon({
     paths: polygonCoords,
     strokeColor: '#000000',
     fillColor: '#FF0000',
     fillOpacity: 0.4,
     editable: true
   });
    currentPolygon = polygon;
    google.maps.event.addListener(polygon.getPath(), 'set_at', function() {
     $("#externals_workshop_polypoints").val(setPolypoints(polygon.getPath()));
   });
    google.maps.event.addListener(polygon.getPath(), 'insert_at', function() {
     $("#externals_workshop_polypoints").val(setPolypoints(polygon.getPath()));
   });
    polygon.setMap(map);

    var center = new google.maps.LatLng($("#externals_workshop_lat").val(), $("#externals_workshop_lng").val());

  }
  google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
  	if (event.type == 'polygon') {
  		$("#reDraw").removeClass("hide");
  		$("#externals_workshop_polypoints").val(setPolypoints(event.overlay.getPath()));
  		currentPolygon = event.overlay;
      google.maps.event.addListener(event.overlay.getPath(), 'set_at', function() {
        $("#externals_workshop_polypoints").val(setPolypoints(event.overlay.getPath()));
      });
      google.maps.event.addListener(event.overlay.getPath(), 'insert_at', function() {
        $("#externals_workshop_polypoints").val(setPolypoints(event.overlay.getPath()));
      });
      drawingManager.setDrawingMode(null);
    }
  });
  google.maps.event.addDomListener(window, 'load', initializeExternalWorkshop);
}

function initMapExternalWorkshopShow() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: parseFloat($("#externals_workshop_lat").val()) || 4.678457, lng: parseFloat($("#externals_workshop_lng").val()) || -74.059232},
    zoom: 18
  });
  if ( $("#externals_workshop_lat").val() && $("#externals_workshop_polypoints").val()) {
    var polygonCoords = [];
    JSON.parse($("#externals_workshop_polypoints").val()).forEach(function(polypoint) {
      polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
    })
    var polygon = new google.maps.Polygon({
      paths: polygonCoords,
      strokeColor: '#000000',
      fillColor: '#FF0000',
      fillOpacity: 0.4,
      editable: false
    });
    currentPolygon = polygon;
    google.maps.event.addListener(polygon.getPath(), 'set_at', function() {
      $("#externals_workshop_polypoints").val(setPolypoints(polygon.getPath()));
    });
    google.maps.event.addListener(polygon.getPath(), 'insert_at', function() {
      $("#externals_workshop_polypoints").val(setPolypoints(polygon.getPath()));
    });
    polygon.setMap(map);
  }
}

function removePolyline() {
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
  }
  if (currentPolygon) {
    currentPolygon.setMap(null);
    currentPolygon = null;
  }
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setMap(map);
  }
  $("#reDraw").addClass("hide");
}

function getBoundsForPoly(poly) {
	var bounds = new google.maps.LatLngBounds;
	poly.getPath().forEach(function(latLng) {
		bounds.extend(latLng);
	});
	return bounds;
}

function setPolypoints(path){
	var polypoints = [];
	path.forEach(function(element) {
    polypoints.push('{"lat": "' + element.lat() + '", "lng": "' + element.lng() + '"}');
  })
  return '['+polypoints.toString()+']';
}

function setPolypointsGeo(path){
  var polypoints = [];
  $.each(path, function(k, element) {
    polypoints.push('{"lat": "' + element.lat + '", "lng": "' + element.lng + '"}');
  });
  return '['+polypoints.toString()+']';
}

function validPolyline(){
  // if ($("#address_status").val() == true){
  //   if (!currentPolygon || !$("#externals_workshop_polypoints").val() || !document.getElementById("externals_workshop_lat").value || !document.getElementById("externals_workshop_lng").value){
  //     alert('Debe agregar una polígono en el mapa para continuar');
  //     return false;
  //   }
  // }
}

;
$( document ).on('turbolinks:load', () => {
    const reassingGasTcktForm = $("#reassingGasTicketForm")

    reassingGasTcktForm.on('submit', event => {
        event.preventDefault()
        let liquidationRadioChecked = false

        // Tickets
        const checkboxes = document.querySelectorAll('#reassingGasTicketForm input[type=checkbox]')
        // Liquidation 
        const radioButtons = document.querySelectorAll('#reassingGasTicketForm input[type=radio]')

        // Validating liquidation 
        radioButtons.forEach( node => {
            if( node.checked ){
                liquidationRadioChecked = true
            }
        })

        // Send data 
        if(liquidationRadioChecked){
            reassingGasTcktForm[0].submit()
        }else{
            alert('Debe seleccionar el/los bonos a transfererir')
        }
    })
})
;
$(function() {
	$("select#generated_ticket_route").on("change", function() {
		$.ajax({
			url: "path_routes/by_route",
			type: "GET",
			data: { route_id: $("select#generated_ticket_route").val() }
		});
	});
	$("select#generated_ticket_route_paths").on("change", function() {
		$.ajax({
			url: "tickets/by_route_path",
			type: "GET",
			data: { path_route_id: $("select#generated_ticket_route_paths").val(), charge_kind: ["bw_empty", "br_empty"] }
		});
	});
});

function validateFormGeneratedTicket(action_name) {
	var isSuccess;
	if (action_name == "new" || action_name == "create") {
		var generated_ticket_route = $("select#generated_ticket_route");
		if (generated_ticket_route.val()) {
			generated_ticket_route.css("borderColor", "#ccc");
			isSuccess = true;
		}else{
			generated_ticket_route.css("borderColor", "#FF3C3A");
			isSuccess = false;
		}
		var generated_ticket_route_paths = $("select#generated_ticket_route_paths");
		if (generated_ticket_route_paths.val()) {
			generated_ticket_route_paths.css("borderColor", "#ccc");
			isSuccess = true;
		}else{
			generated_ticket_route_paths.css("borderColor", "#FF3C3A");
			isSuccess = false;
		}
		var generated_ticket_gasoline_ticket_id = $("#generated_ticket_gasoline_ticket_id");
		if (generated_ticket_gasoline_ticket_id.val()) {
			generated_ticket_gasoline_ticket_id.css("borderColor", "#ccc");
			isSuccess = true;
		}else{
			generated_ticket_gasoline_ticket_id.css("borderColor", "#FF3C3A");
			isSuccess = false;
		}
	}else{
		var generated_ticket_price_per_gallon = $("#generated_ticket_price_per_gallon");
		if (generated_ticket_price_per_gallon.val()) {
			generated_ticket_price_per_gallon.css("borderColor", "#ccc");
			isSuccess = true;
		}else{
			generated_ticket_price_per_gallon.css("borderColor", "#FF3C3A");
			isSuccess = false;
		}
	}
	return isSuccess;
}
;
// gps_people.js
document.addEventListener('turbolinks:load', () => {
    try{
        // Get nodes 
        const fieldWrapper = document.getElementById('wrapperChangeField'),
            selectField = document.getElementById('gps_person_vehicle_id'),
            checkHandler = document.getElementById('handlerCheckChangeVehicle')

    //  --| Edit people_gps_people
        function activateField(){
            fieldWrapper.classList.remove('disabled')
            selectField.removeAttribute('disabled')
        }

        function inactivateField(){
            fieldWrapper.classList.add('disabled')
            selectField.setAttribute('disabled', true)
        }


        // If checkbox node exists 
        // --> Disable and enable select field when user edit record 
        if(checkHandler){
            // checbox event handler for active or in
            checkHandler.addEventListener('change', (e) => {
                const eventVal = e.target.checked
                // call function accordinhg check val 
                eventVal ? activateField() : inactivateField() 
            })
        }

    }catch(err){ console.error(`gps_people script: ${err}`) }
})
;
$( document ).on('turbolinks:load', function() {

    var dian_product_select = {}
    var options_dian = '';
    var x = 1;

    if(window.location.pathname.includes("/free_invoice") === true)
    {
        $.get('get_dian_products', function(data) {
            dian_product_select = data;

            $("#dian_product_fi").append('<option value="">Seleccione un producto</option>');
            $.each(dian_product_select, function(index, item) {
                $("#dian_product_fi").append('<option value=' + item.codigo_producto + '>' + item.codigo_producto+' '+ item.nombre_producto + '</option>');
            });
        });

        if ($('#invoice_value_available').val() !== 'true')
        {
            alert($('#invoice_value_available').val());
            window.location.replace("/invoices");
        }
    }

    if(window.location.pathname.includes("/to_bill/") === true)
    {
        if ($('#invoice_value_available').val() !== 'true')
        {
            alert($('#invoice_value_available').val());
            window.location.replace("/invoices/bigcustomers");
        }
    }

    if(window.location.pathname.includes("/to_bill_preinvoices/") === true)
    {
        if ($('#invoice_value_available').val() !== 'true')
        {
            alert($('#invoice_value_available').val());
            window.location.replace("/invoices/bigcustomers_preinvoices");
        }
    }

    var dataTable = $('#bigcustomers_invoice').DataTable( {
        "processing": true,
        "serverSide": true,
        "pageLength": 25,
        "cache": false,
        "language": { "url": "/assets/datatables/datatables_spanish.json" },
        "ajax": {
            'url': "/invoices/bigcustomers.json",
            'type': 'GET',
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+$('#bigcustomers_invoice').attr('data-toc') );
            },
            'data': function (d) {
                var companyFilterValue = $('#company-filter-select').val();
                if (companyFilterValue) {
                    d.company_filter = companyFilterValue;
                }
            }
        }
    });

    $('#filter-form').submit(function(e) {
        e.preventDefault();
        dataTable.ajax.reload();
    });


    $('#bigcustomers_preinvoices').DataTable( {
        "processing": true,
        "serverSide": true,
        "pageLength": 25,
        "cache": false,
        "language": { "url": "/assets/datatables/datatables_spanish.json" },
        "ajax": {
            'url': "/invoices/bigcustomers_preinvoices.json",
            'type': 'GET',
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+$('#bigcustomers_preinvoices').attr('data-toc') );
            }
        }
    } );



    $("#search_ladgenerator_fi").change(function(){
        $("#search_agent_fi").find('option').not(':first').remove();
        $("#search_address_fi").find('option').not(':first').remove();

        $.get('get_agents_loadgenerator?loadgenerator_id=' + $(this).val(), function(data) {
            console.log(data)
            $.each(data, function(index, item) {
                $('#search_agent_fi').append('<option value=' + item.agent.id + '>' + item.agent.name+' '+ item.agent.lastname + '</option>');
            });
        });
        $.get('get_addreses_loadgenerator?loadgenerator_id=' + $(this).val(), function(data) {
            console.log(data)
            $.each(data, function(index, item) {
                $('#search_address_fi').append('<option value=' + item.id + '>' + item.address_text +' '+ item.name + '</option>');
            });
        });
        // $.get('get_dian_products', function(data) {
        //     dian_product_select = data;
        //     $("#dian_product_fi").find('option').remove();
        //     $("#dian_product_fi").append('<option value="">Seleccione un producto</option>');
        //     $.each(dian_product_select, function(index, item) {
        //         $("#dian_product_fi").append('<option value=' + item.codigo_producto + '>' + item.codigo_producto+' '+ item.nombre_producto + '</option>');
        //     });
        // });
    });

    $("#btnCheckIn").click( function(){
        var json={};
        document.getElementById("btnCheckIn").disabled = true;
        $("#btnCheckIn").off();
        var loadgenerator = $("#search_ladgenerator_fi").val();
        var agent = $("#search_agent_fi").val();
        var address = $("#search_address_fi").val();
        var observations = $("#observations").val();
        var dian_code_product_fi = $("#dian_code_product_fi").val()
        json.loadgenerator_id = loadgenerator;
        json.agent_id = agent;
        json.invoice_address_id = address;
        json.dian_code_product = dian_code_product_fi;
        json.observations = observations;
        json.total_invoice = $("#total_invoice").html();
        json.concepts = []

        $("[name='field_concepto[]']").each(function() {
            var concept = {}
            console.log( this.value);
            var concept_description = this.value
            var tasa = $("#tasaTxt").val();
            var cantidad = $(this).parent("div").parent("div").find("[name='field_cantidad[]']").val();
            var valor_unitario = $(this).parent("div").parent("div").find("[name='field_valor_unitario[]']").val();
            var valor_total = $(this).parent("div").parent("div").find("[name='field_valor_total[]']").val();
            var dian_code_product = $(this).parent("div").parent("div").find("[name='field_dian_product[]']").val();
            concept.concept_description = concept_description;
            concept.tasa = tasa;
            concept.quantity = cantidad;
            concept.unit_value = valor_unitario;
            concept.total_value = valor_total;
            concept.dian_code_product = dian_code_product;
            json.concepts.push(concept);
        });
        var data_json = {}
        data_json.data_json = json ;
        JSON.stringify(data_json)
        console.log(JSON.stringify(data_json));
        // $.post("create_free_invoice",
        //     {
        //         data_json
        //     },
        //     function(data, status){
        //         alert("Data: " + data + "\nStatus: " + status);
        // });

        $.ajax({
            url: 'create_free_invoice.json',
            type: "POST",
            async: false,
            data: json,
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+ token );
            },
            success: function(result) {
                if (result.success == false) {
                    alert(result.message);
                }
                else {
                    // alert('ok');
                }
            }
        });

    });

    function calc_total_invoice()
    {
        var total = 0;
        $("[name='field_valor_total[]']").each(function() {
            console.log( this.value);
            total += parseInt(this.value);
            $("#total_invoice").html(total)
        });
    }

    function calcular_total_concepto(){

        $("[name='field_valor_total[]']").each(function() {
            var valor_total = $(this).parent("div").parent("div").find("[name='field_valor_total[]']");
            var cantidad = $(this).parent("div").parent("div").find("[name='field_cantidad[]']").val();
            var valor_unitario = $(this).parent("div").parent("div").find("[name='field_valor_unitario[]']").val();
            valor_total.val( parseInt(valor_unitario,10)*parseInt(cantidad,10));

        });
        calc_total_invoice();
    }

    $('.field_wrapper').keyup(function(){
        calcular_total_concepto();
    })

    $("#btnAddConcept").click(function(){
        x=0;
        var maxField = 10;
        var options = '<option value="">Seleccione un producto</option>'
        $.each(dian_product_select, function(index, item) {
            options += '<option value=' + item.codigo_producto + '>' + item.codigo_producto+' '+ item.nombre_producto + '</option>';
        });
        var select_dian = $("#dian_product_fi").html();
        console.log('============');

        console.log($("#dian_product_fi"));
        console.log('============');


        var wrapper = $('.field_wrapper');
        var fieldHTML = '<div class="row">\n' +
        '                <div class="col-md-2">' +
        '                    <select name="field_dian_product[]" id = "dian_product_fi" class="select2">'+select_dian+'</select>' +
        '                </div>' +
        '              <div class="col-md-3">\n' +
        '                <input type="text" name="field_concepto[]" value=""/>\n' +
        '              </div>\n' +
        '              <div class="col-md-2">\n' +
        '                <input type="number" name="field_cantidad[]" value=""/>\n' +
        '              </div>\n' +
        '              <div class="col-md-2">\n' +
        '                <input type="number" name="field_valor_unitario[]" value=""/>\n' +
        '              </div>\n' +
        '              <div class="col-md-2">\n' +
        '                <input type="number" name="field_valor_total[]" disabled value=""/>\n' +
        '              </div>\n' +
        '                <div class="col-md-1 " style="top: 10px;"> <a href="javascript:void(0);" class="remove_button"><i class="fa fa-trash fa-fw"></i></a> </div>\n' +
            '            </div>'; //New input field html



            if(x < maxField){
                x++;
                $(wrapper).append(fieldHTML);
            }



            $(wrapper).on('click', '.remove_button', function(e){
                e.preventDefault();
                $(this).parent('div').parent('div').remove();
                x--;
                calcular_total_concepto();
            });

            $("[name='field_dian_product[]']").each(function() {
                $(this).addClass("select2");
            });
        });
});


function check_attachments()
{
    var json = {}
    json.requests = []
    $("#two tr").each(function(rowIndex) {
        $(this).find("td").each(function(cellIndex) {
            if(cellIndex == 1)
            {
                var href = $(this).find('a').attr('href');
                var request = href.substr(10,href.length-1);
                json.requests.push(request)

            }

        });
    });

    if(json.requests.length>0)
    {
        $.ajax({
            url: '/invoices/check_attachments.json',
            type: "POST",
            async: false,
            data: json,
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization",'Bearer '+ token);
            },
            success: function(result) {
                if (result.success == false) {
                    alert(result.message);
                }
                else {
                    
                    if(result.count==0)
                    {
                        $('#msgAttachments').html("<b>No hay ningún archivo adjunto en los servicios seleccionados</b>" +
                            "<br>Si continua con la facturación el cliente no recibirá soporte documental");
                        $('#msgAttachments').show();
                    }
                    else
                    {
                        $('#msgAttachments').fadeOut("fast");
                    }

                }
            }
        });
    }else
    {
        $('#msgAttachments').fadeOut("fast");
    }


}
;
(function() {


}).call(this);
class GPSKalmanFilter {
  constructor (decay = 10) {
    this.decay = decay
    this.variance = -1
    this.minAccuracy = 1
  }

  process (lat, lng, accuracy, timestampInMs) {
    if (accuracy < this.minAccuracy) accuracy = this.minAccuracy
    if (this.variance < 0) {
      this.timestampInMs = timestampInMs
      this.lat = lat
      this.lng = lng
      this.variance = accuracy * accuracy
    } else {
      const timeIncMs = timestampInMs - this.timestampInMs

      if (timeIncMs > 0) {
        this.variance += (timeIncMs * this.decay * this.decay) / 1000
        this.timestampInMs = timestampInMs
      }

      const _k = this.variance / (this.variance + (accuracy * accuracy))
      this.lat += _k * (lat - this.lat)
      this.lng += _k * (lng - this.lng)

      this.variance = (1 - _k) * this.variance
    }

    return {lng:this.lng, lat:this.lat}
  }
}

// const refineLocation = (location, lastLocation, measurementNoise) => {
//   const accuracy = Math.max(location.accuracy, 1);
//   const result = { ...location, ...lastLocation };
//   if (!lastLocation) {
//     result.variance = accuracy * accuracy;
//   } else {
//     const timestampInc = location.timestamp.getTime() - lastLocation.timestamp.getTime();
//     if (timestampInc > 0) {
//       // We can tune the velocity and particularly the coefficient at the end
//       const velocity = _calculateGreatCircleDistance(location, lastLocation) / timestampInc * measurementNoise;
//       result.variance += timestampInc * velocity * velocity / 1000;
//     }
//     const k = result.variance / (result.variance + accuracy * accuracy);
//     result.latitude += k * (location.latitude - lastLocation.latitude);
//     result.longitude += k * (location.longitude - lastLocation.longitude);
//     result.variance = (1 - k) * result.variance;
//   }
//   return {
//     ...location,
//     ..._.pick(result, ['latitude', 'longitude', 'variance']),
//   };
// };
// summary liquidation partial on views/comply/lists.
// ! Functionality for skip tax 
$( document ).on('turbolinks:load', () => {
    // Filed for save taxes 
    const hiddenField = $('#skipTaxes')[0]
    const totalCount = $('#liquidationSummary_TOTAL')[0]

    // For skip tax and set on hidden field 
    function setSkipTax(event){
        try{
            // Checkbox status 
            const checkStatus = event.target.checked
            // Tax for skip 
            const tax = event.target.getAttribute('data-tax')
            const taxValue = event.target.getAttribute('data-value') 
            // set or remove tax
            if(checkStatus){
                hiddenField.value = tax
                // Add value to total - Only visual feature, really value set on backend
                addTaxSkipped(taxValue, tax) 
            }else{
                hiddenField.value = ""
                remoteTaxSkippedFromTotal(tax)
            }
        }catch(err){
            console.error(`Error al setear omisión de impuesto - tax | ${err}`)
        }
    }

    // Add value to total - Only visual feature, really value set on backend
    function addTaxSkipped(value, taxName){
        const taxVal = document.createElement('div')
        taxVal.innerText = `+ ${parseFloat(value).toFixed(2)}`
        taxVal.id= `dataTax${taxName}`
        taxVal.classList.add('taxSkipped')
        totalCount.appendChild(taxVal)
    }

    // Remove value to total - Only visual feature, really value set on backend
    function remoteTaxSkippedFromTotal(taxName){
        totalCount.children.length && totalCount.children[0].remove()
    }

    // On change form 
    $("#formLiquidationSkipTaxDocs").on("change", (event) => setSkipTax(event))
})
;
$(document).on('turbolinks:load', function() {

	if ($("select#loadgenerator_department_id").val() == "") {
		$("select#loadgenerator_city_id option").remove();
		var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
		$(row).appendTo("select#loadgenerator_city_id");
	}
	$("select#loadgenerator_department_id").change(function() {
		var id_value_string = $(this).val();
		if (id_value_string == "") {
			$("select#loadgenerator_city_id option").remove();
			var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
			$(row).appendTo("select#loadgenerator_city_id");
		} else {      //Send the request and update loadgenerator_city_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/system/tables/cities/getcitiesbydepartment/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					alert("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { 	       // Clear all options from loadgenerator_city_id select
					if (data.success){
						$("select#loadgenerator_city_id option").remove(); 	 //put in a empty default line
						var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
						$(row).appendTo("select#loadgenerator_city_id");   // Fill loadgenerator_city_id select
						$.each(data.data, function(i, j) {
						row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
						$(row).appendTo("select#loadgenerator_city_id");
					});
				}

			}
		});
		}
	});

	//Ocultar/Mostrar  Input Digito de verificación
	val = $("#loadgenerator_doctype_id").val();
	if (val == "1"){
		$("#rz").addClass( 'display-none' );
		$("#sociedad").addClass( 'display-none' );
		$("#nameload").removeClass('display-none');
		$("#lastname2").removeClass('display-none');
		$("#lastname").removeClass('display-none');
		$("#digit").addClass( 'display-none' );
		$("#docnit").text("Nro de documento");
	} else if (val == "2") {
		$("#rz").removeClass( 'display-none' );
		$("#sociedad").removeClass( 'display-none' );
		$("#nameload").addClass('display-none');
		$("#lastname2").addClass('display-none');
		$("#lastname").addClass('display-none');
		$("#digit").removeClass('display-none');
		$("#docnit").text("NIT");
	}

	$("#loadgenerator_doctype_id").change(function(){
		val = $("#loadgenerator_doctype_id").val();
		if (val == "1"){
			$("#rz").addClass( 'display-none' );
			$("#sociedad").addClass( 'display-none' );
			$("#nameload").removeClass('display-none');
			$("#lastname").removeClass('display-none');
			$("#lastname2").removeClass('display-none');
			$("#digit").addClass( 'display-none' );
			$("#docnit").text("Nro de documento");
		} else if (val == "2") {
			$("#rz").removeClass( 'display-none' );
			$("#sociedad").removeClass( 'display-none' );
			$("#nameload").addClass('display-none');
			$("#lastname").addClass('display-none');
			$("#lastname2").addClass('display-none');
			$("#digit").removeClass('display-none');
			$("#docnit").text("NIT");
		}
	})

});
(function() {


}).call(this);
document.addEventListener('turbolinks:load', () => {
    function sendPetition(alert_id){
        $.ajax({
            method: 'post',
            url: `/api/v1/gps/remote/resolve_turn_off_or_on`,
            data: { 
                alert_id: alert_id 
            },
            success: (res) => {
                if(res.status == 'ok'){
                    Swal.fire(
                        'Genial!',
                        'El comando se envio correctamente.',
                        'success'
                    )
                    setTimeout(() => {
                        window.location.href = "/notifications/alerts"
                    },1000)


                }else if(res.status == 'unprocessable_entity'){
                    Swal.fire(
                        'Ooops!',
                        res.message,
                        'warning'
                    )
                }
            },
            error: (res) => {
                console.log(res)
                Swal.fire(
                    'Ooops!',
                    'Error de servidor.',
                    'error'
                )
            }
        })
    }

    // --
    const panel = document.getElementById('alertGpsPanel')

    // Only when node exist 
    if(panel){
        const turnOffBtn = panel.querySelector('#turnOffBtnActivator')

        // event listener 
        turnOffBtn.addEventListener('click', event => {
            event.preventDefault();
            // -- 
            Swal.fire({
                title: '¿Estás seguro?',
                text: "Estás apunto de apagar el vehiculo.",
                icon: 'warning',
                showCancelButton: true,
                confirmButtonColor: '#3085d6',
                cancelButtonColor: '#d33',
                confirmButtonText: '¡Si, apagar!',
                cancelButtonText: 'Cancelar'
            }).then((result) => {
                if (result.isConfirmed) {
                    const alert_id = event.target.getAttribute('data-alert-id');
                    // --
                    sendPetition(alert_id)
                }
            })
        })
    }
})
;
$('#notifications_wall_message_messagetype_id').on('change', function() {
    if ($(this).children("option:selected").val() == "other") {
    	$(".other-messagetype").removeClass("hide");
    }else{
    	$(".other-messagetype").addClass("hide");
    }
});
(function() {
  $(document).ready(function() {
    return $('#commit').on("click", function() {
      if ($("#password").val() !== $("#password_confirmation").val()) {
        alert("Verfique que la clave y la confirmación sean la misma...");
        return false;
      }
      return true;
    });
  });

}).call(this);
$(document).on('turbolinks:load', function() {

  $('#addCapacityParking').on('click', function(){
    AddCapacityParking();
  })
  function AddCapacityParking(){
    var $capacity_parking = $("#capacity_parking_div").clone();
    $("#another_capacities").append($capacity_parking)
  }

  $('#addPhysicalsConditionsParking').on('click', function(){
    AddPhysicalsConditionsParking();
  });

  function AddPhysicalsConditionsParking(){
    var $physical_conditions = $("#physical_conditions_div").clone();
    $("#another_physicals_conditions").append($physical_conditions)
  }

  $("#div_capacity_parking").on('click', '.delete_capacity_parking',  function(){
    $(this).parent().parent().parent().remove()
    return false
  })

  $("#div_physicals_condition").on('click', '.delete_physicals_conditions_parking',  function(){
    $(this).parent().parent().parent().remove()
    return false
  })


  $(document).on('change', 'select#parking_lot_department_id', function() {
    var id_value_string = $(this).val();
    var select = $('select.city');
    if (id_value_string == "") {
     $(select).children('option').remove();
     var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
     $(row).appendTo(select);
		} else {      //Send the request and update person_city_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/system/tables/cities/getcitiesbydepartment/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					alert("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { // Clear all options from person_city_id select
					if (data.success){
						$(select).children('option').remove();  //put in a empty default line
						var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
						$(row).appendTo(select);   // Fill person_city_id select
						$.each(data.data, function(i, j) {
							row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
							$(row).appendTo(select);
						});
					}
				}
			});
		}
	});

  if ($('#parking_lot_address_text').length) {
		// para habilitar el campo parking_lot_address_text
		$("#parking_lot_city_id").on('change', function() {
			if ( $("#parking_lot_city_id").val() != "") $('#parking_lot_address_text').removeAttr("disabled")
		});
		// Rellena el campo ciudad
		if ($("#parking_lot_department_id").val() != "") {
			var row = "<option value=\"" + cdad_id + "\" selected >" + cdad + "</option>";
			$(row).appendTo("#parking_lot_city_id");
		}
	}

});


function initializeParkingLot() {
	function geocodeAddress(geocoder, resultsMap) {
		var address = $('#parking_lot_address_text').val()+", "+$('#parking_lot_city_id option:selected').text()+", "+$('#parking_lot_department_id option:selected').text();
		geocoder.geocode({'address': address}, function(results, status) {
      if (status === 'OK') {
       resultsMap.setCenter(results[0].geometry.location);
       marker = new google.maps.Marker({
         map: resultsMap,
         position: results[0].geometry.location,
         draggable: true
       });

       document.getElementById("parking_lot_lat").value = marker.getPosition().lat().toString();
       document.getElementById("parking_lot_lng").value = marker.getPosition().lng().toString();

       google.maps.event.addListener(marker,'dragend',function(event) {
        document.getElementById("parking_lot_lat").value = this.getPosition().lat().toString();
        document.getElementById("parking_lot_lng").value = this.getPosition().lng().toString();
      });
     } else {
       alert('Geocode was not successful for the following reason: ' + status);
     }
   });
	}
	var geocoder = new google.maps.Geocoder();
  if (document.getElementById('parking_lot_address_text') != null ){
    document.getElementById('parking_lot_address_text').addEventListener('change', function() {
      geocodeAddress(geocoder, map);
    });
  }

}

// https://developers.google.com/maps/documentation/javascript/examples/drawing-tools
var map, drawingManager, currentPolygon;

function initMapParkingLot() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: parseFloat($("#parking_lot_lat").val()) || 4.678457, lng: parseFloat($("#parking_lot_lng").val()) || -74.059232},
    zoom: 18
  });

  drawingManager = new google.maps.drawing.DrawingManager({
    drawingMode: google.maps.drawing.OverlayType.POLYGON,
    drawingControl: false,
    polygonOptions: {
    	fillColor: '#FF0000',
    	fillOpacity: 0.4,
    	strokeColor: "#000000",
    	strokeWeight: 3,
    	clickable: false,
    	editable: true
    }
  });
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setMap(map);
  }

  if ( $("#parking_lot_lat").val() && parseInt( $("#parking_lot_lat").val()) != 0  && $("#parking_lot_polypoints").val()) {
  	drawingManager.setDrawingMode(null);
    $("#reDraw").removeClass("hide");

    var polygonCoords = [];
    JSON.parse($("#parking_lot_polypoints").val()).forEach(function(polypoint) {
      polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
    })
    var polygon = new google.maps.Polygon({
     paths: polygonCoords,
     strokeColor: '#000000',
     fillColor: '#FF0000',
     fillOpacity: 0.4,
     editable: true
   });
    currentPolygon = polygon;
    google.maps.event.addListener(polygon.getPath(), 'set_at', function() {
     $("#parking_lot_polypoints").val(setPolypoints(polygon.getPath()));
   });
    google.maps.event.addListener(polygon.getPath(), 'insert_at', function() {
     $("#parking_lot_polypoints").val(setPolypoints(polygon.getPath()));
   });
    polygon.setMap(map);

    var center = new google.maps.LatLng($("#parking_lot_lat").val(), $("#parking_lot_lng").val());

  }
  google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
  	if (event.type == 'polygon') {
  		$("#reDraw").removeClass("hide");
  		$("#parking_lot_polypoints").val(setPolypoints(event.overlay.getPath()));
  		currentPolygon = event.overlay;
      google.maps.event.addListener(event.overlay.getPath(), 'set_at', function() {
        $("#parking_lot_polypoints").val(setPolypoints(event.overlay.getPath()));
      });
      google.maps.event.addListener(event.overlay.getPath(), 'insert_at', function() {
        $("#parking_lot_polypoints").val(setPolypoints(event.overlay.getPath()));
      });
      drawingManager.setDrawingMode(null);
    }
  });
  google.maps.event.addDomListener(window, 'load', initializeParkingLot);
}

function initMapParkingLotShow() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: parseFloat($("#parking_lot_lat").val()) || 4.678457, lng: parseFloat($("#parking_lot_lng").val()) || -74.059232},
    zoom: 18
  });
  if ( $("#parking_lot_lat").val() && $("#parking_lot_polypoints").val()) {
    var polygonCoords = [];
    JSON.parse($("#parking_lot_polypoints").val()).forEach(function(polypoint) {
      polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
    })
    var polygon = new google.maps.Polygon({
      paths: polygonCoords,
      strokeColor: '#000000',
      fillColor: '#FF0000',
      fillOpacity: 0.4,
      editable: false
    });
    currentPolygon = polygon;
    google.maps.event.addListener(polygon.getPath(), 'set_at', function() {
      $("#parking_lot_polypoints").val(setPolypoints(polygon.getPath()));
    });
    google.maps.event.addListener(polygon.getPath(), 'insert_at', function() {
      $("#parking_lot_polypoints").val(setPolypoints(polygon.getPath()));
    });
    polygon.setMap(map);
  }
}

function removePolyline() {
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
  }
  if (currentPolygon) {
    currentPolygon.setMap(null);
    currentPolygon = null;
  }
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setMap(map);
  }
  $("#reDraw").addClass("hide");
}

function getBoundsForPoly(poly) {
	var bounds = new google.maps.LatLngBounds;
	poly.getPath().forEach(function(latLng) {
		bounds.extend(latLng);
	});
	return bounds;
}

function setPolypoints(path){
	var polypoints = [];
	path.forEach(function(element) {
    polypoints.push('{"lat": "' + element.lat() + '", "lng": "' + element.lng() + '"}');
  })
  return '['+polypoints.toString()+']';
}

function setPolypointsGeo(path){
  var polypoints = [];
  $.each(path, function(k, element) {
    polypoints.push('{"lat": "' + element.lat + '", "lng": "' + element.lng + '"}');
  });
  return '['+polypoints.toString()+']';
}

function validPolyline(){
  // if ($("#address_status").val() == true){
  //   if (!currentPolygon || !$("#parking_lot_polypoints").val() || !document.getElementById("parking_lot_lat").value || !document.getElementById("parking_lot_lng").value){
  //     alert('Debe agregar una polígono en el mapa para continuar');
  //     return false;
  //   }
  // }
}



;
$(document).on('turbolinks:load', function() {

	if ($("select#person_department_id").val() == "") {
		$("select#person_city_id option").remove();
		var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
		$(row).appendTo("select#person_city_id");
	}

	$("select#person_department_id").change(function() {
		var id_value_string = $(this).val();
		if (id_value_string == "") {
			$("select#person_city_id option").remove();
			var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
			$(row).appendTo("select#person_city_id");
		} else {      //Send the request and update person_city_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/system/tables/cities/getcitiesbydepartment/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					alert("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { 	       // Clear all options from person_city_id select
					if (data.success){
						$("select#person_city_id option").remove(); 	 //put in a empty default line
						var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
						$(row).appendTo("select#person_city_id");   // Fill person_city_id select
						$.each(data.data, function(i, j) {
							row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
							$(row).appendTo("select#person_city_id");
						});
					}
				}
			});
		}
	});

	if ($("select#person_docdriverdepartment_id").val() == "") {
		$("select#person_docdrivercity_id option").remove();
		var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
		$(row).appendTo("select#person_docdrivercity_id");
	}
	$("select#person_docdriverdepartment_id").change(function() {
		var id_value_string = $(this).val();
		if (id_value_string == "") {
			$("select#person_docdrivercity_id option").remove();
			var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
			$(row).appendTo("select#person_docdrivercity_id");
		} else {      //Send the request and update person_docdrivercity_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/system/tables/cities/getcitiesbydepartment/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					alert("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { 	       // Clear all options from person_docdrivercity_id select
					if (data.success){
						$("select#person_docdrivercity_id option").remove(); 	 //put in a empty default line
						var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
						$(row).appendTo("select#person_docdrivercity_id");   // Fill person_docdrivercity_id select
						$.each(data.data, function(i, j) {
							row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
							$(row).appendTo("select#person_docdrivercity_id");
						});
					}
				}
			});
		}
	});
	//Ocultar/Mostrar  Input Digito de verificación
	val = $("#person_doctype_id").val();
	if (val == "1"){
		$("#digit").addClass( 'display-none' );
		$("#docnit").text("Nro de documento");
		$("#document").removeClass('display-none');
	} else if (val == "2") {
		$("#digit").removeClass('display-none');
		$("#docnit").text("NIT");
		$("#document").addClass( 'display-none' );
	}

	$("#person_doctype_id").change(function(){
		val = $("#person_doctype_id").val();
		if (val == "1"){
			$("#digit").addClass( 'display-none' );
			$("#docnit").text("Nro de documento");
			$("#document").removeClass('display-none');
		} else if (val == "2") {
			$("#digit").removeClass('display-none');
			$("#docnit").text("NIT");
			$("#document").addClass( 'display-none' );
		}
	})

	//Ocultar/Mostrar Información de licencia conductor
	var val =$("#person_is_driver").is(':checked');
	if (val){
		$("#licencia").removeClass('display-none');
		$("#eps").removeClass('display-none');
		$("#arl").removeClass('display-none');
		$("#uploadlicense").removeClass('display-none');
	} else{
		$("#licencia").addClass( 'display-none' );
		$("#arl").addClass( 'display-none' );
		$("#eps").addClass( 'display-none' );
		$("#uploadlicense").addClass( 'display-none' );
	}
	$("#person_is_driver").change(function(){
		var val =$("#person_is_driver").is(':checked');
		if (val){
			$("#licencia").removeClass('display-none');
			$("#eps").removeClass('display-none');
			$("#arl").removeClass('display-none');
			$("#uploadlicense").removeClass('display-none');
		} else{
			$("#licencia").addClass( 'display-none' );
			$("#arl").addClass( 'display-none' );
			$("#eps").addClass( 'display-none' );
			$("#uploadlicense").addClass( 'display-none' );
		}
	})

	$('#people').DataTable( {
        "processing": true,
        "serverSide": true,
        "pageLength": 25,
        "cache": false,
        "language": { "url": "/assets/datatables/datatables_spanish.json" },
        "ajax": {
            'url': "/people/validate_pending_drivers"+( $('#people').attr('pend')==1 ? '?pend=1' : '')+".json",
            'type': 'GET',
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+$('#people').attr('data-toc') );
            }
        }
	});
	if ( $("#people_own").length ) {
		$('#people_own').DataTable( {
	        "processing": true,
	        "serverSide": true,
	        "pageLength": 25,
	        "cache": false,
	        "language": { "url": "/assets/datatables/datatables_spanish.json" },
	        "ajax": {
	            'url': "/people/drivers_own"+( $('#people').attr('pend')==1 ? '?pend=1' : '')+".json",
	            'type': 'GET',
	            'beforeSend': function (request) {
	                request.setRequestHeader("Authorization", 'Bearer '+$('#people_own').attr('data-toc') );
	            }
	        }
		});
	}
	
});


function change_status_third(btn, personId, car = "") {
	var proceed = false;
	if (car=='') {
		var thirdstate = $("#thirdstate-person-status option:selected");
		var thirdstateId = thirdstate.val();
		var notify_to_approve = $('#notify_to_approve');
		var comment = $("#comment_to_approve");
		var notified_push = false;
		var pass_valid = $("#pass_valid");
		var evidence = $("#evidence")
	} else {
		var thirdstate = $("#thirdstate-car-status option:selected");
		var thirdstateId = thirdstate.val();
		var notify_to_approve = $('#car_notify');
		var comment = $("#car_comment");
		var notified_push = false;
		var pass_valid = $("#pass_valid");
		var evidence = $("#car_evidence")
	}

	if (thirdstate.text().toLowerCase() == 'aprobado') {
		if (pass_valid.val() == 'true') {
			if (comment.val() != '') {
				proceed = true
			}else{
				comment.css("borderColor", "#FF3C3A");
			}
		}else{
			alert('No se puede aprobar por falta de documentación.');
		}
	}else{
		if (comment.val() != '') {
			proceed = true
		}else{
			comment.css("borderColor", "#FF3C3A");
		}
	}
	if (proceed) {
		comment.css("borderColor", "#FF3C3A");
		if (notify_to_approve.is(":checked")) notified_push = true
		comment.css("borderColor", "#ccc");

		var formData = new FormData();

		if(evidence[0].files && evidence[0].files.length == 1){
		 var file = evidence[0].files[0]
		 formData.append("evidence2", file , file.name);
		}
		formData.append("evidence", file);
		formData.append("person_id", personId)
		formData.append("comment", comment.val())
		formData.append("notified_push", notified_push)
		formData.append("car", car)
		// Http Request  
		$.ajax({
			url: '../../../thirdstates/'+thirdstateId+'/people/change_status',
			type: "POST",
			data:  formData,
   			contentType: false,
         	cache: false,
   			processData:false,
			success: function(result) {
				console.log(result)
				if (result.success == true) {
					btn.disabled = true;
					location.reload(true);
				}else{
					console.log(result)
					if (result.message != undefined) {
						alert(result.message);
					}
					location.reload(true);
				}
			}
		});
	}
}
;
document.addEventListener("turbolinks:load",() => {
    if(document.getElementById('places_show_view')){
        // Set image name 
        const setImageName = (event) => {
            const target = event.target
            const span_ref = target.dataset.field
    
            // If references exist 
            if(span_ref){
                const span = document.querySelector(`span[data-ref=${span_ref}]`)
                const filename = event.target.value.split('\\').pop()
                // --
                if(filename && span){
                    // Set name 
                    span.textContent = filename
                    // Set upload class for change its style 
                    span.classList.add('upload')
                }
            }
        }
    
        // Get fields 
        const fieldImages = document.querySelectorAll('.wrapper_image_place .file_field_img')
    
        // AddListener for each node
        fieldImages.forEach(node => node.addEventListener('change', event => setImageName(event)))
    
        const L = window.L;
    // -- Maps functions and loading 
    
        function load_place_map(){
            try{
                let marker;
                const mapOnView = document.getElementById('place_map')
                if(mapOnView){
                    const map = L.map(mapOnView).setView([mapOnView.dataset.lat, mapOnView.dataset.long], 17)
                    marker = L.marker([mapOnView.dataset.lat, mapOnView.dataset.long]).addTo(map).bindPopup( mapOnView.dataset.name ).openPopup();
                    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, id: 'mapbox.streets' }).addTo(map);
                }
    
            }catch(err){
                console.error('Error loading place map!', err)
            }
        }
        
        function load_initial_location_plot(){
            try{
                let marker;
                const mapOnView = document.getElementById('plot_start_map')
                if(mapOnView){
                    const map = L.map(mapOnView).setView([mapOnView.dataset.lat, mapOnView.dataset.long], 17)
                    marker = L.marker([mapOnView.dataset.lat, mapOnView.dataset.long]).addTo(map).bindPopup('Inicio de trama').openPopup();
                    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, id: 'mapbox.streets' }).addTo(map);
                }
            }catch(err){
                console.error('Error loading initial plot location!', err)
            }
        }
    
        function load_final_location_plot(){
            try{
                let marker;
                const mapOnView = document.getElementById('plot_destination_map')
                if(mapOnView){
                    const map = L.map(mapOnView).setView([mapOnView.dataset.lat, mapOnView.dataset.long], 17)
                    marker = L.marker([mapOnView.dataset.lat, mapOnView.dataset.long]).addTo(map).bindPopup('Final de trama').openPopup();
                    L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19, id: 'mapbox.streets' }).addTo(map);
                }
            }catch(err){
                console.error('Error loading final plot location!', err)
            }
        }
    
        // Calling function maps for load 
        load_initial_location_plot()
        load_final_location_plot()
        load_place_map()
    }
})
;
(function() {


}).call(this);
$(document).on('turbolinks:load', function() {
	$("#preliquidated_detail_cost_change").on('change',function(){ 
		var element = $(this).find('option:selected'); 
		var cost = element.attr("data-cost"); 
		var provider = element.attr("data-provider"); 
		$('#preliquidated_client_config_cost_form').val(cost); 
		$('#preliquidated_client_config_provider_form').val(provider); 
	});

	optionText = 'Agregar gasto'; 
	optionValue = 'otro'; 
	$('#preliquidated_detail_cost_change').append($('<option>').val(optionValue).text(optionText));

	$("#preliquidated_detail_cost_change").on('change',function(){ 
		var option = $(this).val()
		if (option == 'otro'){
			var txt;
			var r = confirm("¿Desea crear un nuevo gasto?");
			if (r == true) {
				$(location).attr('href', '/preliquidated/details/new/'+category_id+'?redirect_to='+category_id)
			}
		}
	});
});
(function() {


}).call(this);
$(document).on('turbolinks:load', function() {
	optionText = 'Crear otro proveedor'; 
    optionValue = 'otro'; 
	// $('#provider_detail_id').append(`<option value="${optionValue}"> ${optionText} </option>`);
	$('#provider_detail_id, #provider_toll_id').append($('<option>').val(optionValue).text(optionText));

	$("#provider_detail_id, #provider_toll_id").on('change',function(){ 
		var option = $(this).val()
		if (option == 'otro'){
			var txt;
			var r = confirm("¿Desea crear un nuevo proveedor?");
			if (r == true) {
				$(location).attr('href', '/preliquidated/providers/new')
			}
		}
	});

});

function initialize() {
	function geocodeAddresspreliquidated(geocoder) {
		var address = $('#preliquidated_provider_address_text').val()+", "+$('#person_city_id option:selected').text()+", "+$('#person_department_id option:selected').text();
		geocoder.geocode({'address': address}, function(results, status) {
			if (status === 'OK') {
				position = results[0].geometry.location
				document.getElementById("preliquidated_provider_lat").value = position.lat().toString();
				document.getElementById("preliquidated_provider_lng").value = position.lng().toString();

			} else {
				console.log('Geocode was not successful for the following reason: ' + status);
			}
		});
	}
	var geocoder = new google.maps.Geocoder();
	if (document.getElementById('preliquidated_provider_address_text') != null ){
		document.getElementById('preliquidated_provider_address_text').addEventListener('change', function() {
			geocodeAddresspreliquidated(geocoder);
		});
	}
}


function initMapPreliquidated() {
	google.maps.event.addDomListener(window, 'load', initialize);
}
;
var map, marker;
$(document).on('turbolinks:load', function() {
	$("#provider_toll_id").on('change',function(){ 
		var element = $(this).find('option:selected'); 
		var nit = element.attr("data-nit"); 
		$('#nit_toll_provider').val(nit); 
	}); 
	$("#pathroute_toll_check").on('change',function(){ 
		var element = $(this).find('option:selected'); 
		var lat = element.attr("lat"); 
		var lng = element.attr("lng"); 
		map.setCenter({lat: parseFloat(lat), lng: parseFloat(lng)});
		marker.setPosition({lat: parseFloat(lat), lng: parseFloat(lng)});
	});
	$(".change_sort").on('change',function(){ 
		var id = $(this).attr("pathroute_toll_id"); 
		var sort_id = $(this).val(); 
		$.ajax({
			url: '/system/tables/pathroutes/update_sort/'+id+'.json',
			type: "PUT",
			async: false,
			data: {sort: sort_id },
			'beforeSend': function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+token );
			},
			success: function(result) {
				if (result.success == false) {
					alert(result.message);
				}else {
				}
			}
		});
	});

	$("#send_toll_pathroute").on('click',function(){ 
		pathrouteid = $('#toll_pathroute_id').val();
		tollid = $('#pathroute_toll_check').val();
		sort_id = $('#sort').val();
		if( tollid ) {
			$.ajax({
				url: '/system/tables/pathroutes/create_toll_pathroute.json',
				type: "POST",
				async: false,
				data: {pathroute_id: pathrouteid, toll_id: tollid, sort: sort_id },
				'beforeSend': function (request) {
					request.setRequestHeader("Authorization", 'Bearer '+token );
				},
				success: function(result) {
					if (result.success == false) {
						alert(result.message);
					}else {
						toll = result.data.toll
						$('#sort').val(result.sort)
						tr = '<tr>'
						tr = tr + '<td>'+toll.name+'</td>'
						tr = tr + '<td><input type="number" name="sort'+result.data.id+'" id="sort'+result.data.id+'" pathroute_toll_id="'+result.data.id+'" class="change_sort" value="'+result.data.sort+'"></td>'
						tr = tr + '<td>'
						tr = tr + '<a href="/preliquidated/tolls/'+toll.id+'"><i class="fa fa-eye fa-fw"></i></a> | <a href="/preliquidated/tolls/'+toll.id+'/edit"><i class="fa fa-edit fa-fw"></i></a> | '
						tr = tr + '<a data-confirm="¿Estás seguro de continuar?" rel="nofollow" data-method="delete" href="/system/tables/pathroutes/destroy_toll_pathroute/'+result.data.id+'"><i class="fa fa-trash fa-fw"></i></a>'        
						tr = tr + '</td>'
						tr = tr + '</tr>'
						$('#table_tolls > tbody:last-child').append(tr);


					}
				}
			});
		} else {
			alert('Seleccione el peaje');
		}
	});	

	$("#send_toll_pathroutesection").on('click',function(){ 
		pathrouteid = $('#toll_pathroute_id').val();
		pathroutesectionid = $('#toll_pathroutesection_id').val();
		tollid = $('#pathroutesection_toll_check').val();
		sort_id = $('#sort').val();
		if( tollid ) {
			$.ajax({
				url: '/system/tables/pathroutesections/create_toll_pathroutesection.json',
				type: "POST",
				async: false,
				data: {pathroute_id: pathrouteid, pathroutesection_id: pathroutesectionid, toll_id: tollid, sort: sort_id },
				'beforeSend': function (request) {
					request.setRequestHeader("Authorization", 'Bearer '+token );
				},
				success: function(result) {
					if (result.success == false) {
						alert(result.message);
					}else {
						toll = result.data.toll
						$('#sort').val(result.sort)
						tr = '<tr>'
						tr = tr + '<td>'+toll.name+'</td>'
						tr = tr + '<td><input type="number" name="sort'+result.data.id+'" id="sort'+result.data.id+'" pathroute_toll_id="'+result.data.id+'" class="change_sort" value="'+result.data.sort+'"></td>'
						tr = tr + '<td>'
						tr = tr + '<a href="/preliquidated/tolls/'+toll.id+'"><i class="fa fa-eye fa-fw"></i></a> | <a href="/preliquidated/tolls/'+toll.id+'/edit"><i class="fa fa-edit fa-fw"></i></a> | '
						tr = tr + '<a data-confirm="¿Estás seguro de continuar?" rel="nofollow" data-method="delete" href="/system/tables/pathroutesections/destroy_toll_pathroutesection/'+result.data.id+'"><i class="fa fa-trash fa-fw"></i></a>'        
						tr = tr + '</td>'
						tr = tr + '</tr>'
						$('#table_tolls_pathroutesection > tbody:last-child').append(tr);
					}
				}
			});
		} else {
			alert('Seleccione el peaje');
		}
	});	
});

function initMapToll() {
	map = new google.maps.Map(document.getElementById('map_toll'), {
		center: {lat: parseFloat($("#preliquidated_toll_lat").val()) || 4.678457, lng: parseFloat($("#preliquidated_toll_lng").val()) || -74.059232},
		zoom: 18
	});

	marker = new google.maps.Marker({
		map: map,
		position: {lat: parseFloat($("#preliquidated_toll_lat").val()) || 4.678457, lng: parseFloat($("#preliquidated_toll_lng").val()) || -74.059232},
		draggable: true
	});

	if ((document.getElementById('preliquidated_toll_lng') != null )&&(document.getElementById('preliquidated_toll_lat') != null )){
		document.getElementById('preliquidated_toll_lng').addEventListener('change', function() {
			map.setCenter({lat: parseFloat(document.getElementById('preliquidated_toll_lat').value), lng: parseFloat(document.getElementById('preliquidated_toll_lng').value)});
			marker.setPosition({lat: parseFloat(document.getElementById('preliquidated_toll_lat').value), lng: parseFloat(document.getElementById('preliquidated_toll_lng').value)});
		});
		document.getElementById('preliquidated_toll_lat').addEventListener('change', function() {
			map.setCenter({lat: parseFloat(document.getElementById('preliquidated_toll_lat').value), lng: parseFloat(document.getElementById('preliquidated_toll_lng').value)});
			marker.setPosition({lat: parseFloat(document.getElementById('preliquidated_toll_lat').value), lng: parseFloat(document.getElementById('preliquidated_toll_lng').value)});
		});
		google.maps.event.addListener(marker,'dragend',function(event) {
			document.getElementById("preliquidated_toll_lat").value = this.getPosition().lat().toString();
			document.getElementById("preliquidated_toll_lng").value = this.getPosition().lng().toString();
		});
	}
}

function initMapTollPathRoute() {
	map = new google.maps.Map(document.getElementById('map_toll'), {
		center: {lat: parseFloat($("#preliquidated_toll_lat").val()) || 4.678457, lng: parseFloat($("#preliquidated_toll_lng").val()) || -74.059232},
		zoom: 18
	});

	marker = new google.maps.Marker({
		map: map,
		position: {lat: parseFloat($("#preliquidated_toll_lat").val()) || 4.678457, lng: parseFloat($("#preliquidated_toll_lng").val()) || -74.059232},
		draggable: false
	});

}




;
$( document ).on('turbolinks:load', function() {

    $('#productcharacters').DataTable( {
        "processing": true,
        "serverSide": true,
        "pageLength": 25,
        "cache": false,
        "language": { "url": "/assets/datatables/datatables_spanish.json" },
        "ajax": {
            'url': "/productcharacters/list.json",
            'type': 'GET',
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+$('#productcharacters').attr('data-toc'));
            }
        }
    } );
});
$( document ).on('turbolinks:load', function() {
        
    $('#productclassifications').DataTable( {
        "processing": true,
        "serverSide": true,
        "pageLength": 25,
        "cache": false,
        "language": { "url": "/assets/datatables/datatables_spanish.json" },
        "ajax": {
            'url': "/productclassifications/fromcharacter/"+$('#productclassifications').attr('character-id')+".json",
            'type': 'GET',
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+$('#productclassifications').attr('data-toc'));
            }
        }
    } );
});
$( document ).on('turbolinks:load', function() {
        
    $('#productcodes').DataTable( {
        "processing": true,
        "serverSide": true,
        "pageLength": 25,
        "cache": false,
        "language": { "url": "/assets/datatables/datatables_spanish.json" },
        "ajax": {
            'url': "/productcodes/fromclass/"+$('#productcodes').attr('class-id')+".json",
            'type': 'GET',
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+$('#productcodes').attr('data-toc'));
            }
        }
    } );
});
(function() {


}).call(this);
(function() {


}).call(this);
$( document ).on('turbolinks:load', function() {
    $("#replacement_workshop_headquarter_id").on('change', function() {
        var headquarter = $( "#replacement_workshop_headquarter_id option:selected" ).val();
        $('#replacement_workshop_warehouse_id').find('option:not(:first)').remove();
        $.ajax({
            url: '/workshop/warehouses/get_warehouses_by_headquarter/'+headquarter+'.json',
            type: "get",
            async: false,
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+ token );
            },
            success: function(result) {
                if (result.success == true) {
                    // alert(result.data);
                    warehouses = result.data
                    $.each(warehouses, function (index,item) {
                        $("#replacement_workshop_warehouse_id").append('<option value="'+item.id+'">' + item.name + '</option>')
                        $("#replacement_workshop_warehouse_id").focus()
                    });
                }
                else {
                }
            }
        });
    });

    $('#replacement_minimum_inventory').on('change',function(){
        $('#replacement_maximum_inventory').prop('min', parseInt($(this).val()));
	});
})
;
$(document).on('turbolinks:load', function() {
})
;
$(document).on('turbolinks:load', function() {
	sessionStorage.setItem("naturaleza", JSON.stringify([]));
	sessionStorage.setItem("clasificacion", JSON.stringify([]));
	sessionStorage.setItem("producto", JSON.stringify([]));
	sessionStorage.setItem("select_unload", JSON.stringify([]));

	$(document).on('change', 'select.dep', function() {
		var id_value_string = $(this).val();
		var select = $(this).parent().parent().next().find('select.city');
		if (id_value_string == "") {
			$(select).children('option').remove();
			var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
			$(row).appendTo(select);
		} else {      //Send the request and update person_city_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/system/tables/cities/getcitiesbydepartment/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					alert("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { // Clear all options from person_city_id select
					if (data.success){
						$(select).children('option').remove();  //put in a empty default line
						var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
						$(row).appendTo(select);   // Fill person_city_id select
						$.each(data.data, function(i, j) {
							row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
							$(row).appendTo(select);
						});
					}
				}
			});
		}
	});

	$(document).on('change', 'select.p1', function() {
		var id_value_string = $(this).val();
		
		fill_products(); 	

		var select = $(this).parent().parent().next().find('select.p2');
		if (id_value_string == "") {
			$(select).children('option').remove();
			var row = "<option value=\"" + "" + "\">" + "Seleccione Clasificación del producto" + "</option>";
			$(row).appendTo(select);
		} else {      //Send the request and update person_city_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/productclassifications/get_productclassification_by_productcharacters/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					alert("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { // Clear all options from person_city_id select
					if (data.success){
						$(select).children('option').remove();  //put in a empty default line
						var row = "<option value=\"" + "" + "\">" + "Seleccione Clasificación del producto" + "</option>";
						$(row).appendTo(select);   // Fill person_city_id select
						$.each(data.data, function(i, j) {
							row = "<option value=\"" + j.id + "\">" + j.value + "</option>";
							$(row).appendTo(select);
						});
					}
				}
			});
		}
	});

	$(document).on('change', 'select.p2', function() {
		var id_value_string = $(this).val();
		var select = $(this).parent().parent().next().find('select.p3');

		fill_products();

		if (id_value_string == "") {
			$(select).children('option').remove();
			var row = "<option value=\"" + "" + "\">" + "Seleccione producto" + "</option>";
			$(row).appendTo(select);
		} else {      //Send the request and update person_city_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/productcodes/get_code_by_productclassification/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					alert("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { // Clear all options from person_city_id select
					if (data.success){
						$(select).children('option').remove();  //put in a empty default line
						var row = "<option value=\"" + "" + "\">" + "Seleccione producto" + "</option>";
						$(row).appendTo(select);   // Fill person_city_id select
						$.each(data.data, function(i, j) {
							row = "<option value=\"" + j.id + "\">" + j.value + "</option>";
							$(row).appendTo(select);
						});
					}
				}
			});
		}
	});

	$(document).on('change', 'select.p3', function() {
		var p1 = $(this).parent().parent().parent().find('select.p1');
		var p2 = $(this).parent().parent().parent().find('select.p2');
		console.log($(this).val())
		
		fill_products();

		if ($(this).hasClass( "unload_p3" )){
			$(p1).children('option').remove();  //put in a empty default line
    		$(p2).children('option').remove();  //put in a empty default line
    		productos = JSON.parse(sessionStorage.getItem("producto"));
    		id = exist(productos,$(this).val())
    		p = productos[id]
    		row = "<option value=\"" + p.naturaleza + "\">" + p.naturaleza_t + "</option>";
    		$(row).appendTo(p1);
    		row = "<option value=\"" + p.clasificacion + "\">" + p.clasificacion_text + "</option>";
    		$(row).appendTo(p2);
    	}
    });

	$(document).on('click', '.add_address', function(){
		check_first_and_last();
		set_value_isfirst();
	})

	$(document).on('click', '.remove_address', function(){
		check_first_and_last();
		set_value_isfirst();
	})

	$(document).on('click', '.add_product', function(){
		var checkload = $(this).parent().parent().find('.loadaddresscheck');
		var checkunload = $(this).parent().parent().find('.unloadaddresscheck');
		// console.log(checkload.prop('checked'));
		$(this).parent().parent().find('.load_product').prop("disabled", false);

		var carga_producto = $(this).parent().parent().find('.si_carga');
		var descarga_producto = $(this).parent().parent().find('.no_carga');

		carga = checkload.prop('checked')
		descarga = checkunload.prop('checked')
		if (carga.toString() === 'true' && descarga.toString() === 'false'){
			$(carga_producto).prop('checked', true);
			$(this).parent().parent().find('.load_product').prop("disabled", true);
		} else if (carga.toString() === 'false' && descarga.toString() === 'true'){
			$(descarga_producto).prop('checked', true);
			var p1 = $(this).parent().parent().parent().find('select.p1');
			var p2 = $(this).parent().parent().parent().find('select.p2');
			var p3 = $(this).parent().parent().parent().find('select.p3');
			productos = JSON.parse(sessionStorage.getItem("producto"));
			jQuery(p1).prop("disabled", true);
			jQuery(p2).prop("disabled", true);
			fill_select(p3,productos);
			$(this).parent().parent().find('.load_product').prop("disabled", true);
		} else if (carga.toString() === 'true' && descarga.toString() === 'true'){
			$(this).parent().parent().find('.load_product').prop("disabled", false);
		}
		
	})

	$(document).on('change', 'input.load_product', function(){
		var p1 = $(this).parent().parent().parent().find('select.p1');
		var p2 = $(this).parent().parent().parent().find('select.p2');
		var p3 = $(this).parent().parent().parent().find('select.p3');
		// var p1 = $(this).parent().parent().next().find('select.p1');
		var cargar = $(this).val()

		if ($(this).val().toString() === 'true'){
			jQuery(p1).removeAttr("disabled");
			jQuery(p2).removeAttr("disabled");
		} else {
			jQuery(p1).prop("disabled", true);
			jQuery(p2).prop("disabled", true);
			productos = JSON.parse(sessionStorage.getItem("producto"));
			fill_select(p3,productos,cargar);
		}
	})

	function exist(obj,value){
		// iterate over each element in the array
		for (var i = 0; i < obj.length; i++){
	  		if (obj[i].id == value){ // look for the entry with a matching `code` value
	  			return i
	  		}	
	  	}
	  	return false
	  }

	  function fill_products(){
	  	var naturaleza = [];
	  	var clasificacion = [];
	  	var producto = [];
	  	$( "select.p1" ).each(function( index ) {
	  		var id_value_string = $( this ).val();
	  		var texto = $(this).children("option").filter(":selected").text()
	  		json = {id: id_value_string, name: texto};
	  		result = exist(naturaleza, id_value_string)
	  		if (result === false) {
	  			naturaleza.push(json)
	  		}
	  	});
	  	sessionStorage.setItem("naturaleza", JSON.stringify(naturaleza));

	  	$( "select.p2" ).each(function( index ) {
	  		var id_value_string = $( this ).val();
	  		var texto = $(this).children("option").filter(":selected").text()
	  		json = {id: id_value_string, name: texto};
	  		result = exist(clasificacion, id_value_string)
	  		if (result === false) {
	  			clasificacion.push(json)
	  		}
	  	});

	  	sessionStorage.setItem("clasificacion", JSON.stringify(clasificacion));

	  	$( "select.p3" ).each(function( index ) {
	  		var p1 = $(this).parent().parent().parent().find('select.p1');
	  		var p2 = $(this).parent().parent().parent().find('select.p2');
	  		console.log();
	  		console.log($( p2 ).val());

	  		nat = $( p1 ).val();
	  		cla = $( p2 ).val();

	  		nat_text = $( p1 ).children("option").filter(":selected").text()
	  		cla_text = $( p2 ).children("option").filter(":selected").text()


	  		var id_value_string = $( this ).val();
	  		var texto = $(this).children("option").filter(":selected").text()
	  		json = {id: id_value_string, name: texto, naturaleza: nat, clasificacion: cla, naturaleza_t: nat_text, clasificacion_text: cla_text};
	  		result = exist(producto, id_value_string)
	  		if (result === false) {
	  			producto.push(json)
	  		}
	  	});

	  	sessionStorage.setItem("producto", JSON.stringify(producto));
  //   	var json = {id: id_value_string, name: texto};
		// var data = sessionStorage.getItem('naturaleza');

		// var naturaleza = JSON.parse(sessionStorage.getItem("naturaleza"));
		// result = exist(naturaleza, id_value_string)
		// console.log(result)
		// if (result === false) {
		// 	naturaleza.push(json)
		// }

		// sessionStorage.setItem("naturaleza", JSON.stringify(naturaleza));
	}

	function check_first_and_last(){
    	//uncheck ultimo carga
    	jQuery('body').find('.loadaddresscheck:checkbox:last').prop("checked", false);

    	//Habilitar Check
    	jQuery('body').find('.loadaddresscheck:checkbox').prop("disabled", false);
    	jQuery('body').find('.unloadaddresscheck:checkbox').prop("disabled", false);

    	//DesHabilitar Primer Check
    	jQuery('body').find('.loadaddresscheck:checkbox:first').prop("disabled", true);
    	jQuery('body').find('.unloadaddresscheck:checkbox:first').prop("disabled", true);

    	//Check Primero Carga
    	jQuery('body').find('.loadaddresscheck:checkbox:first').prop("checked", true);

    	//Check ultimo descarga
    	jQuery('body').find('.unloadaddresscheck:checkbox:last').prop("checked", true);

    	//Uncheck primero descarga
    	jQuery('body').find('.unloadaddresscheck:checkbox:first').prop("checked", false);


    	//DesHabilitar Ultimo Check
    	jQuery('body').find('.loadaddresscheck:checkbox:last').prop("disabled", true);
    	jQuery('body').find('.unloadaddresscheck:checkbox:last').prop("disabled", true);

    	

    }

    function fill_select(p3,json) {
    	p3.addClass('unload_p3')

    	$(p3).children('option').remove();  //put in a empty default line
    	var row = "<option value=\"" + "" + "\">" + "Seleccione producto" + "</option>";
		$(row).appendTo(p3);   // Fill person_city_id select
		$.each(json, function(i, j) {
			row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
			$(row).appendTo(p3);
		});	
	}
	function set_value_isfirst(){
		jQuery('body').find('.is_first').prop("value", false);
		jQuery('body').find('.is_first:first').prop("value", true);
	}

	function enable() {
		$('input:disabled, select:disabled').each(function () {
			$(this).removeAttr('disabled');
			$(this).removeAttr('readonly');
		});
	}

	function validate_num_address(){
		var NumAddress = $('.direcciones').length
		if (NumAddress >= 2) {
			return true
		} else {
			return false
		}
	}

	function validate_num_products(){
		$('.direcciones').each(function (index, value) {
			numProdAddres = $(this).find(".div-add-products").length
			console.log(numProdAddres)
			if (numProdAddres < 1) {
				return false
			}
		});
	}

	$('#request').submit(function() {
		if (validate_num_address() == false){
			alert('Debe agregar por lo menos una dirección de carga y una de descarga ')
			return false
		}
		if (validate_num_products() == false){
			alert('Debe agregar por lo menos una producto por cada dirección')
			return false
		}
		enable();
	});

	if ( $("#requests").length ) {
		$('#requests').DataTable( {
			"processing": true,
			"serverSide": true,
			"pageLength": 25,
			"cache": false,
			"order": [[ 1, "desc" ]],
			"language": { "url": "/assets/datatables/datatables_spanish.json" },
			"ajax": {
				'url': "/requests"+( filtro!='' ? '?filtro='+filtro : '')+".json",
				'type': 'GET',
				'beforeSend': function (request) {
					request.setRequestHeader("Authorization", 'Bearer '+$('#requests').attr('data-toc') );
				}
			}
		});
	}

	$(document).on('click', '.report_checkpoint', function() {
		var request =  $(this).attr('request_id');
		var checkpoint = $(this).attr('checkpoint_id');
		var driver = $(this).attr('driver_id');
		var vehicle = $(this).attr('vehicle_id');
		$.ajax({
			url: '/requests/report_checkpoint_traffic.js',
			type: "POST",
			async: false,
			data: {request_id: request, checkpoint_id: checkpoint, driver_id: driver, vehicle_id: vehicle },
			'beforeSend': function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+token );
			},
			success: function(result) {
				if (result.success == false) {
					alert(result.message);
				}
			}
		});
	});

	$(document).on('click', '.OpenModalAddTime', function() {
		var route_checkpoint_id = $(this).attr('route_checkpoint_id');
		$('#route_checkpoint_id').val(route_checkpoint_id)	
	});

	$(document).on('click', '.ShowAddTimeToCheckpoint', function() {
		var route_checkpoint_id = $(this).attr('route_checkpoint_id');
		var request_id = $(this).attr('request_id');
		var tbody = $('#addtime_to_checkpoint_requests')
		tbody.empty();
		$.ajax({
			dataType: "json",
			cache: false,
			url: '/requests/get_time_to_checkpoint/' + request_id + '/' + route_checkpoint_id + '.json',
			timeout: 5000,
			'beforeSend': function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+token );
			},
			error: function(XMLHttpRequest, errorTextStatus, error) {
				console.log("Failed to submit : " + errorTextStatus + " ;" + error);
			},
			success: function(data) { // Clear all options from person_city_id select
				if (data.success){
					$.each(data.data, function(i, addtime) {
						tbody.append('<tr>' +
							'<td width="10%">'+(i+1)+'</td>'+
							'<td width="10%">'+addtime.addtime+'</td>'+
							'<td width="10%">'+addtime.comment+'</td>'+
							'<td width="10%">'+addtime.user.full_name+'</td>'+
							'<td width="10%">'+moment(addtime.created_at).format('YYYY-MM-DD hh:mm A') +'</td>'+
							'</tr>'
							);
					});
				}
			}
		});
	});

	if ( $("#body_plan_route").length ) {
		setTimeout(function(){ plant_route_f(); }, 5000);
		$(function(){
			setInterval(plant_route_f, (20*60*1000));
		});
	}

	if ($('#map_request_detail').length){
		setTimeout(function(){ redraw_polyline(); }, 10000);
		$(function(){
			setInterval(redraw_polyline, (3*60*1000));
		});
	}

});

function plant_route_f() {
	$.ajax({
		url: "/checkpoints/plan_route/"+request_id,
		type: "GET",
		async: true,
		'beforeSend': function (request) {
			request.setRequestHeader("Authorization", 'Bearer '+token );
			try {
				$('#body_plan_route').html("<tr><td colspan='9'><img src='/web/img/loading.gif' /></td></tr>");
			} catch(error){

			}
		},
		success: function(result) {
			// console.log(result)
		}
	});
}

function validReasignRequestForm(){
	var input = $('#placa');
	if (!input.val()) {
		input.css("borderColor", "#FF3C3A");
		return false;
	}
	input.css("borderColor", "#ccc");
	return true;
}
var map
var markers = [];
var markersPolygon = [];
var markerCluster;
var markerClusterPolygon;
var polyLine = [];
var polygons = [];
var infowindow;
function initMapRequestDetail() {
	map = new google.maps.Map(document.getElementById('map_request_detail'), {
		center: {lat: parseFloat($("#lat_detail").val()) || 4.678457, lng: parseFloat($("#lng_detail").val()) || -74.059232},
		zoom: 18
	});
	// markerCluster = new MarkerClusterer(map, [], {imagePath: '/assets/js-marker-clusterer/m'});
	markerCluster = new markerClusterer.MarkerClusterer({ map: map, markers: markers });

	infowindow = new google.maps.InfoWindow();
	dbRefDetail = firebase.database().ref(document.getElementById("location_vehicle").value);
	dbRefDetail.on('value', function(vehicle) {
		AddCar(vehicle) 
	});
	AddPolygon();
	AddPolyline();
}
function AddCar(car) {
	try {
		var infowindow = new google.maps.InfoWindow();	
		markerCluster.clearMarkers();
		if (car.val().latitud && car.val().longitud) {
			createMarker(map, markers, car.val(), infowindow, markerCluster);
			map.setCenter({lat: car.val().latitud, lng: car.val().longitud});
		}
		google.maps.event.addListener(map, 'click', function() {
			infowindow.close();
		});
	}
	catch(error) {
		console.error(error);
	}

} 


function mousefn(evt) {
	infowindow.setContent("polygon<br>coords:" + bounds.getCenter().toUrlValue(6));
	infowindow.setPosition(bounds.getCenter());
	infowindow.open(map);
}


function infoCallback(infowindow) {
	return function() {
		infowindow.open(map);
	};
}


AddPolygon = function()
{

    for (let i = 0; i < markersPolygon.length; i++) {
		markersPolygon[i].setMap(null);
	}
	infowindow.opened = false;
	$( ".array_polygon_to_map" ).each(function( index ) {
		json = $( this ).val()
		report = $( this ).attr("is_reported");
		time_report = $( this ).attr("time_report");
		name_checkpoint = $( this ).attr("name_checkpoint");
		if (report == 'true'){
			color = '#4624F3'
		} else {
			color = '#FF0000'
		}
		coordinates = JSON.parse(json);
  		// Define the LatLng coordinates for the polygon's path.
  		coordinates = setPolypointsToMapDetail(coordinates)
  		// Construct the polygon.
  		var polygon = new google.maps.Polygon({
  			paths: coordinates,
  			strokeColor: color,
  			strokeOpacity: 0.8,
  			strokeWeight: 2,
  			fillColor: color,
  			fillOpacity: 0.35
  		});
  		polygon.setMap(map);
  		var bounds = new google.maps.LatLngBounds();
    	for (var i = 0; i < polygon.getPath().getLength(); i++) {
      		bounds.extend(polygon.getPath().getAt(i));
    	}

  		var info = "<div><h3>" + name_checkpoint +
         "</h3><h4>Fecha y Hora de reporte: " +
         "</h4><h4>" + time_report +
         "</h4></div>"
        var infowindow = new google.maps.InfoWindow({
         	content: info,
         	position: bounds.getCenter()
        });

        markerPolygon = new google.maps.Marker({
    		position: {lat: bounds.getCenter().lat(), lng: bounds.getCenter().lng()},
  		});
  		markerPolygon.setMap(map);
  		markersPolygon.push(markerPolygon);
        
         
         // google.maps.event.addListener(polygon, 'click', infoCallback(infowindow));

	   	google.maps.event.addListener(polygon, 'mouseover', infoCallback(infowindow));
    	google.maps.event.addListener(polygon, 'mouseout', function(evt) {
	      	infowindow.close();
      		infowindow.opened = false;
    	});


    	polygons.push(polygon);
    });       
}

var AddPolyline = function(){
	var polyLineCordinates = [];
	$.ajax('/requests/get_polyline_by_request/' + request_id +'.json', {
		type: 'GET',
		dataType: 'json',
		beforeSend: function(request) {
			return request.setRequestHeader('Authorization', 'Bearer ' + token);
		},
		success: function(data, textStatus) {
			console.log(data)
			if (data.success == true){
				if (data.polyline_type == 1){
					var kalmanFilter = new GPSKalmanFilter()
					updatedCoords = []
					data.data.tracing.forEach(function(polypoint) {
						timestampInMs = Math.round(new Date(polypoint.date_report).getTime()/1000)
						s = kalmanFilter.process(parseFloat(polypoint.lat), parseFloat(polypoint.lng), 50, timestampInMs)
						updatedCoords.push(s)
					})
					var polyLineCordinates = setPolypointsToMapDetail(updatedCoords)					
				} else if(data.polyline_type == 2){
					var polyLineCordinates = setPolypointsToMapDetail(data.data)
				}
				console.log(polyLineCordinates)
				polyLine = new google.maps.Polyline({
					path: polyLineCordinates,
					geodesic: true,
					strokeColor: '#FF0000',
					strokeOpacity: 1.0,
					strokeWeight: 2
				});
				polyLine.setMap(map);
			} else {
				polyLine = new google.maps.Polyline({
					path: [],
					geodesic: true,
					strokeColor: '#FF0000',
					strokeOpacity: 1.0,
					strokeWeight: 2
				});
			}
		}
	});
}


function setPolypointsToMapDetail(polypoints){
	var polygonCoords = [];
	try {
		polypoints.forEach(function(polypoint) {
			polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
		})
		return polygonCoords;
	} catch(e) {
		console.log(e)
		return polygonCoords;
	}
}


function removeLine() {
	polyLine.setMap(null);	
}
function removePolygons() {
	for (let i = 0; i < polygons.length; ++i) {
		polygons[i].setMap(null)
	}
}
function redraw_polyline(){
	removeLine();
	removePolygons();
	AddPolyline();
	AddPolygon()
}



function anular_anticipo_siigo(btn, r, transactionID) {
	console.log('===')
	console.log(r)
	console.log(transactionID)
    if (confirm("¿Esta seguro de anular el anticipo")) {
        btn.disabled = true;
        btn.innerHTML = "Anulando...";
        console.log("request: " + r + " transactionID: " + transactionID);
        $.ajax({
            method: "POST",
            url: "/siigo/accounting_vouchers/annulled_advanced.json",
            timeout: 300000,
            data: {
                request_id: r,
                transaction_code: transactionID,
            },
            beforeSend: function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+token_user);
            },
            success: function (result) {
            	console.log(result)
                $('#msgAdvances').show();
                if (result.success == true) {
                    if (result.response_success == true){
	                  	$('#msgAdvances').html(result.message + "<br><b>Servicio:</b> " + r + "<br><b>Transaccion:</b> " + transactionID);
	                    $('#msgAdvances').removeClass("alert-success alert-danger").addClass("alert-success")
	                    setTimeout(function () {
	                        $('#msgAdvances').fadeOut('slow');
	                    }, 3000);
	                    $(btn).parent().parent().children("td:eq(8)").html("<button type='button' class='btn btn-warning btn-xs'>No Reportado</button>");
	                    $(btn).fadeOut('fast');
                    } else {
                    	$('#msgAdvances').text(result.message);
	                    $('#msgAdvances').removeClass("alert-success alert-danger").addClass("alert-danger");
	                    setTimeout(function () {
	                        $('#msgAdvances').fadeOut('slow');
	                    }, 3000);	
                    }
                }
                if (result.success == false) {
                    $('#msgAdvances').text(result.message);
                    $('#msgAdvances').removeClass("alert-success alert-danger").addClass("alert-danger");
                    setTimeout(function () {
                        $('#msgAdvances').fadeOut('slow');
                    }, 3000);
                }
                btn.disabled = false;
                btn.innerHTML = "Anular Siesa y desmarcar reportado";
            }
        });
    }
}

function reportar_anticipo_siigo(btn, r, transactionID) {
	console.log(r)
	console.log(transactionID)
    if (confirm("¿Esta seguro de reportar el anticipo")) {
        btn.disabled = true;
        btn.innerHTML = "Reportando...";
        console.log("request: " + r + " transactionID: " + transactionID);
        $.ajax({
            method: "POST",
            url: "/siigo/accounting_vouchers/reported_advanced_siigo.json",
            timeout: 300000,
            data: {
                request_id: r,
                transaction_code: transactionID,
            },
            beforeSend: function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+token_user);
            },
            success: function (result) {
            	console.log(result)
                $('#msgAdvances').show();
                if (result.success == true) {
                    if (result.response_success == true){
	                  	$('#msgAdvances').html(result.message + "<br><b>Servicio:</b> " + r + "<br><b>Transaccion:</b> " + transactionID);
	                    $('#msgAdvances').removeClass("alert-success alert-danger").addClass("alert-success")
	                    setTimeout(function () {
	                        $('#msgAdvances').fadeOut('slow');
	                    }, 3000);
	                    $(btn).parent().parent().children("td:eq(8)").html("<button type='button' class='btn btn-success btn-xs'>Reportado</button>");
	                    $(btn).fadeOut('fast');
                    } else {
                    	$('#msgAdvances').text(result.message);
	                    $('#msgAdvances').removeClass("alert-success alert-danger").addClass("alert-danger");
	                    setTimeout(function () {
	                        $('#msgAdvances').fadeOut('slow');
	                    }, 3000);	
                    }
                }
                if (result.success == false) {
                    $('#msgAdvances').text(result.message);
                    $('#msgAdvances').removeClass("alert-success alert-danger").addClass("alert-danger");
                    setTimeout(function () {
                        $('#msgAdvances').fadeOut('slow');
                    }, 3000);
                }
                btn.disabled = false;
                btn.innerHTML= "Reportar Siigo";
            }
        });
    }
}
;
function validatePolypointsForm(){
	var risk_area_polypoints = $("#risk_area_polypoints").val();
	if (!risk_area_polypoints) {
		alert("Puntos en el mapa obligatorio");
		return false;
	}
	return true;
}
// https://developers.google.com/maps/documentation/javascript/examples/drawing-tools
var map;
var drawingManager;
var currentPolygon;
var currentCircle;

function initMapRiskArea() {
	var risk_area_polypoints = $("#risk_area_polypoints").val();
	var lat = null;
	var lng = null;
	if(risk_area_polypoints) {
		var risk_area_polypoints_parse = JSON.parse(risk_area_polypoints)
		for (var i = risk_area_polypoints_parse.length - 1; i >= 0; i--) {
			lat = parseFloat(risk_area_polypoints_parse[i].lat);
			lng = parseFloat(risk_area_polypoints_parse[i].lng);
			break;
		}
	}
	map = new google.maps.Map(document.getElementById('map'), {
		center: {lat: lat || 4.678457, lng: lng || -74.059232},
		zoom: 18
	});

	drawingManager = new google.maps.drawing.DrawingManager({
		drawingMode: google.maps.drawing.OverlayType.POLYGON,
		drawingControl: true,
		drawingControlOptions: {
			position: google.maps.ControlPosition.TOP_CENTER,
			drawingModes: []
		},
		polygonOptions: {
			fillColor: '#FF0000',
			fillOpacity: 0.4,
			strokeColor: "#000000",
			strokeWeight: 3,
			clickable: false,
			editable: true
		}
	});
	drawingManager.setMap(map);
	if (risk_area_polypoints) {
		drawingManager.setDrawingMode(null);
		$("#reDrawRiskArea").removeClass("hide");
		var polygonCoords = [];
		JSON.parse(risk_area_polypoints).forEach(function(polypoint) {
			polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
		})
		var polygon = new google.maps.Polygon({
			paths: polygonCoords,
			strokeColor: '#000000',
			fillColor: '#FF0000',
			fillOpacity: 0.4,
			editable: true
		});
		currentPolygon = polygon;
		google.maps.event.addListener(polygon.getPath(), 'set_at', function() {
			$("#risk_area_polypoints").val(setPolypoints(polygon.getPath()));
			$("#risk_area_polygon").val(setPolygon(polygon.getPath()));
		});
		google.maps.event.addListener(polygon.getPath(), 'insert_at', function() {
			$("#risk_area_polypoints").val(setPolypoints(polygon.getPath()));
			$("#risk_area_polygon").val(setPolygon(polygon.getPath()));
		});
		polygon.setMap(map);
	}
	google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
		if (event.type == 'polygon') {
			$("#reDrawRiskArea").removeClass("hide");
			$("#risk_area_polypoints").val(setPolypoints(event.overlay.getPath()));
			$("#risk_area_polygon").val(setPolygon(event.overlay.getPath()));
			currentPolygon = event.overlay;
			drawingManager.setDrawingMode(null);
			google.maps.event.addListener(event.overlay.getPath(), 'set_at', function() {
				$("#risk_area_polypoints").val(setPolypoints(event.overlay.getPath()));
				$("#risk_area_polygon").val(setPolygon(event.overlay.getPath()));
			});
			google.maps.event.addListener(event.overlay.getPath(), 'insert_at', function() {
				$("#risk_area_polypoints").val(setPolypoints(event.overlay.getPath()));
				$("#risk_area_polygon").val(setPolygon(event.overlay.getPath()));
			});
		}
	});
}

function removePolylineRiskArea() {
	drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
	if (currentPolygon) { currentPolygon.setMap(null); }
	$("#risk_area_polypoints").val(null);
	$("#risk_area_polygon").val(null);
	drawingManager.setMap(map);
	$("#reDrawRiskArea").addClass("hide");
}

function setPolypoints(path){
	var polypoints = [];
	path.forEach(function(element) {
		polypoints.push('{"lat": "' + element.lat() + '", "lng": "' + element.lng() + '"}');
	})
	return '['+polypoints.toString()+']';
}

function setPolygon(path){
	var polygon = "";
	path.forEach(function(element, index) {
		if (index == 0) {
			first_lng = element.lng().toString();
			first_lat = element.lat().toString();
		}
		polygon += element.lng().toString()+' '+element.lat().toString()+','
	})
	polygon += first_lng;
	polygon += ' '+first_lat;
	console.log('('+polygon+')');
	return '('+polygon+')';
}
;
$( document ).on('turbolinks:load', function() {

    $(".gen_name").change(function(){
        value_name = $("#scheme_type_number_drivers").val() + ' x ' + $("#scheme_type_number_vehicles").val() + ' - ' + $("#scheme_type_number_trailers").val();
        $('#scheme_type_name').val(value_name);
    });
    
});
(function() {


}).call(this);
(function() {


}).call(this);
$( document ).on('turbolinks:load', function() {

    var getSelectInfoSiderFreights;

    getSelectInfoSiderFreights = function(name, valor) {
      var aux, url, val;
      if (valor == null) {
        valor = '';
      }
      url = (function() {
        switch (name) {
          case "business_sider_freight_id":
            return '/commercial/businesses/getbusinessfrombigcustomer/' +  $('#bigcustomer_sider_freight_id').val();;
          case "route_freight":
            return '/sider_freights/getroutes/' + $('#sider_freight_sider_type_id').val() + '/' +  $('#business_sider_freight_id').val();
          case "product_freight":
            return '/sider_freights/getproducts/' + $('#sider_freight_sider_type_id').val() + '/' +  $('#business_sider_freight_id').val();
        }
      })();
      val = (function() {
        switch (name) {
          case "business_sider_freight_id":
            return 'description';
          case "route_freight":
            return 'name';
          case "product_freight":
            return 'name';
        }
      })();
      name = "#" + name;
      aux = '';
      valor = parseInt(valor);
      return $.ajax(url, {
        type: 'GET',
        dataType: 'json',
        beforeSend: function(request) {
          return request.setRequestHeader('Authorization', 'Bearer ' + token);
        },
        success: function(data, textStatus) {
          data = data.data;
          $.each(data, function(index, item) {
            if (valor !== "") {
              aux = '';
              if (item.id === valor) {
                aux = ' selected';
              }
            }
            return $(name).append('<option value="' + item.id + '"' + aux + '>' + item[val] + '</option>');
          });
          return $(name).focus();
        }
      });
    };

    var completeSelect;

    completeSelect = function(name, data, valor) {
        valor = parseInt(valor);
        $.each(data, function(index, item) {
            var aux;
            aux = '';
            if (item.id === valor) {
              aux = ' selected';
            }
            return $(name).append('<option value="' + item.id + '"' + aux + '>' + item.name + '</option>');
        });
        return $(name).focus();
    };

    var resetSelect;

    resetSelect = function(element) {
      $(element).children().remove();
      return $(element).append('<option value="">Seleccione:</option>');
    };


    $('#bigcustomer_sider_freight_id').on('change', function() {
      var data;
      resetSelect("#business_sider_freight_id");
      data = $('#bigcustomer_sider_freight_id option:selected').val();
      return getSelectInfoSiderFreights('business_sider_freight_id', data);
    });

    $('#business_sider_freight_id').on('change', function() {
      var data;
      resetSelect("#route_freight");
      resetSelect("#product_freight");
      data = $('#bigcustomer_sider_freight_id option:selected').val();
      getSelectInfoSiderFreights('route_freight', data);
      return  getSelectInfoSiderFreights('product_freight', data);
    });

    $('#sider_freight_sider_type_id').on('change', function() {
      var data;
      resetSelect("#route_freight");
      resetSelect("#product_freight");
      data = $('#bigcustomer_sider_freight_id option:selected').val();
      getSelectInfoSiderFreights('route_freight', data);
      return  getSelectInfoSiderFreights('product_freight', data);
    });

    // $('#business_sider_products_id').on("change", function() {
    //   resetSelect("#businessproducts_sider");
    //   return getSelectInfoSiderFreights('businessproducts_sider');
    // });

    if (typeof product_id !== "undefined" && product_id !== null) {
      if (product_id !== "") {
        completeSelect("#product_freight", products, product);
      }
    }

    if (typeof ruta_id !== "undefined" && ruta_id !== null) {
      if (ruta_id !== "") {
        completeSelect("#business_sider_freight_id", businesses, business);
        completeSelect("#route_freight", rutas, ruta);
      }
    }
    
});
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
$( document ).on('turbolinks:load', function() {

    var suppdocument_concepts_select = {}
    var x = 1;

    if(window.location.pathname.includes("/pay_suppdocument") === true)
    {
        $.get('get_suppdocument_concepts', function(data) {
            suppdocument_concepts_select = data;

            $("#field_concept_ps").append('<option value="">Seleccione un concepto</option>');
            $.each(suppdocument_concepts_select, function(index, item) {
                $("#field_concept_ps").append('<option value=' + item.id + '>' + item.name + '</option>');
            });
        });
    }

    $("#search_provider_ps").change(function(){
        console.log('cambia valor');
        $.get('get_provider_data?provider_id=' + $(this).val(), function(data) {
            console.log(data)
            provider_select = data;
            console.log(provider_select.taxes);
            console.log(provider_select.activities);
            console.log(provider_select.city);
            $("#economicactivity_name").val(provider_select.activities);
            $("#tax_name").text(provider_select.taxes);
            $("#city").text(provider_select.city);
            $("#declarant").text(provider_select.declarant);
            $("#verified").text(provider_select.verified);
        });
    });

    function calc_total_suppdoc()
    {
        var total = 0;
        $("[name='field_valor[]']").each(function() {
            console.log( this.value);
            total += parseInt(this.value);
            $("#total_suppdocument").html(total)
        });
    }

    //function calcular_concept_suppdoc(){
    //    console.log('calcula concepto');
    //    calc_total_suppdoc();
    //}

    $('.field_wrapper').keyup(function(){
        console.log('cambia valor');
        //calcular_concept_suppdoc();
        calc_total_suppdoc();
    })
    
    $("#btnAddConceptSuppDoc").click(function(){
        x=0;
        var maxField = 10;
        var options = '<option value="">Seleccione un concepto</option>'
        $.each(suppdocument_concepts_select, function(index, item) {
            options += '<option value=' + item.id + '>' + item.name + '</option>';
        });
        var select_concept = $("#field_concept_ps").html();

        console.log($("#field_concept_ps"));

        var wrapper = $('.field_wrapper');
        var fieldHTML = '<div class="row">\n' +
        '                <div class="col-md-5">' +
        '                    <select name="field_concept_suppdoc[]" id = "field_concept_ps" class="select2">'+select_concept+'</select>' +
        '                </div>' +
        '              <div class="col-md-3">\n' +
        '                <input type="number" name="field_valor[]" value=""/>\n' +
        '              </div>\n' +
        '                <div class="col-md-1 " style="top: 10px;"> <a href="javascript:void(0);" class="remove_button"><i class="fa fa-trash fa-fw"></i></a> </div>\n' +
            '            </div>'; 

            if(x < maxField){
                x++;
                $(wrapper).append(fieldHTML);
            }

            $(wrapper).on('click', '.remove_button', function(e){
                e.preventDefault();
                $(this).parent('div').parent('div').remove();
                x--;
                //calcular_concept_suppdoc();
                calc_total_suppdoc();
            });

            $("[name='field_concept_suppdoc[]']").each(function() {
                $(this).addClass("select2");
            });
    });

    $("#btnGenerarteSuppdoc").click( function(){
        var json={};
        document.getElementById("btnGenerarteSuppdoc").disabled = true;
        $("#btnGenerarteSuppdoc").off();
        var provider = $("#search_provider_ps").val();
        json.provider_id = provider;
        json.total_suppdocument = $("#total_suppdocument").html();
        json.concepts = []

        $("[name='field_concept_suppdoc[]']").each(function() {
            var concept = {}
            console.log( this.value);
            var valor = $(this).parent("div").parent("div").find("[name='field_valor[]']").val();
            var code_concept = $(this).parent("div").parent("div").find("[name='field_concept_suppdoc[]']").val();
            concept.total_value = valor;
            concept.code_concept = code_concept;
            json.concepts.push(concept);
        });
        var data_json = {}
        data_json.data_json = json ;
        JSON.stringify(data_json)
        console.log(JSON.stringify(data_json));

        $.ajax({
            url: 'create_pay_suppdocument.json',
            type: "POST",
            async: false,
            data: json,
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+ token );
            },
            success: function(result) {
                if (result.success == false) {
                    alert(result.message);
                }
                else {
                    alert('ok');
                }
            }
        });
    });


});
!function(t,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(t=t||self).Sweetalert2=e()}(this,function(){"use strict";function r(t){return(r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function a(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")}function o(t,e){for(var n=0;n<e.length;n++){var o=e[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(t,o.key,o)}}function c(t,e,n){return e&&o(t.prototype,e),n&&o(t,n),t}function s(){return(s=Object.assign||function(t){for(var e=1;e<arguments.length;e++){var n=arguments[e];for(var o in n)Object.prototype.hasOwnProperty.call(n,o)&&(t[o]=n[o])}return t}).apply(this,arguments)}function u(t){return(u=Object.setPrototypeOf?Object.getPrototypeOf:function(t){return t.__proto__||Object.getPrototypeOf(t)})(t)}function l(t,e){return(l=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t})(t,e)}function d(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Date.prototype.toString.call(Reflect.construct(Date,[],function(){})),!0}catch(t){return!1}}function i(t,e,n){return(i=d()?Reflect.construct:function(t,e,n){var o=[null];o.push.apply(o,e);var i=new(Function.bind.apply(t,o));return n&&l(i,n.prototype),i}).apply(null,arguments)}function p(t,e){return!e||"object"!=typeof e&&"function"!=typeof e?function(t){if(void 0===t)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return t}(t):e}function f(t,e,n){return(f="undefined"!=typeof Reflect&&Reflect.get?Reflect.get:function(t,e,n){var o=function(t,e){for(;!Object.prototype.hasOwnProperty.call(t,e)&&null!==(t=u(t)););return t}(t,e);if(o){var i=Object.getOwnPropertyDescriptor(o,e);return i.get?i.get.call(n):i.value}})(t,e,n||t)}function m(e){return Object.keys(e).map(function(t){return e[t]})}function h(t){return Array.prototype.slice.call(t)}function g(t,e){var n;n='"'.concat(t,'" is deprecated and will be removed in the next major release. Please use "').concat(e,'" instead.'),-1===z.indexOf(n)&&(z.push(n),_(n))}function v(t){return t&&"function"==typeof t.toPromise}function b(t){return v(t)?t.toPromise():Promise.resolve(t)}function y(t){return t&&Promise.resolve(t)===t}function w(t){return t instanceof Element||"object"===r(e=t)&&e.jquery;var e}function t(t){var e={};for(var n in t)e[t[n]]="swal2-"+t[n];return e}function C(t){var e=Q();return e?e.querySelector(t):null}function e(t){return C(".".concat(t))}function n(){var t=$();return h(t.querySelectorAll(".".concat(Y.icon)))}function k(){var t=n().filter(function(t){return vt(t)});return t.length?t[0]:null}function x(){return e(Y.title)}function P(){return e(Y.content)}function A(){return e(Y.image)}function B(){return e(Y["progress-steps"])}function S(){return e(Y["validation-message"])}function E(){return C(".".concat(Y.actions," .").concat(Y.confirm))}function O(){return C(".".concat(Y.actions," .").concat(Y.cancel))}function T(){return e(Y.actions)}function L(){return e(Y.header)}function j(){return e(Y.footer)}function q(){return e(Y["timer-progress-bar"])}function I(){return e(Y.close)}function V(){var t=h($().querySelectorAll('[tabindex]:not([tabindex="-1"]):not([tabindex="0"])')).sort(function(t,e){return t=parseInt(t.getAttribute("tabindex")),(e=parseInt(e.getAttribute("tabindex")))<t?1:t<e?-1:0}),e=h($().querySelectorAll('\n  a[href],\n  area[href],\n  input:not([disabled]),\n  select:not([disabled]),\n  textarea:not([disabled]),\n  button:not([disabled]),\n  iframe,\n  object,\n  embed,\n  [tabindex="0"],\n  [contenteditable],\n  audio[controls],\n  video[controls],\n  summary\n')).filter(function(t){return"-1"!==t.getAttribute("tabindex")});return function(t){for(var e=[],n=0;n<t.length;n++)-1===e.indexOf(t[n])&&e.push(t[n]);return e}(t.concat(e)).filter(function(t){return vt(t)})}function M(){return!J()&&!document.body.classList.contains(Y["no-backdrop"])}function R(){return $().hasAttribute("data-loading")}function H(e,t){var n;e.textContent="",t&&(n=(new DOMParser).parseFromString(t,"text/html"),h(n.querySelector("head").childNodes).forEach(function(t){e.appendChild(t)}),h(n.querySelector("body").childNodes).forEach(function(t){e.appendChild(t)}))}function D(t,e){if(e){for(var n=e.split(/\s+/),o=0;o<n.length;o++)if(!t.classList.contains(n[o]))return;return 1}}function N(t,e,n){var o,i;if(i=e,h((o=t).classList).forEach(function(t){-1===m(Y).indexOf(t)&&-1===m(Z).indexOf(t)&&-1===m(i.showClass).indexOf(t)&&o.classList.remove(t)}),e.customClass&&e.customClass[n]){if("string"!=typeof e.customClass[n]&&!e.customClass[n].forEach)return _("Invalid type of customClass.".concat(n,'! Expected string or iterable object, got "').concat(r(e.customClass[n]),'"'));mt(t,e.customClass[n])}}var U="SweetAlert2:",_=function(t){console.warn("".concat(U," ").concat(t))},F=function(t){console.error("".concat(U," ").concat(t))},z=[],W=function(t){return"function"==typeof t?t():t},K=Object.freeze({cancel:"cancel",backdrop:"backdrop",close:"close",esc:"esc",timer:"timer"}),Y=t(["container","shown","height-auto","iosfix","popup","modal","no-backdrop","no-transition","toast","toast-shown","toast-column","show","hide","close","title","header","content","html-container","actions","confirm","cancel","footer","icon","icon-content","image","input","file","range","select","radio","checkbox","label","textarea","inputerror","validation-message","progress-steps","active-progress-step","progress-step","progress-step-line","loading","styled","top","top-start","top-end","top-left","top-right","center","center-start","center-end","center-left","center-right","bottom","bottom-start","bottom-end","bottom-left","bottom-right","grow-row","grow-column","grow-fullscreen","rtl","timer-progress-bar","timer-progress-bar-container","scrollbar-measure","icon-success","icon-warning","icon-info","icon-question","icon-error"]),Z=t(["success","warning","info","question","error"]),Q=function(){return document.body.querySelector(".".concat(Y.container))},$=function(){return e(Y.popup)},J=function(){return document.body.classList.contains(Y["toast-shown"])},X={previousBodyPadding:null};function G(t,e){if(!e)return null;switch(e){case"select":case"textarea":case"file":return gt(t,Y[e]);case"checkbox":return t.querySelector(".".concat(Y.checkbox," input"));case"radio":return t.querySelector(".".concat(Y.radio," input:checked"))||t.querySelector(".".concat(Y.radio," input:first-child"));case"range":return t.querySelector(".".concat(Y.range," input"));default:return gt(t,Y.input)}}function tt(t){var e;t.focus(),"file"!==t.type&&(e=t.value,t.value="",t.value=e)}function et(t,e,n){t&&e&&("string"==typeof e&&(e=e.split(/\s+/).filter(Boolean)),e.forEach(function(e){t.forEach?t.forEach(function(t){n?t.classList.add(e):t.classList.remove(e)}):n?t.classList.add(e):t.classList.remove(e)}))}function nt(t,e,n){n||0===parseInt(n)?t.style[e]="number"==typeof n?"".concat(n,"px"):n:t.style.removeProperty(e)}function ot(t,e){var n=1<arguments.length&&void 0!==e?e:"flex";t.style.opacity="",t.style.display=n}function it(t){t.style.opacity="",t.style.display="none"}function rt(t,e,n){e?ot(t,n):it(t)}function at(t){return!!(t.scrollHeight>t.clientHeight)}function ct(t){var e=window.getComputedStyle(t),n=parseFloat(e.getPropertyValue("animation-duration")||"0"),o=parseFloat(e.getPropertyValue("transition-duration")||"0");return 0<n||0<o}function st(t,e){var n=1<arguments.length&&void 0!==e&&e,o=q();vt(o)&&(n&&(o.style.transition="none",o.style.width="100%"),setTimeout(function(){o.style.transition="width ".concat(t/1e3,"s linear"),o.style.width="0%"},10))}function ut(){return"undefined"==typeof window||"undefined"==typeof document}function lt(t){sn.isVisible()&&ft!==t.target.value&&sn.resetValidationMessage(),ft=t.target.value}function dt(t,e){t instanceof HTMLElement?e.appendChild(t):"object"===r(t)?wt(t,e):t&&H(e,t)}function pt(t,e){var n=T(),o=E(),i=O();e.showConfirmButton||e.showCancelButton||it(n),N(n,e,"actions"),xt(o,"confirm",e),xt(i,"cancel",e),e.buttonsStyling?function(t,e,n){mt([t,e],Y.styled),n.confirmButtonColor&&(t.style.backgroundColor=n.confirmButtonColor);n.cancelButtonColor&&(e.style.backgroundColor=n.cancelButtonColor);{var o;R()||(o=window.getComputedStyle(t).getPropertyValue("background-color"),t.style.borderLeftColor=o,t.style.borderRightColor=o)}}(o,i,e):(ht([o,i],Y.styled),o.style.backgroundColor=o.style.borderLeftColor=o.style.borderRightColor="",i.style.backgroundColor=i.style.borderLeftColor=i.style.borderRightColor=""),e.reverseButtons&&o.parentNode.insertBefore(i,o)}var ft,mt=function(t,e){et(t,e,!0)},ht=function(t,e){et(t,e,!1)},gt=function(t,e){for(var n=0;n<t.childNodes.length;n++)if(D(t.childNodes[n],e))return t.childNodes[n]},vt=function(t){return!(!t||!(t.offsetWidth||t.offsetHeight||t.getClientRects().length))},bt='\n <div aria-labelledby="'.concat(Y.title,'" aria-describedby="').concat(Y.content,'" class="').concat(Y.popup,'" tabindex="-1">\n   <div class="').concat(Y.header,'">\n     <ul class="').concat(Y["progress-steps"],'"></ul>\n     <div class="').concat(Y.icon," ").concat(Z.error,'"></div>\n     <div class="').concat(Y.icon," ").concat(Z.question,'"></div>\n     <div class="').concat(Y.icon," ").concat(Z.warning,'"></div>\n     <div class="').concat(Y.icon," ").concat(Z.info,'"></div>\n     <div class="').concat(Y.icon," ").concat(Z.success,'"></div>\n     <img class="').concat(Y.image,'" />\n     <h2 class="').concat(Y.title,'" id="').concat(Y.title,'"></h2>\n     <button type="button" class="').concat(Y.close,'"></button>\n   </div>\n   <div class="').concat(Y.content,'">\n     <div id="').concat(Y.content,'" class="').concat(Y["html-container"],'"></div>\n     <input class="').concat(Y.input,'" />\n     <input type="file" class="').concat(Y.file,'" />\n     <div class="').concat(Y.range,'">\n       <input type="range" />\n       <output></output>\n     </div>\n     <select class="').concat(Y.select,'"></select>\n     <div class="').concat(Y.radio,'"></div>\n     <label for="').concat(Y.checkbox,'" class="').concat(Y.checkbox,'">\n       <input type="checkbox" />\n       <span class="').concat(Y.label,'"></span>\n     </label>\n     <textarea class="').concat(Y.textarea,'"></textarea>\n     <div class="').concat(Y["validation-message"],'" id="').concat(Y["validation-message"],'"></div>\n   </div>\n   <div class="').concat(Y.actions,'">\n     <button type="button" class="').concat(Y.confirm,'">OK</button>\n     <button type="button" class="').concat(Y.cancel,'">Cancel</button>\n   </div>\n   <div class="').concat(Y.footer,'"></div>\n   <div class="').concat(Y["timer-progress-bar-container"],'">\n     <div class="').concat(Y["timer-progress-bar"],'"></div>\n   </div>\n </div>\n').replace(/(^|\n)\s*/g,""),yt=function(t){var e,n,o,i,r,a,c,s,u,l,d,p,f,m,h,g=!!(e=Q())&&(e.parentNode.removeChild(e),ht([document.documentElement,document.body],[Y["no-backdrop"],Y["toast-shown"],Y["has-column"]]),!0);ut()?F("SweetAlert2 requires document to initialize"):((n=document.createElement("div")).className=Y.container,g&&mt(n,Y["no-transition"]),H(n,bt),(o="string"==typeof(i=t.target)?document.querySelector(i):i).appendChild(n),r=t,(a=$()).setAttribute("role",r.toast?"alert":"dialog"),a.setAttribute("aria-live",r.toast?"polite":"assertive"),r.toast||a.setAttribute("aria-modal","true"),c=o,"rtl"===window.getComputedStyle(c).direction&&mt(Q(),Y.rtl),s=P(),u=gt(s,Y.input),l=gt(s,Y.file),d=s.querySelector(".".concat(Y.range," input")),p=s.querySelector(".".concat(Y.range," output")),f=gt(s,Y.select),m=s.querySelector(".".concat(Y.checkbox," input")),h=gt(s,Y.textarea),u.oninput=lt,l.onchange=lt,f.onchange=lt,m.onchange=lt,h.oninput=lt,d.oninput=function(t){lt(t),p.value=d.value},d.onchange=function(t){lt(t),d.nextSibling.value=d.value})},wt=function(t,e){t.jquery?Ct(e,t):H(e,t.toString())},Ct=function(t,e){if(t.textContent="",0 in e)for(var n=0;n in e;n++)t.appendChild(e[n].cloneNode(!0));else t.appendChild(e.cloneNode(!0))},kt=function(){if(ut())return!1;var t=document.createElement("div"),e={WebkitAnimation:"webkitAnimationEnd",OAnimation:"oAnimationEnd oanimationend",animation:"animationend"};for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)&&void 0!==t.style[n])return e[n];return!1}();function xt(t,e,n){var o;rt(t,n["show".concat((o=e).charAt(0).toUpperCase()+o.slice(1),"Button")],"inline-block"),H(t,n["".concat(e,"ButtonText")]),t.setAttribute("aria-label",n["".concat(e,"ButtonAriaLabel")]),t.className=Y[e],N(t,n,"".concat(e,"Button")),mt(t,n["".concat(e,"ButtonClass")])}function Pt(t,e){var n,o,i,r,a,c,s,u,l=Q();l&&(n=l,"string"==typeof(o=e.backdrop)?n.style.background=o:o||mt([document.documentElement,document.body],Y["no-backdrop"]),!e.backdrop&&e.allowOutsideClick&&_('"allowOutsideClick" parameter requires `backdrop` parameter to be set to `true`'),i=l,(r=e.position)in Y?mt(i,Y[r]):(_('The "position" parameter is not valid, defaulting to "center"'),mt(i,Y.center)),a=l,!(c=e.grow)||"string"!=typeof c||(s="grow-".concat(c))in Y&&mt(a,Y[s]),N(l,e,"container"),(u=document.body.getAttribute("data-swal2-queue-step"))&&(l.setAttribute("data-queue-step",u),document.body.removeAttribute("data-swal2-queue-step")))}function At(t,e){t.placeholder&&!e.inputPlaceholder||(t.placeholder=e.inputPlaceholder)}var Bt={promise:new WeakMap,innerParams:new WeakMap,domCache:new WeakMap},St=["input","file","range","select","radio","checkbox","textarea"],Et=function(t){if(!jt[t.input])return F('Unexpected type of input! Expected "text", "email", "password", "number", "tel", "select", "radio", "checkbox", "textarea", "file" or "url", got "'.concat(t.input,'"'));var e=Lt(t.input),n=jt[t.input](e,t);ot(n),setTimeout(function(){tt(n)})},Ot=function(t,e){var n=G(P(),t);if(n)for(var o in!function(t){for(var e=0;e<t.attributes.length;e++){var n=t.attributes[e].name;-1===["type","value","style"].indexOf(n)&&t.removeAttribute(n)}}(n),e)"range"===t&&"placeholder"===o||n.setAttribute(o,e[o])},Tt=function(t){var e=Lt(t.input);t.customClass&&mt(e,t.customClass.input)},Lt=function(t){var e=Y[t]?Y[t]:Y.input;return gt(P(),e)},jt={};jt.text=jt.email=jt.password=jt.number=jt.tel=jt.url=function(t,e){return"string"==typeof e.inputValue||"number"==typeof e.inputValue?t.value=e.inputValue:y(e.inputValue)||_('Unexpected type of inputValue! Expected "string", "number" or "Promise", got "'.concat(r(e.inputValue),'"')),At(t,e),t.type=e.input,t},jt.file=function(t,e){return At(t,e),t},jt.range=function(t,e){var n=t.querySelector("input"),o=t.querySelector("output");return n.value=e.inputValue,n.type=e.input,o.value=e.inputValue,t},jt.select=function(t,e){var n;return t.textContent="",e.inputPlaceholder&&(n=document.createElement("option"),H(n,e.inputPlaceholder),n.value="",n.disabled=!0,n.selected=!0,t.appendChild(n)),t},jt.radio=function(t){return t.textContent="",t},jt.checkbox=function(t,e){var n=G(P(),"checkbox");n.value=1,n.id=Y.checkbox,n.checked=Boolean(e.inputValue);var o=t.querySelector("span");return H(o,e.inputPlaceholder),t},jt.textarea=function(e,t){var n,o;return e.value=t.inputValue,At(e,t),"MutationObserver"in window&&(n=parseInt(window.getComputedStyle($()).width),o=parseInt(window.getComputedStyle($()).paddingLeft)+parseInt(window.getComputedStyle($()).paddingRight),new MutationObserver(function(){var t=e.offsetWidth+o;$().style.width=n<t?"".concat(t,"px"):null}).observe(e,{attributes:!0,attributeFilter:["style"]})),e};function qt(t,e){var n,o,i,r,a,c=P().querySelector("#".concat(Y.content));e.html?(dt(e.html,c),ot(c,"block")):e.text?(c.textContent=e.text,ot(c,"block")):it(c),n=t,o=e,i=P(),r=Bt.innerParams.get(n),a=!r||o.input!==r.input,St.forEach(function(t){var e=Y[t],n=gt(i,e);Ot(t,o.inputAttributes),n.className=e,a&&it(n)}),o.input&&(a&&Et(o),Tt(o)),N(P(),e,"content")}function It(){return Q()&&Q().getAttribute("data-queue-step")}function Vt(t,s){var u=B();if(!s.progressSteps||0===s.progressSteps.length)return it(u),0;ot(u),u.textContent="";var l=parseInt(void 0===s.currentProgressStep?It():s.currentProgressStep);l>=s.progressSteps.length&&_("Invalid currentProgressStep parameter, it should be less than progressSteps.length (currentProgressStep like JS arrays starts from 0)"),s.progressSteps.forEach(function(t,e){var n,o,i,r,a,c=(n=t,o=document.createElement("li"),mt(o,Y["progress-step"]),H(o,n),o);u.appendChild(c),e===l&&mt(c,Y["active-progress-step"]),e!==s.progressSteps.length-1&&(r=s,a=document.createElement("li"),mt(a,Y["progress-step-line"]),r.progressStepsDistance&&(a.style.width=r.progressStepsDistance),i=a,u.appendChild(i))})}function Mt(t,e){var n,o,i,r,a,c,s,u,l=L();N(l,e,"header"),Vt(0,e),n=t,o=e,(r=Bt.innerParams.get(n))&&o.icon===r.icon&&k()?N(k(),o,"icon"):(Dt(),o.icon&&(-1!==Object.keys(Z).indexOf(o.icon)?(i=C(".".concat(Y.icon,".").concat(Z[o.icon])),ot(i),Ut(i,o),Nt(),N(i,o,"icon"),mt(i,o.showClass.icon)):F('Unknown icon! Expected "success", "error", "warning", "info" or "question", got "'.concat(o.icon,'"')))),function(t){var e=A();if(!t.imageUrl)return it(e);ot(e,""),e.setAttribute("src",t.imageUrl),e.setAttribute("alt",t.imageAlt),nt(e,"width",t.imageWidth),nt(e,"height",t.imageHeight),e.className=Y.image,N(e,t,"image")}(e),a=e,c=x(),rt(c,a.title||a.titleText),a.title&&dt(a.title,c),a.titleText&&(c.innerText=a.titleText),N(c,a,"title"),s=e,u=I(),H(u,s.closeButtonHtml),N(u,s,"closeButton"),rt(u,s.showCloseButton),u.setAttribute("aria-label",s.closeButtonAriaLabel)}function Rt(t,e){var n,o,i,r;n=e,o=$(),nt(o,"width",n.width),nt(o,"padding",n.padding),n.background&&(o.style.background=n.background),zt(o,n),Pt(0,e),Mt(t,e),qt(t,e),pt(0,e),i=e,r=j(),rt(r,i.footer),i.footer&&dt(i.footer,r),N(r,i,"footer"),"function"==typeof e.onRender&&e.onRender($())}function Ht(){return E()&&E().click()}var Dt=function(){for(var t=n(),e=0;e<t.length;e++)it(t[e])},Nt=function(){for(var t=$(),e=window.getComputedStyle(t).getPropertyValue("background-color"),n=t.querySelectorAll("[class^=swal2-success-circular-line], .swal2-success-fix"),o=0;o<n.length;o++)n[o].style.backgroundColor=e},Ut=function(t,e){t.textContent="",e.iconHtml?H(t,_t(e.iconHtml)):"success"===e.icon?H(t,'\n      <div class="swal2-success-circular-line-left"></div>\n      <span class="swal2-success-line-tip"></span> <span class="swal2-success-line-long"></span>\n      <div class="swal2-success-ring"></div> <div class="swal2-success-fix"></div>\n      <div class="swal2-success-circular-line-right"></div>\n    '):"error"===e.icon?H(t,'\n      <span class="swal2-x-mark">\n        <span class="swal2-x-mark-line-left"></span>\n        <span class="swal2-x-mark-line-right"></span>\n      </span>\n    '):H(t,_t({question:"?",warning:"!",info:"i"}[e.icon]))},_t=function(t){return'<div class="'.concat(Y["icon-content"],'">').concat(t,"</div>")},Ft=[],zt=function(t,e){t.className="".concat(Y.popup," ").concat(vt(t)?e.showClass.popup:""),e.toast?(mt([document.documentElement,document.body],Y["toast-shown"]),mt(t,Y.toast)):mt(t,Y.modal),N(t,e,"popup"),"string"==typeof e.customClass&&mt(t,e.customClass),e.icon&&mt(t,Y["icon-".concat(e.icon)])};function Wt(){var t=$();t||sn.fire(),t=$();var e=T(),n=E();ot(e),ot(n,"inline-block"),mt([t,e],Y.loading),n.disabled=!0,t.setAttribute("data-loading",!0),t.setAttribute("aria-busy",!0),t.focus()}function Kt(){return new Promise(function(t){var e=window.scrollX,n=window.scrollY;Xt.restoreFocusTimeout=setTimeout(function(){Xt.previousActiveElement&&Xt.previousActiveElement.focus?(Xt.previousActiveElement.focus(),Xt.previousActiveElement=null):document.body&&document.body.focus(),t()},100),void 0!==e&&void 0!==n&&window.scrollTo(e,n)})}function Yt(){if(Xt.timeout)return function(){var t=q(),e=parseInt(window.getComputedStyle(t).width);t.style.removeProperty("transition"),t.style.width="100%";var n=parseInt(window.getComputedStyle(t).width),o=parseInt(e/n*100);t.style.removeProperty("transition"),t.style.width="".concat(o,"%")}(),Xt.timeout.stop()}function Zt(){if(Xt.timeout){var t=Xt.timeout.start();return st(t),t}}function Qt(t){return Object.prototype.hasOwnProperty.call(Gt,t)}function $t(t){return ee[t]}function Jt(t){for(var e in t)Qt(i=e)||_('Unknown parameter "'.concat(i,'"')),t.toast&&(o=e,-1!==ne.indexOf(o)&&_('The parameter "'.concat(o,'" is incompatible with toasts'))),$t(n=e)&&g(n,$t(n));var n,o,i}var Xt={},Gt={title:"",titleText:"",text:"",html:"",footer:"",icon:void 0,iconHtml:void 0,toast:!1,animation:!0,showClass:{popup:"swal2-show",backdrop:"swal2-backdrop-show",icon:"swal2-icon-show"},hideClass:{popup:"swal2-hide",backdrop:"swal2-backdrop-hide",icon:"swal2-icon-hide"},customClass:void 0,target:"body",backdrop:!0,heightAuto:!0,allowOutsideClick:!0,allowEscapeKey:!0,allowEnterKey:!0,stopKeydownPropagation:!0,keydownListenerCapture:!1,showConfirmButton:!0,showCancelButton:!1,preConfirm:void 0,confirmButtonText:"OK",confirmButtonAriaLabel:"",confirmButtonColor:void 0,cancelButtonText:"Cancel",cancelButtonAriaLabel:"",cancelButtonColor:void 0,buttonsStyling:!0,reverseButtons:!1,focusConfirm:!0,focusCancel:!1,showCloseButton:!1,closeButtonHtml:"&times;",closeButtonAriaLabel:"Close this dialog",showLoaderOnConfirm:!1,imageUrl:void 0,imageWidth:void 0,imageHeight:void 0,imageAlt:"",timer:void 0,timerProgressBar:!1,width:void 0,padding:void 0,background:void 0,input:void 0,inputPlaceholder:"",inputValue:"",inputOptions:{},inputAutoTrim:!0,inputAttributes:{},inputValidator:void 0,validationMessage:void 0,grow:!1,position:"center",progressSteps:[],currentProgressStep:void 0,progressStepsDistance:void 0,onBeforeOpen:void 0,onOpen:void 0,onRender:void 0,onClose:void 0,onAfterClose:void 0,onDestroy:void 0,scrollbarPadding:!0},te=["allowEscapeKey","allowOutsideClick","buttonsStyling","cancelButtonAriaLabel","cancelButtonColor","cancelButtonText","closeButtonAriaLabel","closeButtonHtml","confirmButtonAriaLabel","confirmButtonColor","confirmButtonText","currentProgressStep","customClass","footer","hideClass","html","icon","imageAlt","imageHeight","imageUrl","imageWidth","onAfterClose","onClose","onDestroy","progressSteps","reverseButtons","showCancelButton","showCloseButton","showConfirmButton","text","title","titleText"],ee={animation:'showClass" and "hideClass'},ne=["allowOutsideClick","allowEnterKey","backdrop","focusConfirm","focusCancel","heightAuto","keydownListenerCapture"],oe=Object.freeze({isValidParameter:Qt,isUpdatableParameter:function(t){return-1!==te.indexOf(t)},isDeprecatedParameter:$t,argsToParams:function(o){var i={};return"object"!==r(o[0])||w(o[0])?["title","html","icon"].forEach(function(t,e){var n=o[e];"string"==typeof n||w(n)?i[t]=n:void 0!==n&&F("Unexpected type of ".concat(t,'! Expected "string" or "Element", got ').concat(r(n)))}):s(i,o[0]),i},isVisible:function(){return vt($())},clickConfirm:Ht,clickCancel:function(){return O()&&O().click()},getContainer:Q,getPopup:$,getTitle:x,getContent:P,getHtmlContainer:function(){return e(Y["html-container"])},getImage:A,getIcon:k,getIcons:n,getCloseButton:I,getActions:T,getConfirmButton:E,getCancelButton:O,getHeader:L,getFooter:j,getTimerProgressBar:q,getFocusableElements:V,getValidationMessage:S,isLoading:R,fire:function(){for(var t=arguments.length,e=new Array(t),n=0;n<t;n++)e[n]=arguments[n];return i(this,e)},mixin:function(r){return function(t){!function(t,e){if("function"!=typeof e&&null!==e)throw new TypeError("Super expression must either be null or a function");t.prototype=Object.create(e&&e.prototype,{constructor:{value:t,writable:!0,configurable:!0}}),e&&l(t,e)}(i,t);var n,o,e=(n=i,o=d(),function(){var t,e=u(n);return p(this,o?(t=u(this).constructor,Reflect.construct(e,arguments,t)):e.apply(this,arguments))});function i(){return a(this,i),e.apply(this,arguments)}return c(i,[{key:"_main",value:function(t){return f(u(i.prototype),"_main",this).call(this,s({},r,t))}}]),i}(this)},queue:function(t){var r=this;Ft=t;function a(t,e){Ft=[],t(e)}var c=[];return new Promise(function(i){!function e(n,o){n<Ft.length?(document.body.setAttribute("data-swal2-queue-step",n),r.fire(Ft[n]).then(function(t){void 0!==t.value?(c.push(t.value),e(n+1,o)):a(i,{dismiss:t.dismiss})})):a(i,{value:c})}(0)})},getQueueStep:It,insertQueueStep:function(t,e){return e&&e<Ft.length?Ft.splice(e,0,t):Ft.push(t)},deleteQueueStep:function(t){void 0!==Ft[t]&&Ft.splice(t,1)},showLoading:Wt,enableLoading:Wt,getTimerLeft:function(){return Xt.timeout&&Xt.timeout.getTimerLeft()},stopTimer:Yt,resumeTimer:Zt,toggleTimer:function(){var t=Xt.timeout;return t&&(t.running?Yt:Zt)()},increaseTimer:function(t){if(Xt.timeout){var e=Xt.timeout.increase(t);return st(e,!0),e}},isTimerRunning:function(){return Xt.timeout&&Xt.timeout.isRunning()}});function ie(){var t,e=Bt.innerParams.get(this);e&&(t=Bt.domCache.get(this),e.showConfirmButton||(it(t.confirmButton),e.showCancelButton||it(t.actions)),ht([t.popup,t.actions],Y.loading),t.popup.removeAttribute("aria-busy"),t.popup.removeAttribute("data-loading"),t.confirmButton.disabled=!1,t.cancelButton.disabled=!1)}function re(){null===X.previousBodyPadding&&document.body.scrollHeight>window.innerHeight&&(X.previousBodyPadding=parseInt(window.getComputedStyle(document.body).getPropertyValue("padding-right")),document.body.style.paddingRight="".concat(X.previousBodyPadding+function(){var t=document.createElement("div");t.className=Y["scrollbar-measure"],document.body.appendChild(t);var e=t.getBoundingClientRect().width-t.clientWidth;return document.body.removeChild(t),e}(),"px"))}function ae(){return!!window.MSInputMethodContext&&!!document.documentMode}function ce(){var t=Q(),e=$();t.style.removeProperty("align-items"),e.offsetTop<0&&(t.style.alignItems="flex-start")}var se=function(){navigator.userAgent.match(/(CriOS|FxiOS|EdgiOS|YaBrowser|UCBrowser)/i)||$().scrollHeight>window.innerHeight-44&&(Q().style.paddingBottom="".concat(44,"px"))},ue=function(){var e,t=Q();t.ontouchstart=function(t){e=le(t.target)},t.ontouchmove=function(t){e&&(t.preventDefault(),t.stopPropagation())}},le=function(t){var e=Q();return t===e||!(at(e)||"INPUT"===t.tagName||at(P())&&P().contains(t))},de={swalPromiseResolve:new WeakMap};function pe(t,e,n,o){var i;n?he(t,o):(Kt().then(function(){return he(t,o)}),Xt.keydownTarget.removeEventListener("keydown",Xt.keydownHandler,{capture:Xt.keydownListenerCapture}),Xt.keydownHandlerAdded=!1),e.parentNode&&!document.body.getAttribute("data-swal2-queue-step")&&e.parentNode.removeChild(e),M()&&(null!==X.previousBodyPadding&&(document.body.style.paddingRight="".concat(X.previousBodyPadding,"px"),X.previousBodyPadding=null),D(document.body,Y.iosfix)&&(i=parseInt(document.body.style.top,10),ht(document.body,Y.iosfix),document.body.style.top="",document.body.scrollTop=-1*i),"undefined"!=typeof window&&ae()&&window.removeEventListener("resize",ce),h(document.body.children).forEach(function(t){t.hasAttribute("data-previous-aria-hidden")?(t.setAttribute("aria-hidden",t.getAttribute("data-previous-aria-hidden")),t.removeAttribute("data-previous-aria-hidden")):t.removeAttribute("aria-hidden")})),ht([document.documentElement,document.body],[Y.shown,Y["height-auto"],Y["no-backdrop"],Y["toast-shown"],Y["toast-column"]])}function fe(t){var e,n,o,i=$();i&&(e=Bt.innerParams.get(this))&&!D(i,e.hideClass.popup)&&(n=de.swalPromiseResolve.get(this),ht(i,e.showClass.popup),mt(i,e.hideClass.popup),o=Q(),ht(o,e.showClass.backdrop),mt(o,e.hideClass.backdrop),function(t,e,n){var o=Q(),i=kt&&ct(e),r=n.onClose,a=n.onAfterClose;if(r!==null&&typeof r==="function"){r(e)}if(i){me(t,e,o,a)}else{pe(t,o,J(),a)}}(this,i,e),void 0!==t?(t.isDismissed=void 0!==t.dismiss,t.isConfirmed=void 0===t.dismiss):t={isDismissed:!0,isConfirmed:!1},n(t||{}))}var me=function(t,e,n,o){Xt.swalCloseEventFinishedCallback=pe.bind(null,t,n,J(),o),e.addEventListener(kt,function(t){t.target===e&&(Xt.swalCloseEventFinishedCallback(),delete Xt.swalCloseEventFinishedCallback)})},he=function(t,e){setTimeout(function(){"function"==typeof e&&e(),t._destroy()})};function ge(t,e,n){var o=Bt.domCache.get(t);e.forEach(function(t){o[t].disabled=n})}function ve(t,e){if(!t)return!1;if("radio"===t.type)for(var n=t.parentNode.parentNode.querySelectorAll("input"),o=0;o<n.length;o++)n[o].disabled=e;else t.disabled=e}var be=function(){function n(t,e){a(this,n),this.callback=t,this.remaining=e,this.running=!1,this.start()}return c(n,[{key:"start",value:function(){return this.running||(this.running=!0,this.started=new Date,this.id=setTimeout(this.callback,this.remaining)),this.remaining}},{key:"stop",value:function(){return this.running&&(this.running=!1,clearTimeout(this.id),this.remaining-=new Date-this.started),this.remaining}},{key:"increase",value:function(t){var e=this.running;return e&&this.stop(),this.remaining+=t,e&&this.start(),this.remaining}},{key:"getTimerLeft",value:function(){return this.running&&(this.stop(),this.start()),this.remaining}},{key:"isRunning",value:function(){return this.running}}]),n}(),ye={email:function(t,e){return/^[a-zA-Z0-9.+_-]+@[a-zA-Z0-9.-]+\.[a-zA-Z0-9-]{2,24}$/.test(t)?Promise.resolve():Promise.resolve(e||"Invalid email address")},url:function(t,e){return/^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-z]{2,63}\b([-a-zA-Z0-9@:%_+.~#?&/=]*)$/.test(t)?Promise.resolve():Promise.resolve(e||"Invalid URL")}};function we(t){var e,n;(e=t).inputValidator||Object.keys(ye).forEach(function(t){e.input===t&&(e.inputValidator=ye[t])}),t.showLoaderOnConfirm&&!t.preConfirm&&_("showLoaderOnConfirm is set to true, but preConfirm is not defined.\nshowLoaderOnConfirm should be used together with preConfirm, see usage example:\nhttps://sweetalert2.github.io/#ajax-request"),t.animation=W(t.animation),(n=t).target&&("string"!=typeof n.target||document.querySelector(n.target))&&("string"==typeof n.target||n.target.appendChild)||(_('Target parameter is not valid, defaulting to "body"'),n.target="body"),"string"==typeof t.title&&(t.title=t.title.split("\n").join("<br />")),yt(t)}function Ce(t){var e=Q(),n=$();"function"==typeof t.onBeforeOpen&&t.onBeforeOpen(n);var o=window.getComputedStyle(document.body).overflowY;je(e,n,t),Te(e,n),M()&&(Le(e,t.scrollbarPadding,o),h(document.body.children).forEach(function(t){t===Q()||function(t,e){if("function"==typeof t.contains)return t.contains(e)}(t,Q())||(t.hasAttribute("aria-hidden")&&t.setAttribute("data-previous-aria-hidden",t.getAttribute("aria-hidden")),t.setAttribute("aria-hidden","true"))})),J()||Xt.previousActiveElement||(Xt.previousActiveElement=document.activeElement),"function"==typeof t.onOpen&&setTimeout(function(){return t.onOpen(n)}),ht(e,Y["no-transition"])}function ke(t){var e,n=$();t.target===n&&(e=Q(),n.removeEventListener(kt,ke),e.style.overflowY="auto")}function xe(t,e){"select"===e.input||"radio"===e.input?Me(t,e):-1!==["text","email","number","tel","textarea"].indexOf(e.input)&&(v(e.inputValue)||y(e.inputValue))&&Re(t,e)}function Pe(t,e){t.disableButtons(),e.input?Ne(t,e):Ue(t,e,!0)}function Ae(t,e){t.disableButtons(),e(K.cancel)}function Be(t,e){t.closePopup({value:e})}function Se(e,t,n,o){t.keydownTarget&&t.keydownHandlerAdded&&(t.keydownTarget.removeEventListener("keydown",t.keydownHandler,{capture:t.keydownListenerCapture}),t.keydownHandlerAdded=!1),n.toast||(t.keydownHandler=function(t){return ze(e,t,o)},t.keydownTarget=n.keydownListenerCapture?window:$(),t.keydownListenerCapture=n.keydownListenerCapture,t.keydownTarget.addEventListener("keydown",t.keydownHandler,{capture:t.keydownListenerCapture}),t.keydownHandlerAdded=!0)}function Ee(t,e,n){var o=V(),i=0;if(i<o.length)return(e+=n)===o.length?e=0:-1===e&&(e=o.length-1),o[e].focus();$().focus()}function Oe(t,e,n){Bt.innerParams.get(t).toast?Qe(t,e,n):(Je(e),Xe(e),Ge(t,e,n))}var Te=function(t,e){kt&&ct(e)?(t.style.overflowY="hidden",e.addEventListener(kt,ke)):t.style.overflowY="auto"},Le=function(t,e,n){var o;(/iPad|iPhone|iPod/.test(navigator.userAgent)&&!window.MSStream||"MacIntel"===navigator.platform&&1<navigator.maxTouchPoints)&&!D(document.body,Y.iosfix)&&(o=document.body.scrollTop,document.body.style.top="".concat(-1*o,"px"),mt(document.body,Y.iosfix),ue(),se()),"undefined"!=typeof window&&ae()&&(ce(),window.addEventListener("resize",ce)),e&&"hidden"!==n&&re(),setTimeout(function(){t.scrollTop=0})},je=function(t,e,n){mt(t,n.showClass.backdrop),ot(e),mt(e,n.showClass.popup),mt([document.documentElement,document.body],Y.shown),n.heightAuto&&n.backdrop&&!n.toast&&mt([document.documentElement,document.body],Y["height-auto"])},qe=function(t){return t.checked?1:0},Ie=function(t){return t.checked?t.value:null},Ve=function(t){return t.files.length?null!==t.getAttribute("multiple")?t.files:t.files[0]:null},Me=function(e,n){function o(t){return He[n.input](i,De(t),n)}var i=P();v(n.inputOptions)||y(n.inputOptions)?(Wt(),b(n.inputOptions).then(function(t){e.hideLoading(),o(t)})):"object"===r(n.inputOptions)?o(n.inputOptions):F("Unexpected type of inputOptions! Expected object, Map or Promise, got ".concat(r(n.inputOptions)))},Re=function(e,n){var o=e.getInput();it(o),b(n.inputValue).then(function(t){o.value="number"===n.input?parseFloat(t)||0:"".concat(t),ot(o),o.focus(),e.hideLoading()}).catch(function(t){F("Error in inputValue promise: ".concat(t)),o.value="",ot(o),o.focus(),e.hideLoading()})},He={select:function(t,e,i){function r(t,e,n){var o=document.createElement("option");o.value=n,H(o,e),i.inputValue.toString()===n.toString()&&(o.selected=!0),t.appendChild(o)}var a=gt(t,Y.select);e.forEach(function(t){var e,n=t[0],o=t[1];Array.isArray(o)?((e=document.createElement("optgroup")).label=n,e.disabled=!1,a.appendChild(e),o.forEach(function(t){return r(e,t[1],t[0])})):r(a,o,n)}),a.focus()},radio:function(t,e,a){var c=gt(t,Y.radio);e.forEach(function(t){var e=t[0],n=t[1],o=document.createElement("input"),i=document.createElement("label");o.type="radio",o.name=Y.radio,o.value=e,a.inputValue.toString()===e.toString()&&(o.checked=!0);var r=document.createElement("span");H(r,n),r.className=Y.label,i.appendChild(o),i.appendChild(r),c.appendChild(i)});var n=c.querySelectorAll("input");n.length&&n[0].focus()}},De=function o(n){var i=[];return"undefined"!=typeof Map&&n instanceof Map?n.forEach(function(t,e){var n=t;"object"===r(n)&&(n=o(n)),i.push([e,n])}):Object.keys(n).forEach(function(t){var e=n[t];"object"===r(e)&&(e=o(e)),i.push([t,e])}),i},Ne=function(e,n){var o=function(t,e){var n=t.getInput();if(!n)return null;switch(e.input){case"checkbox":return qe(n);case"radio":return Ie(n);case"file":return Ve(n);default:return e.inputAutoTrim?n.value.trim():n.value}}(e,n);n.inputValidator?(e.disableInput(),Promise.resolve().then(function(){return b(n.inputValidator(o,n.validationMessage))}).then(function(t){e.enableButtons(),e.enableInput(),t?e.showValidationMessage(t):Ue(e,n,o)})):e.getInput().checkValidity()?Ue(e,n,o):(e.enableButtons(),e.showValidationMessage(n.validationMessage))},Ue=function(e,t,n){t.showLoaderOnConfirm&&Wt(),t.preConfirm?(e.resetValidationMessage(),Promise.resolve().then(function(){return b(t.preConfirm(n,t.validationMessage))}).then(function(t){vt(S())||!1===t?e.hideLoading():Be(e,void 0===t?n:t)})):Be(e,n)},_e=["ArrowLeft","ArrowRight","ArrowUp","ArrowDown","Left","Right","Up","Down"],Fe=["Escape","Esc"],ze=function(t,e,n){var o=Bt.innerParams.get(t);o.stopKeydownPropagation&&e.stopPropagation(),"Enter"===e.key?We(t,e,o):"Tab"===e.key?Ke(e,o):-1!==_e.indexOf(e.key)?Ye():-1!==Fe.indexOf(e.key)&&Ze(e,o,n)},We=function(t,e,n){if(!e.isComposing&&e.target&&t.getInput()&&e.target.outerHTML===t.getInput().outerHTML){if(-1!==["textarea","file"].indexOf(n.input))return;Ht(),e.preventDefault()}},Ke=function(t){for(var e=t.target,n=V(),o=-1,i=0;i<n.length;i++)if(e===n[i]){o=i;break}t.shiftKey?Ee(0,o,-1):Ee(0,o,1),t.stopPropagation(),t.preventDefault()},Ye=function(){var t=E(),e=O();document.activeElement===t&&vt(e)?e.focus():document.activeElement===e&&vt(t)&&t.focus()},Ze=function(t,e,n){W(e.allowEscapeKey)&&(t.preventDefault(),n(K.esc))},Qe=function(e,t,n){t.popup.onclick=function(){var t=Bt.innerParams.get(e);t.showConfirmButton||t.showCancelButton||t.showCloseButton||t.input||n(K.close)}},$e=!1,Je=function(e){e.popup.onmousedown=function(){e.container.onmouseup=function(t){e.container.onmouseup=void 0,t.target===e.container&&($e=!0)}}},Xe=function(e){e.container.onmousedown=function(){e.popup.onmouseup=function(t){e.popup.onmouseup=void 0,t.target!==e.popup&&!e.popup.contains(t.target)||($e=!0)}}},Ge=function(n,o,i){o.container.onclick=function(t){var e=Bt.innerParams.get(n);$e?$e=!1:t.target===o.container&&W(e.allowOutsideClick)&&i(K.backdrop)}};var tn=function(t,e,n){var o=q();it(o),e.timer&&(t.timeout=new be(function(){n("timer"),delete t.timeout},e.timer),e.timerProgressBar&&(ot(o),setTimeout(function(){t.timeout.running&&st(e.timer)})))},en=function(t,e){if(!e.toast)return W(e.allowEnterKey)?e.focusCancel&&vt(t.cancelButton)?t.cancelButton.focus():e.focusConfirm&&vt(t.confirmButton)?t.confirmButton.focus():void Ee(0,-1,1):nn()},nn=function(){document.activeElement&&"function"==typeof document.activeElement.blur&&document.activeElement.blur()};var on,rn=function(t){for(var e in t)t[e]=new WeakMap},an=Object.freeze({hideLoading:ie,disableLoading:ie,getInput:function(t){var e=Bt.innerParams.get(t||this),n=Bt.domCache.get(t||this);return n?G(n.content,e.input):null},close:fe,closePopup:fe,closeModal:fe,closeToast:fe,enableButtons:function(){ge(this,["confirmButton","cancelButton"],!1)},disableButtons:function(){ge(this,["confirmButton","cancelButton"],!0)},enableInput:function(){return ve(this.getInput(),!1)},disableInput:function(){return ve(this.getInput(),!0)},showValidationMessage:function(t){var e=Bt.domCache.get(this);H(e.validationMessage,t);var n=window.getComputedStyle(e.popup);e.validationMessage.style.marginLeft="-".concat(n.getPropertyValue("padding-left")),e.validationMessage.style.marginRight="-".concat(n.getPropertyValue("padding-right")),ot(e.validationMessage);var o=this.getInput();o&&(o.setAttribute("aria-invalid",!0),o.setAttribute("aria-describedBy",Y["validation-message"]),tt(o),mt(o,Y.inputerror))},resetValidationMessage:function(){var t=Bt.domCache.get(this);t.validationMessage&&it(t.validationMessage);var e=this.getInput();e&&(e.removeAttribute("aria-invalid"),e.removeAttribute("aria-describedBy"),ht(e,Y.inputerror))},getProgressSteps:function(){return Bt.domCache.get(this).progressSteps},_main:function(t){Jt(t),Xt.currentInstance&&Xt.currentInstance._destroy(),Xt.currentInstance=this;var e=function(t){var e=s({},Gt.showClass,t.showClass),n=s({},Gt.hideClass,t.hideClass),o=s({},Gt,t);if(o.showClass=e,o.hideClass=n,t.animation===false){o.showClass={popup:"swal2-noanimation",backdrop:"swal2-noanimation"};o.hideClass={}}return o}(t);we(e),Object.freeze(e),Xt.timeout&&(Xt.timeout.stop(),delete Xt.timeout),clearTimeout(Xt.restoreFocusTimeout);var n=function(t){var e={popup:$(),container:Q(),content:P(),actions:T(),confirmButton:E(),cancelButton:O(),closeButton:I(),validationMessage:S(),progressSteps:B()};return Bt.domCache.set(t,e),e}(this);return Rt(this,e),Bt.innerParams.set(this,e),function(n,o,i){return new Promise(function(t){var e=function t(e){n.closePopup({dismiss:e})};de.swalPromiseResolve.set(n,t);o.confirmButton.onclick=function(){return Pe(n,i)};o.cancelButton.onclick=function(){return Ae(n,e)};o.closeButton.onclick=function(){return e(K.close)};Oe(n,o,e);Se(n,Xt,i,e);if(i.toast&&(i.input||i.footer||i.showCloseButton)){mt(document.body,Y["toast-column"])}else{ht(document.body,Y["toast-column"])}xe(n,i);Ce(i);tn(Xt,i,e);en(o,i);setTimeout(function(){o.container.scrollTop=0})})}(this,n,e)},update:function(e){var t=$(),n=Bt.innerParams.get(this);if(!t||D(t,n.hideClass.popup))return _("You're trying to update the closed or closing popup, that won't work. Use the update() method in preConfirm parameter or show a new popup.");var o={};Object.keys(e).forEach(function(t){sn.isUpdatableParameter(t)?o[t]=e[t]:_('Invalid parameter to update: "'.concat(t,'". Updatable params are listed here: https://github.com/sweetalert2/sweetalert2/blob/master/src/utils/params.js'))});var i=s({},n,o);Rt(this,i),Bt.innerParams.set(this,i),Object.defineProperties(this,{params:{value:s({},this.params,e),writable:!1,enumerable:!0}})},_destroy:function(){var t=Bt.domCache.get(this),e=Bt.innerParams.get(this);e&&(t.popup&&Xt.swalCloseEventFinishedCallback&&(Xt.swalCloseEventFinishedCallback(),delete Xt.swalCloseEventFinishedCallback),Xt.deferDisposalTimer&&(clearTimeout(Xt.deferDisposalTimer),delete Xt.deferDisposalTimer),"function"==typeof e.onDestroy&&e.onDestroy(),delete this.params,delete Xt.keydownHandler,delete Xt.keydownTarget,rn(Bt),rn(de))}}),cn=function(){function r(){if(a(this,r),"undefined"!=typeof window){"undefined"==typeof Promise&&F("This package requires a Promise library, please include a shim to enable it in this browser (See: https://github.com/sweetalert2/sweetalert2/wiki/Migration-from-SweetAlert-to-SweetAlert2#1-ie-support)"),on=this;for(var t=arguments.length,e=new Array(t),n=0;n<t;n++)e[n]=arguments[n];var o=Object.freeze(this.constructor.argsToParams(e));Object.defineProperties(this,{params:{value:o,writable:!1,enumerable:!0,configurable:!0}});var i=this._main(this.params);Bt.promise.set(this,i)}}return c(r,[{key:"then",value:function(t){return Bt.promise.get(this).then(t)}},{key:"finally",value:function(t){return Bt.promise.get(this).finally(t)}}]),r}();s(cn.prototype,an),s(cn,oe),Object.keys(an).forEach(function(t){cn[t]=function(){if(on)return on[t].apply(on,arguments)}}),cn.DismissReason=K,cn.version="9.17.2";var sn=cn;return sn.default=sn}),void 0!==this&&this.Sweetalert2&&(this.swal=this.sweetAlert=this.Swal=this.SweetAlert=this.Sweetalert2);
"undefined"!=typeof document&&function(e,t){var n=e.createElement("style");if(e.getElementsByTagName("head")[0].appendChild(n),n.styleSheet)n.styleSheet.disabled||(n.styleSheet.cssText=t);else try{n.innerHTML=t}catch(e){n.innerText=t}}(document,".swal2-popup.swal2-toast{flex-direction:row;align-items:center;width:auto;padding:.625em;overflow-y:hidden;background:#fff;box-shadow:0 0 .625em #d9d9d9}.swal2-popup.swal2-toast .swal2-header{flex-direction:row;padding:0}.swal2-popup.swal2-toast .swal2-title{flex-grow:1;justify-content:flex-start;margin:0 .6em;font-size:1em}.swal2-popup.swal2-toast .swal2-footer{margin:.5em 0 0;padding:.5em 0 0;font-size:.8em}.swal2-popup.swal2-toast .swal2-close{position:static;width:.8em;height:.8em;line-height:.8}.swal2-popup.swal2-toast .swal2-content{justify-content:flex-start;padding:0;font-size:1em}.swal2-popup.swal2-toast .swal2-icon{width:2em;min-width:2em;height:2em;margin:0}.swal2-popup.swal2-toast .swal2-icon .swal2-icon-content{display:flex;align-items:center;font-size:1.8em;font-weight:700}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-popup.swal2-toast .swal2-icon .swal2-icon-content{font-size:.25em}}.swal2-popup.swal2-toast .swal2-icon.swal2-success .swal2-success-ring{width:2em;height:2em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line]{top:.875em;width:1.375em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{left:.3125em}.swal2-popup.swal2-toast .swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{right:.3125em}.swal2-popup.swal2-toast .swal2-actions{flex-basis:auto!important;width:auto;height:auto;margin:0 .3125em}.swal2-popup.swal2-toast .swal2-styled{margin:0 .3125em;padding:.3125em .625em;font-size:1em}.swal2-popup.swal2-toast .swal2-styled:focus{box-shadow:0 0 0 1px #fff,0 0 0 3px rgba(50,100,150,.4)}.swal2-popup.swal2-toast .swal2-success{border-color:#a5dc86}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line]{position:absolute;width:1.6em;height:3em;transform:rotate(45deg);border-radius:50%}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=left]{top:-.8em;left:-.5em;transform:rotate(-45deg);transform-origin:2em 2em;border-radius:4em 0 0 4em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-circular-line][class$=right]{top:-.25em;left:.9375em;transform-origin:0 1.5em;border-radius:0 4em 4em 0}.swal2-popup.swal2-toast .swal2-success .swal2-success-ring{width:2em;height:2em}.swal2-popup.swal2-toast .swal2-success .swal2-success-fix{top:0;left:.4375em;width:.4375em;height:2.6875em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line]{height:.3125em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line][class$=tip]{top:1.125em;left:.1875em;width:.75em}.swal2-popup.swal2-toast .swal2-success [class^=swal2-success-line][class$=long]{top:.9375em;right:.1875em;width:1.375em}.swal2-popup.swal2-toast .swal2-success.swal2-icon-show .swal2-success-line-tip{-webkit-animation:swal2-toast-animate-success-line-tip .75s;animation:swal2-toast-animate-success-line-tip .75s}.swal2-popup.swal2-toast .swal2-success.swal2-icon-show .swal2-success-line-long{-webkit-animation:swal2-toast-animate-success-line-long .75s;animation:swal2-toast-animate-success-line-long .75s}.swal2-popup.swal2-toast.swal2-show{-webkit-animation:swal2-toast-show .5s;animation:swal2-toast-show .5s}.swal2-popup.swal2-toast.swal2-hide{-webkit-animation:swal2-toast-hide .1s forwards;animation:swal2-toast-hide .1s forwards}.swal2-container{display:flex;position:fixed;z-index:1060;top:0;right:0;bottom:0;left:0;flex-direction:row;align-items:center;justify-content:center;padding:.625em;overflow-x:hidden;transition:background-color .1s;-webkit-overflow-scrolling:touch}.swal2-container.swal2-backdrop-show,.swal2-container.swal2-noanimation{background:rgba(0,0,0,.4)}.swal2-container.swal2-backdrop-hide{background:0 0!important}.swal2-container.swal2-top{align-items:flex-start}.swal2-container.swal2-top-left,.swal2-container.swal2-top-start{align-items:flex-start;justify-content:flex-start}.swal2-container.swal2-top-end,.swal2-container.swal2-top-right{align-items:flex-start;justify-content:flex-end}.swal2-container.swal2-center{align-items:center}.swal2-container.swal2-center-left,.swal2-container.swal2-center-start{align-items:center;justify-content:flex-start}.swal2-container.swal2-center-end,.swal2-container.swal2-center-right{align-items:center;justify-content:flex-end}.swal2-container.swal2-bottom{align-items:flex-end}.swal2-container.swal2-bottom-left,.swal2-container.swal2-bottom-start{align-items:flex-end;justify-content:flex-start}.swal2-container.swal2-bottom-end,.swal2-container.swal2-bottom-right{align-items:flex-end;justify-content:flex-end}.swal2-container.swal2-bottom-end>:first-child,.swal2-container.swal2-bottom-left>:first-child,.swal2-container.swal2-bottom-right>:first-child,.swal2-container.swal2-bottom-start>:first-child,.swal2-container.swal2-bottom>:first-child{margin-top:auto}.swal2-container.swal2-grow-fullscreen>.swal2-modal{display:flex!important;flex:1;align-self:stretch;justify-content:center}.swal2-container.swal2-grow-row>.swal2-modal{display:flex!important;flex:1;align-content:center;justify-content:center}.swal2-container.swal2-grow-column{flex:1;flex-direction:column}.swal2-container.swal2-grow-column.swal2-bottom,.swal2-container.swal2-grow-column.swal2-center,.swal2-container.swal2-grow-column.swal2-top{align-items:center}.swal2-container.swal2-grow-column.swal2-bottom-left,.swal2-container.swal2-grow-column.swal2-bottom-start,.swal2-container.swal2-grow-column.swal2-center-left,.swal2-container.swal2-grow-column.swal2-center-start,.swal2-container.swal2-grow-column.swal2-top-left,.swal2-container.swal2-grow-column.swal2-top-start{align-items:flex-start}.swal2-container.swal2-grow-column.swal2-bottom-end,.swal2-container.swal2-grow-column.swal2-bottom-right,.swal2-container.swal2-grow-column.swal2-center-end,.swal2-container.swal2-grow-column.swal2-center-right,.swal2-container.swal2-grow-column.swal2-top-end,.swal2-container.swal2-grow-column.swal2-top-right{align-items:flex-end}.swal2-container.swal2-grow-column>.swal2-modal{display:flex!important;flex:1;align-content:center;justify-content:center}.swal2-container.swal2-no-transition{transition:none!important}.swal2-container:not(.swal2-top):not(.swal2-top-start):not(.swal2-top-end):not(.swal2-top-left):not(.swal2-top-right):not(.swal2-center-start):not(.swal2-center-end):not(.swal2-center-left):not(.swal2-center-right):not(.swal2-bottom):not(.swal2-bottom-start):not(.swal2-bottom-end):not(.swal2-bottom-left):not(.swal2-bottom-right):not(.swal2-grow-fullscreen)>.swal2-modal{margin:auto}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-container .swal2-modal{margin:0!important}}.swal2-popup{display:none;position:relative;box-sizing:border-box;flex-direction:column;justify-content:center;width:32em;max-width:100%;padding:1.25em;border:none;border-radius:.3125em;background:#fff;font-family:inherit;font-size:1rem}.swal2-popup:focus{outline:0}.swal2-popup.swal2-loading{overflow-y:hidden}.swal2-header{display:flex;flex-direction:column;align-items:center;padding:0 1.8em}.swal2-title{position:relative;max-width:100%;margin:0 0 .4em;padding:0;color:#595959;font-size:1.875em;font-weight:600;text-align:center;text-transform:none;word-wrap:break-word}.swal2-actions{display:flex;z-index:1;flex-wrap:wrap;align-items:center;justify-content:center;width:100%;margin:1.25em auto 0}.swal2-actions:not(.swal2-loading) .swal2-styled[disabled]{opacity:.4}.swal2-actions:not(.swal2-loading) .swal2-styled:hover{background-image:linear-gradient(rgba(0,0,0,.1),rgba(0,0,0,.1))}.swal2-actions:not(.swal2-loading) .swal2-styled:active{background-image:linear-gradient(rgba(0,0,0,.2),rgba(0,0,0,.2))}.swal2-actions.swal2-loading .swal2-styled.swal2-confirm{box-sizing:border-box;width:2.5em;height:2.5em;margin:.46875em;padding:0;-webkit-animation:swal2-rotate-loading 1.5s linear 0s infinite normal;animation:swal2-rotate-loading 1.5s linear 0s infinite normal;border:.25em solid transparent;border-radius:100%;border-color:transparent;background-color:transparent!important;color:transparent!important;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.swal2-actions.swal2-loading .swal2-styled.swal2-cancel{margin-right:30px;margin-left:30px}.swal2-actions.swal2-loading :not(.swal2-styled).swal2-confirm::after{content:\"\";display:inline-block;width:15px;height:15px;margin-left:5px;-webkit-animation:swal2-rotate-loading 1.5s linear 0s infinite normal;animation:swal2-rotate-loading 1.5s linear 0s infinite normal;border:3px solid #999;border-radius:50%;border-right-color:transparent;box-shadow:1px 1px 1px #fff}.swal2-styled{margin:.3125em;padding:.625em 2em;box-shadow:none;font-weight:500}.swal2-styled:not([disabled]){cursor:pointer}.swal2-styled.swal2-confirm{border:0;border-radius:.25em;background:initial;background-color:#3085d6;color:#fff;font-size:1.0625em}.swal2-styled.swal2-cancel{border:0;border-radius:.25em;background:initial;background-color:#aaa;color:#fff;font-size:1.0625em}.swal2-styled:focus{outline:0;box-shadow:0 0 0 1px #fff,0 0 0 3px rgba(50,100,150,.4)}.swal2-styled::-moz-focus-inner{border:0}.swal2-footer{justify-content:center;margin:1.25em 0 0;padding:1em 0 0;border-top:1px solid #eee;color:#545454;font-size:1em}.swal2-timer-progress-bar-container{position:absolute;right:0;bottom:0;left:0;height:.25em;overflow:hidden;border-bottom-right-radius:.3125em;border-bottom-left-radius:.3125em}.swal2-timer-progress-bar{width:100%;height:.25em;background:rgba(0,0,0,.2)}.swal2-image{max-width:100%;margin:1.25em auto}.swal2-close{position:absolute;z-index:2;top:0;right:0;align-items:center;justify-content:center;width:1.2em;height:1.2em;padding:0;overflow:hidden;transition:color .1s ease-out;border:none;border-radius:0;background:0 0;color:#ccc;font-family:serif;font-size:2.5em;line-height:1.2;cursor:pointer}.swal2-close:hover{transform:none;background:0 0;color:#f27474}.swal2-close::-moz-focus-inner{border:0}.swal2-content{z-index:1;justify-content:center;margin:0;padding:0 1.6em;color:#545454;font-size:1.125em;font-weight:400;line-height:normal;text-align:center;word-wrap:break-word}.swal2-checkbox,.swal2-file,.swal2-input,.swal2-radio,.swal2-select,.swal2-textarea{margin:1em auto}.swal2-file,.swal2-input,.swal2-textarea{box-sizing:border-box;width:100%;transition:border-color .3s,box-shadow .3s;border:1px solid #d9d9d9;border-radius:.1875em;background:inherit;box-shadow:inset 0 1px 1px rgba(0,0,0,.06);color:inherit;font-size:1.125em}.swal2-file.swal2-inputerror,.swal2-input.swal2-inputerror,.swal2-textarea.swal2-inputerror{border-color:#f27474!important;box-shadow:0 0 2px #f27474!important}.swal2-file:focus,.swal2-input:focus,.swal2-textarea:focus{border:1px solid #b4dbed;outline:0;box-shadow:0 0 3px #c4e6f5}.swal2-file::-moz-placeholder,.swal2-input::-moz-placeholder,.swal2-textarea::-moz-placeholder{color:#ccc}.swal2-file:-ms-input-placeholder,.swal2-input:-ms-input-placeholder,.swal2-textarea:-ms-input-placeholder{color:#ccc}.swal2-file::-ms-input-placeholder,.swal2-input::-ms-input-placeholder,.swal2-textarea::-ms-input-placeholder{color:#ccc}.swal2-file::placeholder,.swal2-input::placeholder,.swal2-textarea::placeholder{color:#ccc}.swal2-range{margin:1em auto;background:#fff}.swal2-range input{width:80%}.swal2-range output{width:20%;color:inherit;font-weight:600;text-align:center}.swal2-range input,.swal2-range output{height:2.625em;padding:0;font-size:1.125em;line-height:2.625em}.swal2-input{height:2.625em;padding:0 .75em}.swal2-input[type=number]{max-width:10em}.swal2-file{background:inherit;font-size:1.125em}.swal2-textarea{height:6.75em;padding:.75em}.swal2-select{min-width:50%;max-width:100%;padding:.375em .625em;background:inherit;color:inherit;font-size:1.125em}.swal2-checkbox,.swal2-radio{align-items:center;justify-content:center;background:#fff;color:inherit}.swal2-checkbox label,.swal2-radio label{margin:0 .6em;font-size:1.125em}.swal2-checkbox input,.swal2-radio input{margin:0 .4em}.swal2-validation-message{display:none;align-items:center;justify-content:center;padding:.625em;overflow:hidden;background:#f0f0f0;color:#666;font-size:1em;font-weight:300}.swal2-validation-message::before{content:\"!\";display:inline-block;width:1.5em;min-width:1.5em;height:1.5em;margin:0 .625em;border-radius:50%;background-color:#f27474;color:#fff;font-weight:600;line-height:1.5em;text-align:center}.swal2-icon{position:relative;box-sizing:content-box;justify-content:center;width:5em;height:5em;margin:1.25em auto 1.875em;border:.25em solid transparent;border-radius:50%;font-family:inherit;line-height:5em;cursor:default;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.swal2-icon .swal2-icon-content{display:flex;align-items:center;font-size:3.75em}.swal2-icon.swal2-error{border-color:#f27474;color:#f27474}.swal2-icon.swal2-error .swal2-x-mark{position:relative;flex-grow:1}.swal2-icon.swal2-error [class^=swal2-x-mark-line]{display:block;position:absolute;top:2.3125em;width:2.9375em;height:.3125em;border-radius:.125em;background-color:#f27474}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=left]{left:1.0625em;transform:rotate(45deg)}.swal2-icon.swal2-error [class^=swal2-x-mark-line][class$=right]{right:1em;transform:rotate(-45deg)}.swal2-icon.swal2-error.swal2-icon-show{-webkit-animation:swal2-animate-error-icon .5s;animation:swal2-animate-error-icon .5s}.swal2-icon.swal2-error.swal2-icon-show .swal2-x-mark{-webkit-animation:swal2-animate-error-x-mark .5s;animation:swal2-animate-error-x-mark .5s}.swal2-icon.swal2-warning{border-color:#facea8;color:#f8bb86}.swal2-icon.swal2-info{border-color:#9de0f6;color:#3fc3ee}.swal2-icon.swal2-question{border-color:#c9dae1;color:#87adbd}.swal2-icon.swal2-success{border-color:#a5dc86;color:#a5dc86}.swal2-icon.swal2-success [class^=swal2-success-circular-line]{position:absolute;width:3.75em;height:7.5em;transform:rotate(45deg);border-radius:50%}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=left]{top:-.4375em;left:-2.0635em;transform:rotate(-45deg);transform-origin:3.75em 3.75em;border-radius:7.5em 0 0 7.5em}.swal2-icon.swal2-success [class^=swal2-success-circular-line][class$=right]{top:-.6875em;left:1.875em;transform:rotate(-45deg);transform-origin:0 3.75em;border-radius:0 7.5em 7.5em 0}.swal2-icon.swal2-success .swal2-success-ring{position:absolute;z-index:2;top:-.25em;left:-.25em;box-sizing:content-box;width:100%;height:100%;border:.25em solid rgba(165,220,134,.3);border-radius:50%}.swal2-icon.swal2-success .swal2-success-fix{position:absolute;z-index:1;top:.5em;left:1.625em;width:.4375em;height:5.625em;transform:rotate(-45deg)}.swal2-icon.swal2-success [class^=swal2-success-line]{display:block;position:absolute;z-index:2;height:.3125em;border-radius:.125em;background-color:#a5dc86}.swal2-icon.swal2-success [class^=swal2-success-line][class$=tip]{top:2.875em;left:.8125em;width:1.5625em;transform:rotate(45deg)}.swal2-icon.swal2-success [class^=swal2-success-line][class$=long]{top:2.375em;right:.5em;width:2.9375em;transform:rotate(-45deg)}.swal2-icon.swal2-success.swal2-icon-show .swal2-success-line-tip{-webkit-animation:swal2-animate-success-line-tip .75s;animation:swal2-animate-success-line-tip .75s}.swal2-icon.swal2-success.swal2-icon-show .swal2-success-line-long{-webkit-animation:swal2-animate-success-line-long .75s;animation:swal2-animate-success-line-long .75s}.swal2-icon.swal2-success.swal2-icon-show .swal2-success-circular-line-right{-webkit-animation:swal2-rotate-success-circular-line 4.25s ease-in;animation:swal2-rotate-success-circular-line 4.25s ease-in}.swal2-progress-steps{align-items:center;margin:0 0 1.25em;padding:0;background:inherit;font-weight:600}.swal2-progress-steps li{display:inline-block;position:relative}.swal2-progress-steps .swal2-progress-step{z-index:20;width:2em;height:2em;border-radius:2em;background:#3085d6;color:#fff;line-height:2em;text-align:center}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step{background:#3085d6}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step{background:#add8e6;color:#fff}.swal2-progress-steps .swal2-progress-step.swal2-active-progress-step~.swal2-progress-step-line{background:#add8e6}.swal2-progress-steps .swal2-progress-step-line{z-index:10;width:2.5em;height:.4em;margin:0 -1px;background:#3085d6}[class^=swal2]{-webkit-tap-highlight-color:transparent}.swal2-show{-webkit-animation:swal2-show .3s;animation:swal2-show .3s}.swal2-hide{-webkit-animation:swal2-hide .15s forwards;animation:swal2-hide .15s forwards}.swal2-noanimation{transition:none}.swal2-scrollbar-measure{position:absolute;top:-9999px;width:50px;height:50px;overflow:scroll}.swal2-rtl .swal2-close{right:auto;left:0}.swal2-rtl .swal2-timer-progress-bar{right:0;left:auto}@supports (-ms-accelerator:true){.swal2-range input{width:100%!important}.swal2-range output{display:none}}@media all and (-ms-high-contrast:none),(-ms-high-contrast:active){.swal2-range input{width:100%!important}.swal2-range output{display:none}}@-moz-document url-prefix(){.swal2-close:focus{outline:2px solid rgba(50,100,150,.4)}}@-webkit-keyframes swal2-toast-show{0%{transform:translateY(-.625em) rotateZ(2deg)}33%{transform:translateY(0) rotateZ(-2deg)}66%{transform:translateY(.3125em) rotateZ(2deg)}100%{transform:translateY(0) rotateZ(0)}}@keyframes swal2-toast-show{0%{transform:translateY(-.625em) rotateZ(2deg)}33%{transform:translateY(0) rotateZ(-2deg)}66%{transform:translateY(.3125em) rotateZ(2deg)}100%{transform:translateY(0) rotateZ(0)}}@-webkit-keyframes swal2-toast-hide{100%{transform:rotateZ(1deg);opacity:0}}@keyframes swal2-toast-hide{100%{transform:rotateZ(1deg);opacity:0}}@-webkit-keyframes swal2-toast-animate-success-line-tip{0%{top:.5625em;left:.0625em;width:0}54%{top:.125em;left:.125em;width:0}70%{top:.625em;left:-.25em;width:1.625em}84%{top:1.0625em;left:.75em;width:.5em}100%{top:1.125em;left:.1875em;width:.75em}}@keyframes swal2-toast-animate-success-line-tip{0%{top:.5625em;left:.0625em;width:0}54%{top:.125em;left:.125em;width:0}70%{top:.625em;left:-.25em;width:1.625em}84%{top:1.0625em;left:.75em;width:.5em}100%{top:1.125em;left:.1875em;width:.75em}}@-webkit-keyframes swal2-toast-animate-success-line-long{0%{top:1.625em;right:1.375em;width:0}65%{top:1.25em;right:.9375em;width:0}84%{top:.9375em;right:0;width:1.125em}100%{top:.9375em;right:.1875em;width:1.375em}}@keyframes swal2-toast-animate-success-line-long{0%{top:1.625em;right:1.375em;width:0}65%{top:1.25em;right:.9375em;width:0}84%{top:.9375em;right:0;width:1.125em}100%{top:.9375em;right:.1875em;width:1.375em}}@-webkit-keyframes swal2-show{0%{transform:scale(.7)}45%{transform:scale(1.05)}80%{transform:scale(.95)}100%{transform:scale(1)}}@keyframes swal2-show{0%{transform:scale(.7)}45%{transform:scale(1.05)}80%{transform:scale(.95)}100%{transform:scale(1)}}@-webkit-keyframes swal2-hide{0%{transform:scale(1);opacity:1}100%{transform:scale(.5);opacity:0}}@keyframes swal2-hide{0%{transform:scale(1);opacity:1}100%{transform:scale(.5);opacity:0}}@-webkit-keyframes swal2-animate-success-line-tip{0%{top:1.1875em;left:.0625em;width:0}54%{top:1.0625em;left:.125em;width:0}70%{top:2.1875em;left:-.375em;width:3.125em}84%{top:3em;left:1.3125em;width:1.0625em}100%{top:2.8125em;left:.8125em;width:1.5625em}}@keyframes swal2-animate-success-line-tip{0%{top:1.1875em;left:.0625em;width:0}54%{top:1.0625em;left:.125em;width:0}70%{top:2.1875em;left:-.375em;width:3.125em}84%{top:3em;left:1.3125em;width:1.0625em}100%{top:2.8125em;left:.8125em;width:1.5625em}}@-webkit-keyframes swal2-animate-success-line-long{0%{top:3.375em;right:2.875em;width:0}65%{top:3.375em;right:2.875em;width:0}84%{top:2.1875em;right:0;width:3.4375em}100%{top:2.375em;right:.5em;width:2.9375em}}@keyframes swal2-animate-success-line-long{0%{top:3.375em;right:2.875em;width:0}65%{top:3.375em;right:2.875em;width:0}84%{top:2.1875em;right:0;width:3.4375em}100%{top:2.375em;right:.5em;width:2.9375em}}@-webkit-keyframes swal2-rotate-success-circular-line{0%{transform:rotate(-45deg)}5%{transform:rotate(-45deg)}12%{transform:rotate(-405deg)}100%{transform:rotate(-405deg)}}@keyframes swal2-rotate-success-circular-line{0%{transform:rotate(-45deg)}5%{transform:rotate(-45deg)}12%{transform:rotate(-405deg)}100%{transform:rotate(-405deg)}}@-webkit-keyframes swal2-animate-error-x-mark{0%{margin-top:1.625em;transform:scale(.4);opacity:0}50%{margin-top:1.625em;transform:scale(.4);opacity:0}80%{margin-top:-.375em;transform:scale(1.15)}100%{margin-top:0;transform:scale(1);opacity:1}}@keyframes swal2-animate-error-x-mark{0%{margin-top:1.625em;transform:scale(.4);opacity:0}50%{margin-top:1.625em;transform:scale(.4);opacity:0}80%{margin-top:-.375em;transform:scale(1.15)}100%{margin-top:0;transform:scale(1);opacity:1}}@-webkit-keyframes swal2-animate-error-icon{0%{transform:rotateX(100deg);opacity:0}100%{transform:rotateX(0);opacity:1}}@keyframes swal2-animate-error-icon{0%{transform:rotateX(100deg);opacity:0}100%{transform:rotateX(0);opacity:1}}@-webkit-keyframes swal2-rotate-loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}@keyframes swal2-rotate-loading{0%{transform:rotate(0)}100%{transform:rotate(360deg)}}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow:hidden}body.swal2-height-auto{height:auto!important}body.swal2-no-backdrop .swal2-container{top:auto;right:auto;bottom:auto;left:auto;max-width:calc(100% - .625em * 2);background-color:transparent!important}body.swal2-no-backdrop .swal2-container>.swal2-modal{box-shadow:0 0 10px rgba(0,0,0,.4)}body.swal2-no-backdrop .swal2-container.swal2-top{top:0;left:50%;transform:translateX(-50%)}body.swal2-no-backdrop .swal2-container.swal2-top-left,body.swal2-no-backdrop .swal2-container.swal2-top-start{top:0;left:0}body.swal2-no-backdrop .swal2-container.swal2-top-end,body.swal2-no-backdrop .swal2-container.swal2-top-right{top:0;right:0}body.swal2-no-backdrop .swal2-container.swal2-center{top:50%;left:50%;transform:translate(-50%,-50%)}body.swal2-no-backdrop .swal2-container.swal2-center-left,body.swal2-no-backdrop .swal2-container.swal2-center-start{top:50%;left:0;transform:translateY(-50%)}body.swal2-no-backdrop .swal2-container.swal2-center-end,body.swal2-no-backdrop .swal2-container.swal2-center-right{top:50%;right:0;transform:translateY(-50%)}body.swal2-no-backdrop .swal2-container.swal2-bottom{bottom:0;left:50%;transform:translateX(-50%)}body.swal2-no-backdrop .swal2-container.swal2-bottom-left,body.swal2-no-backdrop .swal2-container.swal2-bottom-start{bottom:0;left:0}body.swal2-no-backdrop .swal2-container.swal2-bottom-end,body.swal2-no-backdrop .swal2-container.swal2-bottom-right{right:0;bottom:0}@media print{body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown){overflow-y:scroll!important}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown)>[aria-hidden=true]{display:none}body.swal2-shown:not(.swal2-no-backdrop):not(.swal2-toast-shown) .swal2-container{position:static!important}}body.swal2-toast-shown .swal2-container{background-color:transparent}body.swal2-toast-shown .swal2-container.swal2-top{top:0;right:auto;bottom:auto;left:50%;transform:translateX(-50%)}body.swal2-toast-shown .swal2-container.swal2-top-end,body.swal2-toast-shown .swal2-container.swal2-top-right{top:0;right:0;bottom:auto;left:auto}body.swal2-toast-shown .swal2-container.swal2-top-left,body.swal2-toast-shown .swal2-container.swal2-top-start{top:0;right:auto;bottom:auto;left:0}body.swal2-toast-shown .swal2-container.swal2-center-left,body.swal2-toast-shown .swal2-container.swal2-center-start{top:50%;right:auto;bottom:auto;left:0;transform:translateY(-50%)}body.swal2-toast-shown .swal2-container.swal2-center{top:50%;right:auto;bottom:auto;left:50%;transform:translate(-50%,-50%)}body.swal2-toast-shown .swal2-container.swal2-center-end,body.swal2-toast-shown .swal2-container.swal2-center-right{top:50%;right:0;bottom:auto;left:auto;transform:translateY(-50%)}body.swal2-toast-shown .swal2-container.swal2-bottom-left,body.swal2-toast-shown .swal2-container.swal2-bottom-start{top:auto;right:auto;bottom:0;left:0}body.swal2-toast-shown .swal2-container.swal2-bottom{top:auto;right:auto;bottom:0;left:50%;transform:translateX(-50%)}body.swal2-toast-shown .swal2-container.swal2-bottom-end,body.swal2-toast-shown .swal2-container.swal2-bottom-right{top:auto;right:0;bottom:0;left:auto}body.swal2-toast-column .swal2-toast{flex-direction:column;align-items:stretch}body.swal2-toast-column .swal2-toast .swal2-actions{flex:1;align-self:stretch;height:2.2em;margin-top:.3125em}body.swal2-toast-column .swal2-toast .swal2-loading{justify-content:center}body.swal2-toast-column .swal2-toast .swal2-input{height:2em;margin:.3125em auto;font-size:1em}body.swal2-toast-column .swal2-toast .swal2-validation-message{font-size:1em}");
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t(require("jquery")):"function"==typeof define&&define.amd?define(["jquery"],t):"object"==typeof exports?exports.autonumeric=t(require("jquery")):e.autonumeric=t(e.jQuery)}(this,function(e){return function(e){function t(a){if(i[a])return i[a].exports;var n=i[a]={exports:{},id:a,loaded:!1};return e[a].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var i={};return t.m=e,t.c=i,t.p="",t(0)}([function(e,t,i){var a,n,r;i(1),i(1);(function(){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var o=function(){function e(e,t){var i=[],a=!0,n=!1,r=void 0;try{for(var o,s=e[Symbol.iterator]();!(a=(o=s.next()).done)&&(i.push(o.value),!t||i.length!==t);a=!0);}catch(e){n=!0,r=e}finally{try{!a&&s.return&&s.return()}finally{if(n)throw r}}return i}return function(t,i){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,i);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),s="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},u=void 0,l=void 0,c=void 0,h=void 0,g=void 0,p=["b","caption","cite","code","const","dd","del","div","dfn","dt","em","h1","h2","h3","h4","h5","h6","ins","kdb","label","li","option","output","p","q","s","sample","span","strong","td","th","u"],f={aSep:",",nSep:!1,dGroup:"3",aDec:".",altDec:null,aSign:"",pSign:"p",pNeg:"l",aSuffix:"",oLimits:null,vMax:"9999999999999.99",vMin:"-9999999999999.99",mDec:null,eDec:null,scaleDivisor:null,scaleDecimal:null,scaleSymbol:null,aStor:!1,mRound:"S",aPad:!0,nBracket:null,wEmpty:"focus",lZero:"allow",aForm:!0,sNumber:!1,anDefault:null,unSetOnSubmit:!1,outputType:null,debug:!1},d={Backspace:8,Tab:9,Enter:13,Shift:16,Ctrl:17,Alt:18,PauseBreak:19,CapsLock:20,Esc:27,Space:32,PageUp:33,PageDown:34,End:35,Home:36,LeftArrow:37,UpArrow:38,RightArrow:39,DownArrow:40,Insert:45,Delete:46,num0:48,num1:49,num2:50,num3:51,num4:52,num5:53,num6:54,num7:55,num8:56,num9:57,a:65,b:66,c:67,d:68,e:69,f:70,g:71,h:72,i:73,j:74,k:75,l:76,m:77,n:78,o:79,p:80,q:81,r:82,s:83,t:84,u:85,v:86,w:87,x:88,y:89,z:90,Windows:91,RightClick:93,numpad0:96,numpad1:97,numpad2:98,numpad3:99,numpad4:100,numpad5:101,numpad6:102,numpad7:103,numpad8:104,numpad9:105,MultiplyNumpad:106,PlusNumpad:107,MinusNumpad:109,DotNumpad:110,SlashNumpad:111,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,NumLock:144,ScrollLock:145,MyComputer:182,MyCalculator:183,Semicolon:186,Equal:187,Comma:188,Hyphen:189,Dot:190,Slash:191,Backquote:192,LeftBracket:219,Backslash:220,RightBracket:221,Quote:222,Command:224};!function(o){n=[i(1)],a=o,r="function"==typeof a?a.apply(t,n):a,!(void 0!==r&&(e.exports=r))}(function(e){function t(e){return null===e}function i(e){return void 0===e}function a(e){return null===e||void 0===e||""===e}function n(e){return"string"==typeof e||e instanceof String}function r(e){return"boolean"==typeof e}function v(e){var t=String(e).toLowerCase();return"true"===t||"false"===t}function m(e){return"object"===("undefined"==typeof e?"undefined":s(e))&&null!==e&&!Array.isArray(e)}function S(e){for(var t in e)if(e.hasOwnProperty(t))return!1;return!0}function N(e){return""!==e&&!isNaN(e)}function b(e,t){return B(e,t.settingsClone).replace(t.settingsClone.aDec,".")}function y(e,t){return!(!n(e)||!n(t)||""===e||""===t)&&e.indexOf(t)!==-1}function D(e,t){return!(!w(t)||t===[]||i(e))&&t.indexOf(e)!==-1}function w(e){if("[object Array]"===Object.prototype.toString.call([]))return Array.isArray(e)||"object"===("undefined"==typeof e?"undefined":s(e))&&"[object Array]"===Object.prototype.toString.call(e);throw new Error("toString message changed for Object Array")}function x(e){var t=e.split("."),a=o(t,2),n=a[1];return!i(n)}function k(e){var t=e.split("."),a=o(t,2),n=a[1];return i(n)?null:n.length}function C(e){var t={};if(i(e.selectionStart)){e.focus();var a=document.selection.createRange();t.length=a.text.length,a.moveStart("character",-e.value.length),t.end=a.text.length,t.start=t.end-t.length}else t.start=e.selectionStart,t.end=e.selectionEnd,t.length=t.end-t.start;return t}function A(e,t,a){if(i(e.selectionStart)){e.focus();var n=e.createTextRange();n.collapse(!0),n.moveEnd("character",a),n.moveStart("character",t),n.select()}else e.selectionStart=t,e.selectionEnd=a}function M(e){throw new Error(e)}function O(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];t&&console.warn("Warning: "+e)}function T(t,i){e.each(i,function(e,a){"function"==typeof a?i[e]=a(t,i,e):"function"==typeof t.autoNumeric[a]&&(i[e]=t.autoNumeric[a](t,i,e))})}function P(e,t){var i=0,a=0;return t[1]&&(i=t[1].length),e[1]&&(a=e[1].length),Math.max(i,a)}function V(e,t){T(e,t);var i=t.vMax.toString().split("."),a=t.vMin||0===t.vMin?t.vMin.toString().split("."):[];i[0]=i[0].replace("-",""),a[0]=a[0].replace("-",""),t.mIntPos=Math.max(i[0].length,1),t.mIntNeg=Math.max(a[0].length,1),null===t.mDec?(t.mDec=P(a,i),t.oDec=t.mDec):t.mDec=Number(t.mDec),t.mDec=t.scaleDivisor&&t.scaleDecimal?t.scaleDecimal:t.mDec,null===t.altDec&&t.mDec>0&&("."===t.aDec&&","!==t.aSep?t.altDec=",":","===t.aDec&&"."!==t.aSep&&(t.altDec="."));var n=t.aNeg?"([-\\"+t.aNeg+"]?)":"(-?)";t.aNegRegAutoStrip=n,t.skipFirstAutoStrip=new RegExp(n+"[^-"+(t.aNeg?"\\"+t.aNeg:"")+"\\"+t.aDec+"\\d].*?(\\d|\\"+t.aDec+"\\d)"),t.skipLastAutoStrip=new RegExp("(\\d\\"+t.aDec+"?)[^\\"+t.aDec+"\\d]\\D*$");var r="-0123456789\\"+t.aDec;return t.allowedAutoStrip=new RegExp("[^"+r+"]","gi"),t.numRegAutoStrip=new RegExp(n+"(?:\\"+t.aDec+"?(\\d+\\"+t.aDec+"\\d+)|(\\d*(?:\\"+t.aDec+"\\d*)?))"),t}function B(e,t){if(""!==t.aSign&&(e=e.replace(t.aSign,"")),t.aSuffix)for(;y(e,t.aSuffix);)e=e.replace(t.aSuffix,"");e=e.replace(t.skipFirstAutoStrip,"$1$2"),("s"===t.pNeg||"s"===t.pSign&&"p"!==t.pNeg)&&y(e,"-")&&""!==e&&(t.trailingNegative=!0),e=e.replace(t.skipLastAutoStrip,"$1"),e=e.replace(t.allowedAutoStrip,""),t.altDec&&(e=e.replace(t.altDec,t.aDec));var a=e.match(t.numRegAutoStrip);if(e=a?[a[1],a[2],a[3]].join(""):"","allow"===t.lZero||"keep"===t.lZero){var n="",r=e.split(t.aDec),s=o(r,2),u=s[0],l=s[1],c=u;y(c,t.aNeg)&&(n=t.aNeg,c=c.replace(t.aNeg,"")),""===n&&c.length>t.mIntPos&&"0"===c.charAt(0)&&(c=c.slice(1)),""!==n&&c.length>t.mIntNeg&&"0"===c.charAt(0)&&(c=c.slice(1)),e=""+n+c+(i(l)?"":t.aDec+l)}if(t.onOff&&"deny"===t.lZero||"allow"===t.lZero&&t.onOff===!1){var h="^"+t.aNegRegAutoStrip+"0*(\\d)";h=new RegExp(h),e=e.replace(h,"$1$2")}return e}function F(e,t){if("p"===t.pSign&&"l"===t.pNeg||"s"===t.pSign&&"p"===t.pNeg){var i=t.nBracket.split(","),a=o(i,2),n=a[0],r=a[1];t.onOff?t.onOff&&e.charAt(0)===n&&(e=e.replace(n,t.aNeg),e=e.replace(r,"")):(e=e.replace(t.aNeg,""),e=n+e+r)}return e}function E(e){return e=e.replace(",","."),y(e,"-")&&e.lastIndexOf("-")===e.length-1&&(e=e.replace("-",""),e="-"+e),e}function R(e,i){if(t(i)||"string"===i)return e;var a=void 0;switch(i){case"number":a=Number(e);break;case".-":a=y(e,"-")?e.replace("-","")+"-":e;break;case",":case"-,":a=e.replace(".",",");break;case",-":a=e.replace(".",","),a=y(a,"-")?a.replace("-","")+"-":a;break;case".":case"-.":a=e;break;default:M("The given outputType ["+i+"] option is not recognized.")}return a}function j(e,t){return"."!==t.aDec&&(e=e.replace(t.aDec,".")),"-"!==t.aNeg&&""!==t.aNeg&&(e=e.replace(t.aNeg,"-")),e.match(/\d/)||(e+="0.00"),e}function L(e,t){return"-"!==t.aNeg&&""!==t.aNeg&&(e=e.replace("-",t.aNeg)),"."!==t.aDec&&(e=e.replace(".",t.aDec)),e}function $(e,t,i){return""===e||e===t.aNeg?"always"===t.wEmpty||i?"l"===t.pNeg?e+t.aSign+t.aSuffix:t.aSign+e+t.aSuffix:e:null}function I(e,t){t.strip&&(e=B(e,t)),t.trailingNegative&&!y(e,"-")&&(e="-"+e);var a=$(e,t,!0),n=y(e,"-");if(n&&(e=e.replace("-","")),null!==a)return a;var r="";t.dGroup=t.dGroup.toString(),r="2"===t.dGroup?/(\d)((\d)(\d{2}?)+)$/:"2s"===t.dGroup?/(\d)((?:\d{2}){0,2}\d{3}(?:(?:\d{2}){2}\d{3})*?)$/:"4"===t.dGroup?/(\d)((\d{4}?)+)$/:/(\d)((\d{3}?)+)$/;var s=e.split(t.aDec),u=o(s,2),l=u[0],c=u[1];if(t.altDec&&i(c)){var h=e.split(t.altDec),g=o(h,2);l=g[0],c=g[1]}if(""!==t.aSep)for(;r.test(l);)l=l.replace(r,"$1"+t.aSep+"$2");return 0===t.mDec||i(c)?e=l:(c.length>t.mDec&&(c=c.substring(0,t.mDec)),e=l+t.aDec+c),"p"===t.pSign&&(n&&"l"===t.pNeg&&(e=t.aNeg+t.aSign+e),n&&"r"===t.pNeg&&(e=t.aSign+t.aNeg+e),n&&"s"===t.pNeg&&(e=t.aSign+e+t.aNeg),n||(e=t.aSign+e)),"s"===t.pSign&&(n&&"r"===t.pNeg&&(e=e+t.aSign+t.aNeg),n&&"l"===t.pNeg&&(e=e+t.aNeg+t.aSign),n&&"p"===t.pNeg&&(e=t.aNeg+e+t.aSign),n||(e+=t.aSign)),null!==t.nBracket&&(t.rawValue<0||"-"===e.charAt(0))&&(e=F(e,t)),t.trailingNegative=!1,e+t.aSuffix}function q(e,t){var i=void 0;switch(t){case 0:i=/(\.(?:\d*[1-9])?)0*$/;break;case 1:i=/(\.\d(?:\d*[1-9])?)0*$/;break;default:i=new RegExp("(\\.\\d{"+t+"}(?:\\d*[1-9])?)0*")}return e=e.replace(i,"$1"),0===t&&(e=e.replace(/\.$/,"")),e}function z(e,t){if(e=""===e?"0":e.toString(),"N05"===t.mRound||"CHF"===t.mRound||"U05"===t.mRound||"D05"===t.mRound){switch(t.mRound){case"N05":e=(Math.round(20*e)/20).toString();break;case"U05":e=(Math.ceil(20*e)/20).toString();break;default:e=(Math.floor(20*e)/20).toString()}var i=void 0;return i=y(e,".")?e.length-e.indexOf(".")<3?e+"0":e:e+".00"}var a="",n=0,r="",o=void 0;o=t.aPad?t.mDec:0,"-"===e.charAt(0)&&(r="-",e=e.replace("-","")),e.match(/^\d/)||(e="0"+e),"-"===r&&0===Number(e)&&(r=""),(Number(e)>0&&"keep"!==t.lZero||e.length>0&&"allow"===t.lZero)&&(e=e.replace(/^0*(\d)/,"$1"));var s=e.lastIndexOf("."),u=s===-1?e.length-1:s,l=e.length-1-u;if(l<=t.mDec){if(a=e,l<o){s===-1&&(a+=t.aDec);for(var c="000000";l<o;)c=c.substring(0,o-l),a+=c,l+=c.length}else l>o?a=q(a,o):0===l&&0===o&&(a=a.replace(/\.$/,""));return 0===Number(a)?a:r+a}var h=s+t.mDec,g=Number(e.charAt(h+1)),p="."===e.charAt(h)?e.charAt(h-1)%2:e.charAt(h)%2,f=e.substring(0,h+1).split("");if(g>4&&"S"===t.mRound||g>4&&"A"===t.mRound&&""===r||g>5&&"A"===t.mRound&&"-"===r||g>5&&"s"===t.mRound||g>5&&"a"===t.mRound&&""===r||g>4&&"a"===t.mRound&&"-"===r||g>5&&"B"===t.mRound||5===g&&"B"===t.mRound&&1===p||g>0&&"C"===t.mRound&&""===r||g>0&&"F"===t.mRound&&"-"===r||g>0&&"U"===t.mRound)for(n=f.length-1;n>=0;n-=1)if("."!==f[n]){if(f[n]=+f[n]+1,f[n]<10)break;n>0&&(f[n]="0")}return f=f.slice(0,h+1),a=q(f.join(""),o),0===Number(a)?a:r+a}function U(e,t,i){var a=t.aDec,n=t.mDec;if(e="paste"===i?z(e,t):e,a&&n){var r=e.split(a),s=o(r,2),u=s[0],l=s[1];if(l&&l.length>n)if(n>0){var c=l.substring(0,n);e=""+u+a+c}else e=u}return e}function K(e){var t={},i=void 0,a=void 0,n=void 0,r=void 0;if(0===e&&1/e<0&&(e="-0"),e=e.toString(),"-"===e.charAt(0)?(e=e.slice(1),t.s=-1):t.s=1,i=e.indexOf("."),i>-1&&(e=e.replace(".","")),i<0&&(i=e.length),a=e.search(/[1-9]/i)===-1?e.length:e.search(/[1-9]/i),n=e.length,a===n)t.e=0,t.c=[0];else{for(r=n-1;"0"===e.charAt(r);r-=1)n-=1;for(n-=1,t.e=i-a-1,t.c=[],i=0;a<=n;a+=1)t.c[i]=+e.charAt(a),i+=1}return t}function Z(e,t){var i=t.c,a=e.c,n=t.s,r=e.s,o=t.e,s=e.e;if(!i[0]||!a[0]){var u=void 0;return u=i[0]?n:a[0]?-r:0}if(n!==r)return n;var l=n<0;if(o!==s)return o>s^l?1:-1;for(n=-1,o=i.length,s=a.length,r=o<s?o:s,n+=1;n<r;n+=1)if(i[n]!==a[n])return i[n]>a[n]^l?1:-1;var c=void 0;return c=o===s?0:o>s^l?1:-1}function G(e,t){e=e.toString(),e=e.replace(",",".");var i=K(t.vMin),a=K(t.vMax),n=K(e),r=void 0;switch(t.oLimits){case"floor":r=[Z(i,n)>-1,!0];break;case"ceiling":r=[!0,Z(a,n)<1];break;case"ignore":r=[!0,!0];break;default:r=[Z(i,n)>-1,Z(a,n)<1]}return r}function Q(t){return n(t)&&(t="#"+t.replace(/(:|\.|\[|]|,|=)/g,"\\$1")),e(t)}function H(e,t){var a=arguments.length>2&&void 0!==arguments[2]&&arguments[2],n=e.data("autoNumeric");n||(n={},e.data("autoNumeric",n));var r=n.holder;return(i(r)&&t||a)&&(r=new Y(e.get(0),t),n.holder=r),r}function _(e){e.oDec=e.mDec,e.oPad=e.aPad,e.oBracket=e.nBracket,e.oSep=e.aSep,e.oSign=e.aSign,e.oSuffix=e.aSuffix}function W(e){for(var t=e+"=",i=document.cookie.split(";"),a="",n=0;n<i.length;n+=1){for(a=i[n];" "===a.charAt(0);)a=a.substring(1,a.length);if(0===a.indexOf(t))return a.substring(t.length,a.length)}return null}function J(){var e="modernizr";try{return sessionStorage.setItem(e,e),sessionStorage.removeItem(e),!0}catch(e){return!1}}function X(e,t,a){if(t.aStor){var n=""===e[0].name||i(e[0].name)?"AUTO_"+e[0].id:"AUTO_"+decodeURIComponent(e[0].name),r=void 0,o=void 0;if(J()===!1)switch(a){case"set":document.cookie=n+"="+t.rawValue+"; expires= ; path=/";break;case"wipe":r=new Date,r.setTime(r.getTime()+-864e5),o="; expires="+r.toUTCString(),document.cookie=n+"='' ;"+o+"; path=/";break;case"get":return W(n)}else switch(a){case"set":sessionStorage.setItem(n,t.rawValue);break;case"wipe":sessionStorage.removeItem(n);break;case"get":return sessionStorage.getItem(n)}}}function Y(t,i){this.settings=i,this.that=t,this.$that=e(t),this.formatted=!1,this.settingsClone=V(this.$that,this.settings),this.value=t.value}function ee(){var t=!(arguments.length>0&&void 0!==arguments[0])||arguments[0],i=arguments[1],a=Q(i),n=e("form").index(a),r=e("form:eq("+n+")")[0],u=[],l=[],c=/^(?:submit|button|image|reset|file)$/i,h=/^(?:input|select|textarea|keygen)/i,g=/^(?:checkbox|radio)$/i,p=/^(?:button|checkbox|color|date|datetime|datetime-local|email|file|image|month|number|password|radio|range|reset|search|submit|time|url|week)/i,f=0;if(e.each(r,function(e,t){""===t.name||!h.test(t.localName)||c.test(t.type)||t.disabled||!t.checked&&g.test(t.type)?l.push(-1):(l.push(f),f++)}),f=0,e.each(r,function(e,t){"input"!==t.localName||""!==t.type&&"text"!==t.type&&"hidden"!==t.type&&"tel"!==t.type?(u.push(-1),"input"===t.localName&&p.test(t.type)&&f++):(u.push(f),f++)}),t){var d=a.serializeArray();return e.each(d,function(t,i){var a=e.inArray(t,l);if(a>-1&&u[a]>-1){var r=e("form:eq("+n+") input:eq("+u[a]+")"),o=r.data("autoNumeric");"object"===("undefined"==typeof o?"undefined":s(o))&&(i.value=r.autoNumeric("getLocalized").toString())}}),d}var v=function(){var t=a.serialize(),i=t.split("&");return e.each(i,function(t){var a=i[t].split("="),r=o(a,2),c=r[0],h=r[1],g=e.inArray(t,l);if(g>-1&&u[g]>-1){var p=e("form:eq("+n+") input:eq("+u[g]+")"),f=p.data("autoNumeric");if("object"===("undefined"==typeof f?"undefined":s(f))&&null!==h){var d=p.autoNumeric("getLocalized").toString();i[t]=c+"="+d}}}),{v:i.join("&")}}();if("object"===("undefined"==typeof v?"undefined":s(v)))return v.v}function te(e,t){return e.on("focusin.autoNumeric mouseenter.autoNumeric",function(i){t=H(e);var a=t.settingsClone;if("focusin"===i.type||"mouseenter"===i.type&&!e.is(":focus")&&"focus"===a.wEmpty){a.onOff=!0,null!==a.nBracket&&""!==a.aNeg&&e.val(F(e.val(),a));var n=void 0;a.eDec?(a.mDec=a.eDec,e.autoNumeric("set",a.rawValue)):a.scaleDivisor?(a.mDec=a.oDec,e.autoNumeric("set",a.rawValue)):a.nSep?(a.aSep="",a.aSign="",a.aSuffix="",e.autoNumeric("set",a.rawValue)):(n=B(e.val(),a))!==a.rawValue&&e.autoNumeric("set",n),t.inVal=e.val(),t.lastVal=t.inVal;var r=$(t.inVal,a,!0);null!==r&&""!==r&&"focus"===a.wEmpty&&e.val(r)}}),t}function ie(e,t){return e.on("keydown.autoNumeric",function(i){if(t=H(e),t.that.readOnly)return t.processed=!0,!0;if(t.init(i),t.skipAlways(i))return t.processed=!0,!0;if(t.processAlways()){t.processed=!0,t.formatQuick(i);var a=e.val();return a!==t.lastVal&&t.settingsClone.throwInput&&e.trigger("input"),t.lastVal=a,t.settingsClone.throwInput=!0,i.preventDefault(),!1}return t.formatted=!1,!0}),t}function ae(e,t){return e.on("keypress.autoNumeric",function(i){if(!i.shiftKey||i.keyCode!==d.Insert){t=H(e);var a=t.processed;if(t.init(i),t.skipAlways(i))return!0;if(a)return i.preventDefault(),!1;if(t.processAlways()||t.processKeypress()){t.formatQuick(i);var n=e.val();return n!==t.lastVal&&t.settingsClone.throwInput&&e.trigger("input"),t.lastVal=n,t.settingsClone.throwInput=!0,void i.preventDefault()}t.formatted=!1}}),t}function ne(e,t,i){return e.on("keyup.autoNumeric",function(a){t=H(e),t.init(a);var n=t.skipAlways(a),r=t.kdCode;return t.kdCode=0,delete t.valuePartsBeforePaste,e[0].value===t.settingsClone.aSign?"s"===t.settingsClone.pSign?A(this,0,0):A(this,t.settingsClone.aSign.length,t.settingsClone.aSign.length):r===d.Tab&&A(this,0,e.val().length),e[0].value===t.settingsClone.aSuffix&&A(this,0,0),""===t.settingsClone.rawValue&&""!==t.settingsClone.aSign&&""!==t.settingsClone.aSuffix&&A(this,0,0),null!==t.settingsClone.eDec&&t.settingsClone.aStor&&X(e,i,"set"),!!n||(""===this.value||void(t.formatted||t.formatQuick(a)))}),t}function re(e,t){return e.on("focusout.autoNumeric mouseleave.autoNumeric",function(){if(!e.is(":focus")){t=H(e);var i=e.val(),a=i,n=t.settingsClone;if(n.onOff=!1,n.aStor&&X(e,n,"set"),n.nSep===!0&&(n.aSep=n.oSep,n.aSign=n.oSign,n.aSuffix=n.oSuffix),null!==n.eDec&&(n.mDec=n.oDec,n.aPad=n.oPad,n.nBracket=n.oBracket),i=B(i,n),""!==i){n.trailingNegative&&(i="-"+i,n.trailingNegative=!1);var r=G(i,n),s=o(r,2),u=s[0],l=s[1];null===$(i,n,!1)&&u&&l?(i=j(i,n),n.rawValue=i,n.scaleDivisor&&(i/=n.scaleDivisor,i=i.toString()),n.mDec=n.scaleDivisor&&n.scaleDecimal?+n.scaleDecimal:n.mDec,i=z(i,n),i=L(i,n)):(u||e.trigger("autoNumeric:minExceeded"),l||e.trigger("autoNumeric:maxExceeded"),i=n.rawValue)}else"zero"===n.wEmpty?(n.rawValue="0",i=z("0",n)):n.rawValue="";var c=$(i,n,!1);null===c&&(c=I(i,n)),c!==a&&(c=n.scaleSymbol?c+n.scaleSymbol:c,e.val(c)),c!==t.inVal&&(e.change(),delete t.inVal)}}),t}function oe(e,t){return e.on("paste",function(i){i.preventDefault(),t=H(e);var a=e.autoNumeric("get"),n=this.value||"",r=this.selectionStart||0,o=this.selectionEnd||0,s=n.substring(0,r),u=n.substring(o,n.length),l=b(i.originalEvent.clipboardData.getData("text/plain").holder);if(N(l)){var c=b(s+Number(l).valueOf()+u,t);N(c)&&Number(a).valueOf()!==Number(c).valueOf()&&(e.autoNumeric("set",c),e.trigger("input"))}else this.selectionStart=o}),t}function se(e,t){return e.closest("form").on("submit.autoNumeric",function(){if(t=H(e)){var i=t.settingsClone;i.unSetOnSubmit&&e.val(i.rawValue)}}),t}function ue(e){var t=e.is("input[type=text], input[type=hidden], input[type=tel], input:not([type])");t||"input"!==e.prop("tagName").toLowerCase()||M('The input type "'+e.prop("type")+'" is not supported by autoNumeric');var i=e.prop("tagName").toLowerCase();return"input"===i||D(i,p)||M("The <"+i+"> tag is not supported by autoNumeric"),t}function le(t,i,n){var r=!0;if(i){var o=n.val();if(t.aForm&&""!==o&&a(n.attr("value"))){var s=parseFloat(o.replace(",","."));isNaN(s)||1/0===s?M("The value ["+o+"] used in the input is not a valid value autoNumeric can work with."):(n.autoNumeric("set",s),r=!1)}else if(null!==t.anDefault&&t.anDefault.toString()!==o||null===t.anDefault&&""!==o&&o!==n.attr("value")||""!==o&&"hidden"===n.attr("type")&&!e.isNumeric(o.replace(",","."))){if((null!==t.eDec&&t.aStor||t.scaleDivisor&&t.aStor)&&(t.rawValue=X(n,t,"get")),!t.aStor){var u=void 0;null!==t.nBracket&&""!==t.aNeg?(t.onOff=!0,u=F(o,t)):u=o,t.rawValue=("s"===t.pNeg||"s"===t.pSign&&"p"!==t.pNeg)&&""!==t.aNeg&&y(o,"-")?"-"+B(u,t):B(u,t)}r=!1}if(""===o)switch(t.wEmpty){case"focus":r=!1;break;case"always":n.val(t.aSign),r=!1;break;case"zero":n.autoNumeric("set","0"),r=!1}else r&&o===n.attr("value")&&n.autoNumeric("set",o)}D(n.prop("tagName").toLowerCase(),t.tagList)&&""!==n.text()&&(null!==t.anDefault?t.anDefault===n.text()&&n.autoNumeric("set",n.text()):n.autoNumeric("set",n.text()))}function ce(e,t){if(!i(e)&&a(e.pNeg)&&""!==e.aSign)switch(t.pSign){case"s":t.pNeg="p";break;case"p":t.pNeg="r"}}function he(t,i){var a=i.data("autoNumeric");if("object"!==("undefined"==typeof a?"undefined":s(a))){var n=i.data();return a=e.extend({},f,n,t,{onOff:!1,runOnce:!1,rawValue:"",trailingNegative:!1,caretFix:!1,throwInput:!0,strip:!0,tagList:p}),e.each(a,function(e,t){"true"!==t&&"false"!==t||(a[e]="true"===t),"number"==typeof t&&"aScale"!==e&&(a[e]=t.toString())}),ce(t,a),a.aNeg=a.vMin<0?"-":"",h(a,!1),i.data("autoNumeric",a),a}return null}function ge(e,t){return new CustomEvent(e,{detail:t,bubbles:!1,cancelable:!1})}function pe(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null;return document.dispatchEvent(ge(e,t))}Y.prototype={init:function(e){this.value=this.that.value,this.settingsClone=V(this.$that,this.settings),this.ctrlKey=e.ctrlKey,this.cmdKey=e.metaKey,this.shiftKey=e.shiftKey,this.selection=C(this.that),"keydown"!==e.type&&"keyup"!==e.type||(this.kdCode=e.keyCode),this.which=e.which,this.processed=!1,this.formatted=!1},setSelection:function(e,t,a){e=Math.max(e,0),t=Math.min(t,this.that.value.length),this.selection={start:e,end:t,length:t-e},(i(a)||a)&&A(this.that,e,t)},setPosition:function(e,t){this.setSelection(e,e,t)},getBeforeAfter:function(){var e=this.value,t=e.substring(0,this.selection.start),i=e.substring(this.selection.end,e.length);return[t,i]},getBeforeAfterStriped:function(){var e=this.settingsClone,t=this.getBeforeAfter(),i=o(t,2),a=i[0],n=i[1];return a=B(a,this.settingsClone),n=B(n,this.settingsClone),e.trailingNegative&&!y(a,"-")&&(a="-"+a,n="-"===n?"":n),e.trailingNegative=!1,[a,n]},normalizeParts:function(e,t){var i=this.settingsClone;if(e=B(e,i),t=B(t,i),i.trailingNegative&&!y(e,"-")&&(e="-"+e,i.trailingNegative=!1),""!==e&&e!==i.aNeg||"deny"!==i.lZero||t>""&&(t=t.replace(/^0*(\d)/,"$1")),this.newValue=e+t,i.aDec){var a=this.newValue.match(new RegExp("^"+i.aNegRegAutoStrip+"\\"+i.aDec));a&&(e=e.replace(a[1],a[1]+"0"),this.newValue=e+t)}return[e,t]},setValueParts:function(e,t,i){var a=this.settingsClone,n=this.normalizeParts(e,t),r=G(this.newValue,a),s=o(r,2),u=s[0],l=s[1],c=n[0].length;if(this.newValue=n.join(""),u&&l){this.newValue=U(this.newValue,a,i);var h=y(this.newValue,",")?this.newValue.replace(",","."):this.newValue;return""===h||h===a.aNeg?a.rawValue="":a.rawValue=h,c>this.newValue.length&&(c=this.newValue.length),this.value=this.newValue,this.setPosition(c,!1),!0}return u?l||this.$that.trigger("autoNumeric:maxExceeded"):this.$that.trigger("autoNumeric:minExceeded"),!1},signPosition:function(){var e=this.settingsClone,t=e.aSign,i=this.that;if(t){var a=t.length;if("p"===e.pSign){var n=e.aNeg&&i.value&&i.value.charAt(0)===e.aNeg;return n?[1,a+1]:[0,a]}var r=i.value.length;return[r-a,r]}return[1e3,-1]},expandSelectionOnSign:function(e){var t=this.signPosition(),i=this.selection;i.start<t[1]&&i.end>t[0]&&((i.start<t[0]||i.end>t[1])&&this.value.substring(Math.max(i.start,t[0]),Math.min(i.end,t[1])).match(/^\s*$/)?i.start<t[0]?this.setSelection(i.start,t[0],e):this.setSelection(t[1],i.end,e):this.setSelection(Math.min(i.start,t[0]),Math.max(i.end,t[1]),e))},checkPaste:function(){if(!i(this.valuePartsBeforePaste)){var e=this.valuePartsBeforePaste,t=this.getBeforeAfter(),a=o(t,2),n=a[0],r=a[1];delete this.valuePartsBeforePaste;var s=n.substr(0,e[0].length)+B(n.substr(e[0].length),this.settingsClone);this.setValueParts(s,r,"paste")||(this.value=e.join(""),this.setPosition(e[0].length,!1))}},skipAlways:function(e){var t=this.kdCode,a=this.which,n=this.ctrlKey,r=this.cmdKey,o=this.shiftKey;if((n||r)&&"keyup"===e.type&&!i(this.valuePartsBeforePaste)||o&&t===d.Insert)return this.checkPaste(),!1;if(t>=d.F1&&t<=d.F12||t>=d.Windows&&t<=d.RightClick||t>=d.Tab&&t<d.Space||t<d.Backspace&&(0===a||a===t)||t===d.NumLock||t===d.ScrollLock||t===d.Insert||t===d.Command)return!0;if((n||r)&&t===d.a){if(this.settings.sNumber){e.preventDefault();var s=this.that.value.length,u=this.settings.aSign.length,l=y(this.that.value,"-")?1:0,c=this.settings.aSuffix.length,h=this.settings.pSign,g=this.settings.pNeg,p=void 0;p="s"===h?0:"l"===g&&1===l&&u>0?u+1:u;var f=void 0;if("p"===h)f=s-c;else switch(g){case"l":f=s-(c+u);break;case"r":f=u>0?s-(u+l+c):s-(u+c);break;default:f=s-(u+c)}A(this.that,p,f)}return!0}if((n||r)&&(t===d.c||t===d.v||t===d.x))return"keydown"===e.type&&this.expandSelectionOnSign(),t!==d.v&&t!==d.Insert||("keydown"===e.type||"keypress"===e.type?i(this.valuePartsBeforePaste)&&(this.valuePartsBeforePaste=this.getBeforeAfter()):this.checkPaste()),"keydown"===e.type||"keypress"===e.type||t===d.c;if(n||r)return!0;if(t===d.LeftArrow||t===d.RightArrow){var v=this.settingsClone.aSep,m=this.settingsClone.aDec,S=this.selection.start,N=this.that.value;return"keydown"!==e.type||this.shiftKey||(t!==d.LeftArrow||N.charAt(S-2)!==v&&N.charAt(S-2)!==m?t!==d.RightArrow||N.charAt(S+1)!==v&&N.charAt(S+1)!==m||this.setPosition(S+1):this.setPosition(S-1)),!0}return t>=d.PageDown&&t<=d.DownArrow},processTrailing:function(e){var t=o(e,2),i=t[0],a=t[1],n=this.settingsClone;return"p"===n.pSign&&"s"===n.pNeg&&(8===this.kdCode?(n.caretFix=this.selection.start>=this.value.indexOf(n.aSuffix)&&""!==n.aSuffix,"-"===this.value.charAt(this.selection.start-1)?i=i.substring(1):this.selection.start<=this.value.length-n.aSuffix.length&&(i=i.substring(0,i.length-1))):(n.caretFix=this.selection.start>=this.value.indexOf(n.aSuffix)&&""!==n.aSuffix,this.selection.start>=this.value.indexOf(n.aSign)+n.aSign.length&&(a=a.substring(1,a.length)),y(i,"-")&&"-"===this.value.charAt(this.selection.start)&&(i=i.substring(1)))),"s"===n.pSign&&"l"===n.pNeg&&(n.caretFix=this.selection.start>=this.value.indexOf(n.aNeg)+n.aNeg.length,8===this.kdCode?this.selection.start===this.value.indexOf(n.aNeg)+n.aNeg.length&&y(this.value,n.aNeg)?i=i.substring(1):"-"!==i&&(this.selection.start<=this.value.indexOf(n.aNeg)||!y(this.value,n.aNeg))&&(i=i.substring(0,i.length-1)):("-"===i[0]&&(a=a.substring(1)),this.selection.start===this.value.indexOf(n.aNeg)&&y(this.value,n.aNeg)&&(i=i.substring(1)))),"s"===n.pSign&&"r"===n.pNeg&&(n.caretFix=this.selection.start>=this.value.indexOf(n.aNeg)+n.aNeg.length,8===this.kdCode?this.selection.start===this.value.indexOf(n.aNeg)+n.aNeg.length?i=i.substring(1):"-"!==i&&this.selection.start<=this.value.indexOf(n.aNeg)-n.aSign.length?i=i.substring(0,i.length-1):""===i||y(this.value,n.aNeg)||(i=i.substring(0,i.length-1)):(n.caretFix=this.selection.start>=this.value.indexOf(n.aSign)&&""!==n.aSign,this.selection.start===this.value.indexOf(n.aNeg)&&(i=i.substring(1)),a=a.substring(1))),[i,a]},processAlways:function(){var e=this.settingsClone;if(this.kdCode===d.Backspace||this.kdCode===d.Delete){var t=void 0,i=void 0;if(this.selection.length){this.expandSelectionOnSign(!1);var a=this.getBeforeAfterStriped(),n=o(a,2);t=n[0],i=n[1],this.setValueParts(t,i)}else{var r=this.getBeforeAfterStriped(),s=o(r,2);if(t=s[0],i=s[1],""===t&&""===i&&(e.throwInput=!1),("p"===e.pSign&&"s"===e.pNeg||"s"===e.pSign&&("l"===e.pNeg||"r"===e.pNeg))&&y(this.value,"-")){var u=this.processTrailing([t,i]),l=o(u,2);t=l[0],i=l[1]}else 8===this.kdCode?t=t.substring(0,t.length-1):i=i.substring(1,i.length);this.setValueParts(t,i)}return!0}return!1},processKeypress:function(){var e=this.settingsClone,t=String.fromCharCode(this.which),i=this.getBeforeAfterStriped(),a=o(i,2),n=a[0],r=a[1];return e.throwInput=!0,t===e.aDec||e.altDec&&t===e.altDec||("."===t||","===t)&&this.kdCode===d.DotNumpad?!e.mDec||!e.aDec||(!(!e.aNeg||!y(r,e.aNeg))||(!!y(n,e.aDec)||(r.indexOf(e.aDec)>0||(0===r.indexOf(e.aDec)&&(r=r.substr(1)),this.setValueParts(n+e.aDec,r,null),!0)))):"-"!==t&&"+"!==t||"-"!==e.aNeg?t>="0"&&t<="9"?(e.aNeg&&""===n&&y(r,e.aNeg)&&(n=e.aNeg,r=r.substring(1,r.length)),e.vMax<=0&&e.vMin<e.vMax&&!y(this.value,e.aNeg)&&"0"!==t&&(n=e.aNeg+n),this.setValueParts(n+t,r,null),!0):(e.throwInput=!1,!0):!e||("p"===e.pSign&&"s"===e.pNeg||"s"===e.pSign&&"p"!==e.pNeg?(""===n&&y(r,e.aNeg)&&(n=e.aNeg,r=r.substring(1,r.length)),n="-"===n.charAt(0)||y(n,e.aNeg)?n.substring(1,n.length):"-"===t?e.aNeg+n:n):(""===n&&y(r,e.aNeg)&&(n=e.aNeg,r=r.substring(1,r.length)),n=n.charAt(0)===e.aNeg?n.substring(1,n.length):"-"===t?e.aNeg+n:n),this.setValueParts(n,r,null),!0)},formatQuick:function(t){var i=this,a=this.settingsClone,n=this.value,r=t.keyCode,s=this.getBeforeAfterStriped(),u=o(s,1),l=u[0];if((""===a.aSep||""!==a.aSep&&!y(n,a.aSep))&&(""===a.aSign||""!==a.aSign&&!y(n,a.aSign))){var c=n.split(a.aDec),h=o(c,1),g=h[0],p="";y(g,"-")&&(p="-",g=g.replace("-",""),l=l.replace("-","")),""===p&&g.length>a.mIntPos&&"0"===l.charAt(0)&&(l=l.slice(1)),"-"===p&&g.length>a.mIntNeg&&"0"===l.charAt(0)&&(l=l.slice(1)),l=p+l}var f=I(this.value,this.settingsClone),v=f.length;if(f){var m=l.split("");("s"===a.pNeg||"s"===a.pSign&&"p"!==a.pNeg)&&"-"===m[0]&&""!==a.aNeg&&(m.shift(),"s"!==a.pSign||"l"!==a.pNeg||r!==d.Backspace&&this.kdCode!==d.Backspace&&r!==d.Delete&&this.kdCode!==d.Delete||!a.caretFix||(m.push("-"),a.caretFix="keydown"===t.type),"p"!==a.pSign||"s"!==a.pNeg||r!==d.Backspace&&this.kdCode!==d.Backspace&&r!==d.Delete&&this.kdCode!==d.Delete||!a.caretFix||(m.push("-"),a.caretFix="keydown"===t.type),"s"!==a.pSign||"r"!==a.pNeg||r!==d.Backspace&&this.kdCode!==d.Backspace&&r!==d.Delete&&this.kdCode!==d.Delete||!a.caretFix||!function(){var n=a.aSign.split(""),o=["\\","^","$",".","|","?","*","+","(",")","["],s=[];e.each(n,function(e,t){t=n[e],D(t,o)?s.push("\\"+t):s.push(t)}),r!==d.Backspace&&i.kdCode!==d.Backspace||s.push("-"),m.push(s.join("")),a.caretFix="keydown"===t.type}());for(var S=0;S<m.length;S++)m[S].match("\\d")||(m[S]="\\"+m[S]);var N=new RegExp("^.*?"+m.join(".*?")),b=f.match(N);b?(v=b[0].length,(0===v&&f.charAt(0)!==a.aNeg||1===v&&f.charAt(0)===a.aNeg)&&a.aSign&&"p"===a.pSign&&(v=this.settingsClone.aSign.length+("-"===f.charAt(0)?1:0))):(a.aSign&&"s"===a.pSign&&(v-=a.aSign.length),a.aSuffix&&(v-=a.aSuffix.length))}this.that.value=f,this.setPosition(v),this.formatted=!0}};var fe={init:function(i){return this.each(function(){var a=e(this),n=ue(a),r=he(i,a);if(t(r))return this;_(r);var o=H(a,r);r.mDec=r.scaleDivisor&&r.scaleDecimal?r.scaleDecimal:r.mDec,r.runOnce===!1&&r.aForm&&le(r,n,a),r.runOnce=!0,n&&(o=te(a,o),o=re(a,o),o=ie(a,o),o=ae(a,o),o=ne(a,o,r),o=oe(a,o),se(a,o))})},destroy:function(){return e(this).each(function(){var e=Q(this),t=e.data("autoNumeric");"object"===("undefined"==typeof t?"undefined":s(t))&&(e.val(""),X(e,t,"wipe"),e.removeData("autoNumeric"),e.off(".autoNumeric"))})},wipe:function(){return e(this).each(function(){var e=Q(this),t=e.data("autoNumeric");"object"===("undefined"==typeof t?"undefined":s(t))&&(e.val(""),t.rawValue="",X(e,t,"wipe"))})},update:function(t){return e(this).each(function(){var i=Q(this),a=i.data("autoNumeric");"object"!==("undefined"==typeof a?"undefined":s(a))&&M('Initializing autoNumeric is required prior to calling the "update" method');var n=i.autoNumeric("get");if(a=e.extend(a,t),a.scaleDivisor&&(a.mDec=a.scaleDecimal?a.scaleDecimal:a.mDec),_(a),H(i,a,!0),a.aDec===a.aSep&&M('autoNumeric will not function properly when the decimal character aDec: "'+a.aDec+'" and thousand separator aSep: "'+a.aSep+'" are the same character'),ce(t,a),i.data("autoNumeric",a),""!==i.val()||""!==i.text())return i.autoNumeric("set",n)})},set:function(t){return e(this).each(function(){if(null!==t&&!i(t)){var a=Q(this),n=a.data("autoNumeric"),r=a.is("input[type=text], input[type=hidden], input[type=tel], input:not([type])"),u=t.toString();if("object"!==("undefined"==typeof n?"undefined":s(n))&&M('Initializing autoNumeric is required prior to calling the "set" method'),u=E(u),!e.isNumeric(Number(u)))return O('The value "'+u+'" being "set" is not numeric and therefore cannot be used appropriately.'),a.val("");if(""===u)return a.val("");var l=G(u,n),c=o(l,2),h=c[0],g=c[1];if(!h||!g){n.rawValue="",X(a,n,"wipe");var p=u;return u="",h||a.trigger("autoNumeric:minExceeded"),g||a.trigger("autoNumeric:maxExceeded"),M("The value ["+p+"] being set falls outside of the vMin ["+n.vMin+"] and vMax ["+n.vMax+"] range set for this element"),a.val("")}return r&&(n.eDec||n.scaleDivisor)&&(n.rawValue=u),(r||D(a.prop("tagName").toLowerCase(),n.tagList))&&(n.scaleDivisor&&!n.onOff&&(u/=n.scaleDivisor,u=u.toString(),n.mDec=n.scaleDecimal?n.scaleDecimal:n.mDec),u=z(u,n),null===n.eDec&&null===n.scaleDivisor&&(n.rawValue=u),u=L(u,n),u=I(u,n)),n.aStor&&(n.eDec||n.scaleDivisor)&&X(a,n,"set"),!n.onOff&&n.scaleSymbol&&(u+=n.scaleSymbol),r?a.val(u):!!D(a.prop("tagName").toLowerCase(),n.tagList)&&a.text(u)}})},unSet:function(){return e(this).each(function(){var e=Q(this),t=e.data("autoNumeric");
    "object"===("undefined"==typeof t?"undefined":s(t))&&(t.onOff=!0,e.val(e.autoNumeric("getLocalized")))})},reSet:function(){return e(this).each(function(){var e=Q(this),t=e.data("autoNumeric");"object"===("undefined"==typeof t?"undefined":s(t))&&e.autoNumeric("set",e.val())})},get:function(){var e=Q(this),t=e.is("input[type=text], input[type=hidden], input[type=tel], input:not([type])"),i=e.data("autoNumeric");"object"!==("undefined"==typeof i?"undefined":s(i))&&M('Initializing autoNumeric is required prior to calling the "get" method');var a="";if(t?a=e.eq(0).val():D(e.prop("tagName").toLowerCase(),i.tagList)?a=e.eq(0).text():M('The "<'+e.prop("tagName").toLowerCase()+'>" tag is not supported by autoNumeric'),i.eDec||i.scaleDivisor)a=i.rawValue;else{if(!/\d/.test(a)&&0!==Number(a)&&"focus"===i.wEmpty)return"";""!==a&&null!==i.nBracket&&(i.onOff=!0,a=F(a,i)),(i.runOnce||i.aForm===!1)&&(a=B(a,i)),a=j(a,i)}return a},getLocalized:function(){var e=Q(this),t=e.autoNumeric("get"),i=e.data("autoNumeric");return 0===Number(t)&&"keep"!==i.lZero&&(t="0"),R(t,i.outputType)},getFormatted:function(){return this.hasOwnProperty("0")&&"value"in this[0]||M("Unable to get the formatted string from the element."),this[0].value},getString:function(){return ee(!1,this)},getArray:function(){return ee(!0,this)},getSettings:function(){var e=Q(this);return e.eq(0).data("autoNumeric")}};e.fn.autoNumeric=function(e){if(fe[e]){for(var t=arguments.length,i=Array(t>1?t-1:0),a=1;a<t;a++)i[a-1]=arguments[a];return fe[e].apply(this,i)}return"object"!==("undefined"==typeof e?"undefined":s(e))&&e?void M('Method "'+e+'" is not supported by autoNumeric'):fe.init.apply(this,[e])},c=function(){return f},e.fn.autoNumeric.defaults=f,u=function(t,a){if(i(t)||null===t)return null;var n=e.extend({},f,{strip:!1},a);if(t=t.toString(),t=E(t),Number(t)<0&&(n.aNeg="-"),null===n.mDec){var r=n.vMax.toString().split("."),s=n.vMin||0===n.vMin?n.vMin.toString().split("."):[];n.mDec=P(s,r)}var u=G(t,n),l=o(u,2),c=l[0],h=l[1];return c&&h||(pe("autoFormat.autoNumeric","Range test failed"),M("The value ["+t+"] being set falls outside of the vMin ["+n.vMin+"] and vMax ["+n.vMax+"] range set for this element")),t=z(t,n),t=L(t,n),t=I(t,n)},e.fn.autoFormat=u,l=function(t,a){if(i(t)||null===t)return null;var n=e.extend({},f,{strip:!1},a),r="-0123456789\\"+n.aDec,o=new RegExp("[^"+r+"]","gi");return t=t.toString(),"-"===t.charAt(0)?n.aNeg="-":n.nBracket&&n.nBracket.split(",")[0]===t.charAt(0)&&(n.aNeg="-",n.onOff=!0,t=F(t,n)),t=t.replace(o,""),t=t.replace(",","."),t=R(t,n.outputType)},e.fn.autoUnformat=l,h=function(i){var o=!(arguments.length>1&&void 0!==arguments[1])||arguments[1],s=!0;(a(i)||!m(i)||S(i))&&M("The userOptions are invalid ; it should be a valid object, ["+i+"] given.");var u=void 0;u=o?e.extend({},f,i):i;var l=/^[0-9]+$/,c=/[0-9]+/,h=/^-?[0-9]+(\.?[0-9]+)?$/,g=/^[0-9]+(\.?[0-9]+)?$/;D(u.aSep,[",","."," ",""])||M("The thousand separator character option 'aSep' is invalid ; it should be ',', '.', ' ' or empty (''), ["+u.aSep+"] given."),v(u.nSep)||r(u.nSep)||M("The 'nSep' option is invalid ; it should be either 'false' or 'true', ["+u.nSep+"] given."),l.test(u.dGroup)||M("The digital grouping for thousand separator option 'dGroup' is invalid ; it should be a positive integer, ["+u.dGroup+"] given."),D(u.aDec,[",","."])||M("The decimal separator character option 'aDec' is invalid ; it should be '.' or ',', ["+u.aDec+"] given."),u.aDec===u.aSep&&M("autoNumeric will not function properly when the decimal character 'aDec' ["+u.aDec+"] and the thousand separator 'aSep' ["+u.aSep+"] are the same character."),t(u.altDec)||n(u.altDec)||M("The alternate decimal separator character option 'altDec' is invalid ; it should be a string, ["+u.altDec+"] given."),""===u.aSign||n(u.aSign)||M("The currency symbol option 'aSign' is invalid ; it should be a string, ["+u.aSign+"] given."),D(u.pSign,["p","s"])||M("The placement of the currency sign option 'pSign' is invalid ; it should either be 'p' (prefix) or 's' (suffix), ["+u.pSign+"] given."),D(u.pNeg,["p","s","l","r"])||M("The placement of the negative sign option 'pNeg' is invalid ; it should either be 'p' (prefix), 's' (suffix), 'l' (left) or 'r' (right), ["+u.pNeg+"] given."),(!n(u.aSuffix)||""!==u.aSuffix&&(y(u.aSuffix,"-")||c.test(u.aSuffix)))&&M("The additional suffix option 'aSuffix' is invalid ; it should not contains the negative sign '-' nor any numerical characters, ["+u.aSuffix+"] given."),t(u.oLimits)||D(u.oLimits,["ceiling","floor","ignore"])||M("The override min & max limits option 'oLimits' is invalid ; it should either be 'ceiling', 'floor' or 'ignore', ["+u.oLimits+"] given."),n(u.vMax)&&h.test(u.vMax)||M("The maximum possible value option 'vMax' is invalid ; it should be a string that represents a positive or negative number, ["+u.vMax+"] given."),n(u.vMin)&&h.test(u.vMin)||M("The minimum possible value option 'vMin' is invalid ; it should be a string that represents a positive or negative number, ["+u.vMin+"] given."),parseFloat(u.vMin)>parseFloat(u.vMax)&&M("The minimum possible value option is greater than the maximum possible value option ; 'vMin' ["+u.vMin+"] should be smaller than 'vMax' ["+u.vMax+"]."),t(u.mDec)||n(u.mDec)&&l.test(u.mDec)||M("The maximum number of decimal places option 'mDec' is invalid ; it should be a positive integer, ["+u.mDec+"] given."),u.aPad||t(u.mDec)||O("Setting 'aPad' to [false] will override the current 'mDec' setting ["+u.mDec+"].",s);var p=k(u.vMin),d=k(u.vMax);p=t(p)?0:p,d=t(d)?0:d;var N=Math.max(p,d);t(u.mDec)||!x(u.vMin)&&!x(u.vMax)||N===Number(u.mDec)||O("Setting 'mDec' to ["+u.mDec+"] will override the decimals declared in 'vMin' ["+u.vMin+"] and 'vMax' ["+u.vMax+"].",s),t(u.eDec)||n(u.eDec)&&l.test(u.eDec)||M("The number of expanded decimal places option 'eDec' is invalid ; it should be a positive integer, ["+u.eDec+"] given."),!t(u.eDec)&&!t(u.mDec)&&Number(u.mDec)<Number(u.eDec)&&M("autoNumeric will not function properly when the extended decimal places 'eDec' ["+u.eDec+"] is greater than the 'mDec' ["+u.mDec+"] value."),t(u.scaleDivisor)||g.test(u.scaleDivisor)||M("The scale divisor option 'scaleDivisor' is invalid ; it should be a positive number, preferably an integer, ["+u.scaleDivisor+"] given."),t(u.scaleDecimal)||l.test(u.scaleDecimal)||M("The scale number of decimals option 'scaleDecimal' is invalid ; it should be a positive integer, ["+u.scaleDecimal+"] given."),t(u.scaleSymbol)||n(u.scaleSymbol)||M("The scale symbol option 'scaleSymbol' is invalid ; it should be a string, ["+u.scaleSymbol+"] given."),v(u.aStor)||r(u.aStor)||M("The save to session storage option 'aStor' is invalid ; it should be either 'false' or 'true', ["+u.aStor+"] given."),D(u.mRound,["S","A","s","a","B","U","D","C","F","N05","CHF","U05","D05"])||M("The rounding method option 'mRound' is invalid ; it should either be 'S', 'A', 's', 'a', 'B', 'U', 'D', 'C', 'F', 'N05', 'CHF', 'U05' or 'D05' (cf. documentation), ["+u.mRound+"] given."),v(u.aPad)||r(u.aPad)||M("The control decimal padding option 'aPad' is invalid ; it should be either 'false' or 'true', ["+u.aPad+"] given."),t(u.nBracket)||D(u.nBracket,["(,)","[,]","<,>","{,}"])||M("The brackets for negative values option 'nBracket' is invalid ; it should either be '(,)', '[,]', '<,>' or '{,}', ["+u.nBracket+"] given."),D(u.wEmpty,["focus","press","always","zero"])||M("The display on empty string option 'wEmpty' is invalid ; it should either be 'focus', 'press', 'always' or 'zero', ["+u.wEmpty+"] given."),D(u.lZero,["allow","deny","keep"])||M("The leading zero behavior option 'lZero' is invalid ; it should either be 'allow', 'deny' or 'keep', ["+u.lZero+"] given."),v(u.aForm)||r(u.aForm)||M("The format on initialization option 'aForm' is invalid ; it should be either 'false' or 'true', ["+u.aForm+"] given."),v(u.sNumber)||r(u.sNumber)||M("The select number only option 'sNumber' is invalid ; it should be either 'false' or 'true', ["+u.sNumber+"] given."),t(u.anDefault)||""===u.anDefault||h.test(u.anDefault)||M("The unformatted default value option 'anDefault' is invalid ; it should be a string that represents a positive or negative number, ["+u.anDefault+"] given."),v(u.unSetOnSubmit)||r(u.unSetOnSubmit)||M("The remove formatting on submit option 'unSetOnSubmit' is invalid ; it should be either 'false' or 'true', ["+u.unSetOnSubmit+"] given."),t(u.outputType)||D(u.outputType,["string","number",".","-.",",","-,",".-",",-"])||M("The custom locale format option 'outputType' is invalid ; it should either be null, 'string', 'number', '.', '-.', ',', '-,', '.-' or ',-', ["+u.outputType+"] given."),v(u.debug)||r(u.debug)||M("The debug option 'debug' is invalid ; it should be either 'false' or 'true', ["+u.debug+"] given.")},e.fn.validate=h,g=function(e){var t=!0;try{h(e)}catch(e){t=!1}return t},function(){function e(e,t){t=t||{bubbles:!1,cancelable:!1,detail:void 0};var i=document.createEvent("CustomEvent");return i.initCustomEvent(e,t.bubbles,t.cancelable,t.detail),i}return"function"!=typeof window.CustomEvent&&(e.prototype=window.Event.prototype,void(window.CustomEvent=e))}()}),t.default={format:u,unFormat:l,getDefaultConfig:c,validate:h,areSettingsValid:g}}).call(window)},function(t,i){t.exports=e}])});
/**
 * autonumeric_ujs.js
 * @author: randoum
 * @version: 2.0 - 2017-01-07
 *
 * Created by Randoum on 2013-08-15. Please report any bugs to https://github.com/randoum/autonumeric-rails
 *
 * Wrap-up autoNumeric.js library to be used with Rails in a UJS flavor
 * All credits for autoNumeric library goes to its original creators
 * Whom can be reached at https://github.com/BobKnothe/autoNumeric
 *
 * The MIT License (http://www.opensource.org/licenses/mit-license.php)
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */


var AutonumericRails;

window.AutonumericRails = AutonumericRails = (function() {
    AutonumericRails.create_autonumeric_object = function(obj) {
        if (!obj.data('autonumeric-initialized')) {
            return new this(obj);
        }
    };

    AutonumericRails.delete_autonumeric_object = function(obj) {
      if (obj.data('autonumeric-initialized')) {
        obj.removeData('autonumeric-initialized').removeData('autonumeric').removeAttr('data-autonumeric').off('keyup blur').autoNumeric('destroy');
        $('input#' + obj.attr('id') + '_val[type="hidden"][name="' + obj.attr('name') + '"]').remove();
      }
    };

    function AutonumericRails(field) {
        this.field = field;
        this.field.data('autonumeric-initialized', true);
        this.create_hidden_field();
        this.init_autonumeric();
        this.sanitize_value();
        this.field.on('keyup blur', $.proxy(function() {
            this.sanitize_value();
        }, this));
    }

    AutonumericRails.prototype.create_hidden_field = function() {
        this.hidden = $('<input>').attr('type', 'hidden').attr('id', this.field.attr('id') + '_val').attr('name', this.field.attr('name'));
        this.hidden.insertAfter(this.field);
    };

    AutonumericRails.prototype.init_autonumeric = function() {
        this.field.autoNumeric('init', $.parseJSON(this.field.attr('data-autonumeric')));
    };

    AutonumericRails.prototype.sanitize_value = function() {
        this.hidden.val(this.field.autoNumeric('getSettings').rawValue);
    };

    return AutonumericRails;

})();

window.refresh_autonumeric = function() {
    $('input[data-autonumeric]').each(function() {
        AutonumericRails.create_autonumeric_object($(this));
    });
};

jQuery(function() {
    window.refresh_autonumeric();
    $(document).on('refresh_autonumeric', function() {
        window.refresh_autonumeric();
    });
    $(document).on('ajaxComplete', function() {
        window.refresh_autonumeric();
    });
});


(function() {


}).call(this);
(function() {
  var getContentSec, resetSelectSec;

  resetSelectSec = function(element) {
    $(element).children().remove();
    return $(element).append('<option value="">Seleccione:</option>');
  };

  getContentSec = function(model, point) {
    var element, elementcity, url;
    elementcity = '#system_tables_pathroutesection_' + point + '_city_id';
    if (model === 'department') {
      url = '/system/tables/departments/fromcountry/' + $('#sections_country_' + point).val();
      element = '#sections_department_' + point;
      resetSelectSec(elementcity);
    } else if (model === 'city') {
      url = '/system/tables/cities/getcitiesbydepartment/' + $('#sections_department_' + point).val();
      element = '#sections_city_' + point;
      resetSelectSec(elementcity);
    } else if (model === 'locality') {
      url = '/system/tables/cities/getlocalitiesbycities/' + $('#sections_city_' + point).val();
      element = '#system_tables_pathroutesection_' + point + '_city_id';
      resetSelectSec(elementcity);
    }
    resetSelectSec(element);
    return $.ajax(url, {
      type: 'GET',
      dataType: 'json',
      success: function(data, textStatus) {
        data = data.data;
        $.each(data, function(index, item) {
          return $(element).append('<option value="' + item.id + '">' + item.name + '</option>');
        });
        return $(element).focus();
      }
    });
  };

  $(document).on('turbolinks:load', function() {
    $('#system_tables_pathroutesection_from_city_id').attr("required", "true");
    $('#system_tables_pathroutesection_to_city_id').attr("required", "true");
    $('#sections_country_from').on("change", function() {
      return getContentSec('department', 'from');
    });
    $('#sections_country_to').on("change", function() {
      return getContentSec('department', 'to');
    });
    $('#sections_department_from').on("change", function() {
      return getContentSec('city', 'from');
    });
    $('#sections_department_to').on("change", function() {
      return getContentSec('city', 'to');
    });
    $('#sections_city_from').on("change", function() {
      return getContentSec('locality', 'from');
    });
    return $('#sections_city_to').on("change", function() {
      return getContentSec('locality', 'to');
    });
  });

}).call(this);
(function() {
  var getContent, mostrarpathroute, resetSelect;

  resetSelect = function(element) {
    $(element).children().remove();
    return $(element).append('<option value="">Seleccione:</option>');
  };

  getContent = function(model, point) {
    var contador_sicetac, element, elementcity, url;
    elementcity = '#system_tables_route_' + point + '_city_id';
    if (model === 'department') {
      url = '/system/tables/departments/fromcountry/' + $('#country_' + point).val();
      element = '#department_' + point;
      resetSelect(elementcity);
    } else if (model === 'city') {
      url = '/system/tables/cities/getcitiesbydepartment/' + $('#department_' + point).val();
      element = '#city_' + point;
      resetSelect(elementcity);
    } else if (model === 'filter_city') {
      url = '/system/tables/cities/getcitiesbydepartment/' + $('#route_department_id').val();
      element = '#route_city_filtro';
      resetSelect(element);
    } else if (model === 'filter_locality') {
      url = '/system/tables/cities/getlocalitiesbycities/' + $('#route_city_filtro').val();
      element = '#route_locality_filtro';
      resetSelect(element);
    } else if (model === 'filter_to_city') {
      url = '/system/tables/cities/getcitiesbydepartment/' + $('#route_to_department_id').val();
      element = '#route_to_city_filtro';
      resetSelect(element);
    } else if (model === 'filter_to_locality') {
      url = '/system/tables/cities/getlocalitiesbycities/' + $('#route_to_city_filtro').val();
      element = '#route_to_locality_filtro';
      resetSelect(element);
    } else if (model === 'sicetac') {
      url = '/system/tables/routes/get_sicetac_costs/' + $('#system_tables_route_from_city_id').val() + '/' + $('#system_tables_route_to_city_id').val();
      element = '#sicetac';
      resetSelect(element);
    } else {
      url = '/system/tables/cities/getlocalitiesbycities/' + $('#city_' + point).val();
      element = elementcity;
    }
    resetSelect(element);
    contador_sicetac = 0;
    return $.ajax(url, {
      type: 'GET',
      dataType: 'json',
      success: function(data, textStatus) {
        data = data.data;
        $.each(data, function(index, item) {
          if (element === '#sicetac') {
            contador_sicetac = contador_sicetac + 1;
          }
          return $(element).append('<option value="' + item.id + '">' + item.name + '</option>');
        });
        $(element).focus();
        if (element === '#sicetac') {
          if (contador_sicetac > 0) {
            console.log('hay datos ');
            return alert('la ruta tiene costos en la tabla SiceTac, por favor Seleccionar un valor de la lista del campo Valor Minimo SiceTac ');
          } else {
            console.log('no hay datos');
            $('#system_tables_route_minimum_cost').val(0);
            return $('#system_tables_route_minimum_cost').prop("disabled", false);
          }
        }
      }
    });
  };

  mostrarpathroute = function(value) {
    $('#dialog').html(value);
    return $('#dialog').dialog(function() {
      return {
        autoOpen: false,
        show: 'blind',
        hide: 'explode'
      };
    });
  };

  $(document).ready(function() {
    $('#system_tables_route_from_city_id').attr("required", "true");
    $('#system_tables_route_to_city_id').attr("required", "true");
    $('#country_from').on("change", function() {
      return getContent('department', 'from');
    });
    $('#country_to').on("change", function() {
      return getContent('department', 'to');
    });
    $('#department_from').on("change", function() {
      return getContent('city', 'from');
    });
    $('#department_to').on("change", function() {
      return getContent('city', 'to');
    });
    $('#city_from').on("change", function() {
      return getContent('locality', 'from');
    });
    $('#city_to').on("change", function() {
      return getContent('locality', 'to');
    });
    $('#route_department_id').on("change", function() {
      return getContent('filter_city', 'to');
    });
    $('#route_city_filtro').on("change", function() {
      return getContent('filter_locality', 'to');
    });
    $('#route_to_department_id').on("change", function() {
      return getContent('filter_to_city', 'to');
    });
    $('#route_to_city_filtro').on("change", function() {
      return getContent('filter_to_locality', 'to');
    });
    $('#system_tables_route_from_city_id').on("change", function() {
      return getContent('sicetac', '');
    });
    $('#system_tables_route_to_city_id').on("change", function() {
      return getContent('sicetac', '');
    });
    $('#sicetac').on("change", function() {
      var negotiated;
      negotiated = parseFloat($('#sicetac').val());
      console.log('cambio el valor' + negotiated);
      $('#system_tables_route_minimum_cost').val(negotiated);
      return $('#system_tables_route_minimum_cost').prop("disabled", true);
    });
    jQuery(function() {});
    return $('#routes tbody').on('click', 'tr td.details-control', function(event) {
      console.log("tr");
      $('#dialog').html("Hola");
      return $('#dialog').dialog(function() {
        return {
          autoOpen: false,
          show: 'blind',
          hide: 'explode'
        };
      });
    });
  });

}).call(this);
$( document ).on('turbolinks:load', function() {

    load_page_tax();

    $("#tax_tax_calculation_type_id").on('change', function(){
        val = $(this).val();
        show_hidden_input(val);
    });

    $("#tax_has_exceptions").on('change', function(){
        val = $(this).val();
        show_hidden_exceptions(val);
    });

    $("#tax_charge_only_to").on('change', function(){
        val = $(this).val();
        show_hidden_only_to(val);
    });


    $( window ).on( "load", function() {
        // val = $('#tax_tax_calculation_type_id').val();
        // show_hidden_input(val)
        // val = $('#tax_has_exceptions').val();
        // show_hidden_exceptions(val)
        // val = $('#tax_charge_only_to').val();
        // show_hidden_only_to(val)
        load_page_tax();
    });

    function show_hidden_input(val){
        if (val == 1){
            $("#value_fixed_input").addClass("ocultar");
            $("#percentage_input").addClass("ocultar");
        }
        else if (val == 2){
            $("#value_fixed_input").removeClass("ocultar"); 
            $("#percentage_input").addClass("ocultar");          
        }
        else if (val == 3){
            $("#percentage_input").removeClass("ocultar");
            $("#value_fixed_input").addClass("ocultar");
        }
    }

    function show_hidden_exceptions(val){
        if (val == 'true'){
            $("#only_to").addClass("ocultar");
            $("#exceptions").removeClass("ocultar");
            $("#apply_only_to").addClass("ocultar");
            clear_form_elements('apply_only_to')
        }
        else if (val == 'false'){
            $("#only_to").removeClass("ocultar");
            $("#exceptions").addClass("ocultar");

        }
    }

    function show_hidden_only_to(val){
        if (val == 'true'){
            $("#apply_only_to").removeClass("ocultar");
            $("#exceptions").addClass("ocultar");
            $("#exceptions_show").addClass("ocultar");
            clear_form_elements('exceptions');
        }
        else if (val == 'false'){
            $("#apply_only_to").addClass("ocultar");
            $("#exceptions_show").removeClass("ocultar");
        }
    }

    function clear_form_elements(element) {
        $("#"+element).find(':input').each(function() {
            switch(this.type) {
                case 'password':
                case 'text':
                case 'textarea':
                case 'file':
                case 'select-one':
                case 'select-multiple':
                case 'date':
                case 'number':
                case 'tel':
                case 'email':
                $(this).val('');
                break;
                case 'checkbox':
                case 'radio':
                this.checked = false;
                break;
            }
        });
    }

    function load_page_tax(){
        val = $('#tax_tax_calculation_type_id').val();
        show_hidden_input(val)
        val = $('#tax_has_exceptions').val();
        show_hidden_exceptions(val)
        val = $('#tax_charge_only_to').val();
        show_hidden_only_to(val)
        
    }
});
$( document ).on('turbolinks:load', function() {

    $('#taxesloadgenerators').DataTable( {
        "processing": true,
        "serverSide": true,
        "pageLength": 25,
        "cache": false,
        "language": { "url": "/assets/datatables/datatables_spanish.json" },
        "ajax": {
            'url': "/taxesloadgenerators/list.json",
            'type': 'GET',
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+$('#taxesloadgenerators').attr('data-toc') );
            }
        }
    } );
});
$( document ).on('turbolinks:load', function() {
    $("#tool_workshop_headquarter_id").on('change', function() {
        var headquarter = $( "#tool_workshop_headquarter_id option:selected" ).val();
        $('#tool_workshop_warehouse_id').find('option:not(:first)').remove();
        $.ajax({
            url: '/workshop/warehouses/get_warehouses_by_headquarter/'+headquarter+'.json',
            type: "get",
            async: false,
            'beforeSend': function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+ token );
            },
            success: function(result) {
                if (result.success == true) {
                    // alert(result.data);
                    warehouses = result.data
                    $.each(warehouses, function (index,item) {
                        $("#tool_workshop_warehouse_id").append('<option value="'+item.id+'">' + item.name + '</option>')
                        $("#tool_workshop_warehouse_id").focus()
                    });
                }
                else {
                }
            }
        });
    });
})
;
$(document).on('turbolinks:load', function() {
    $("#mil_attach").click(function(){
        alert("clickmil");
      });
});

(function() {


}).call(this);
(function() {


}).call(this);

$(document).on('turbolinks:load', function() {
	$('#trailer_group_cartype_id').on('change', function(){
		if ($(this).val() == 233){
			$('#subtype_trailer_group').removeClass('ocultar')
			$('#trailer_group_sider_type_id').attr('required', true)
		} else {
			$('#subtype_trailer_group').addClass('ocultar')
			$('#trailer_group_sider_type_id').attr('required', false)
		}
	})
})
;
$(function() {
    $("select#trailer_vehicle_id").on("change", function() {
    	if ($("#trailer_id").val()) {
    		var url = "../valid/vehicle"
    	}else{
    		var url = "valid/vehicle"
    	}
        $.ajax({
            url: url,
            type: "GET",
            data: { vehicle_id: $("select#trailer_vehicle_id").val(), trailer_id: $("#trailer_id").val() },
            success: function(result) {
            	if (result.success == false) alert(result.message);
            },
            error: function(result){
            	console.log(result);
            }
        });
    });
});
var vehicles;
var vehicles_filter = [];
$(document).ready(function() {
	$('.quantity_car_type').change(function(){
		var newValue = 0;
		$(".quantity_car_type"+$(this).attr('id')).each(function(){
			newValue += parseInt($(this).val());
		})
		$("#total_car_type_"+$(this).attr('id')).val(newValue);
	})
	if ($(".container-third-party .indicators").length) {
		vehicles = set_vehicles();
		total_cars =  vehicles.to_table.length
		load_office_markers_gmap(vehicles.to_map);
		load_table_office_vehicles(vehicles.to_table);
		if($("#third-party-tbody-vehicles").length) {
			// https://stackoverflow.com/questions/17067294/html-table-with-100-width-with-vertical-scroll-inside-tbody
			// Change the selector if needed
			var $table = $('table'),
			    $bodyCells = $table.find('tbody tr:first').children(),
			    colWidth;

			// Get the tbody columns width array
			colWidth = $bodyCells.map(function() {
			    return $(this).width();
			}).get();

			// Set the width of thead columns
			$table.find('thead tr').children().each(function(i, v) {
			    $(v).width(colWidth[i]);
			});
			dbRef = firebase.database().ref(document.getElementById("table_fr").value);
			dbRef.on('child_changed', function(car) {
				var car = car.val();
				if (car.driver) {
					if (car.driver.chat_office) {
						var messages = JSON.parse(car.driver.chat_office.note);
						var contentMessagesChatDriver = document.getElementById("contentMessagesChatDriver");
						contentMessagesChatDriver.innerHTML = null;
						for (var i = 0; i < messages.length; i++) {
							var messageDriver = document.createElement("div");
							if (messages[i].user_id == $("#currentUserId").val()) {
								messageDriver.setAttribute("class", "col-md-10 col-md-offset-2 ownMessage text-right");
							}else{
								messageDriver.setAttribute("class", "col-md-10 messageDriver");
							}
							var messageChat = document.createElement("p");
							messageChat.setAttribute("class", "messageChat");
							messageChat.innerHTML = messages[i].message;
							messageDriver.append(messageChat);
							contentMessagesChatDriver.append(messageDriver);
						}
						document.getElementById("btnChat"+car.driver.id).setAttribute("onclick", "seeChatDriver("+JSON.stringify(car.driver)+", '/notifications/wall/messages/add/comments', 'office')");
						document.getElementById("btnSendMessageChat").setAttribute("onclick", "sendMessageChat("+JSON.stringify(car.driver)+", '/notifications/wall/messages/add/comments', 'office')");
					}
				}
			});
		}
	}
	$("#tableOfficeVehicles tr").click(function() {
		if($(this).find("td").length) {
			var selected = $(this).hasClass("highlight");
			$('.detail-vehicle-third-party').removeClass('hide');
			$("#tableOfficeVehicles tr").removeClass("highlight");
			if(!selected){
				
				$('#state_vehicle_third_party').html($(this).find("td")[6].innerText);
				imagen = $(this).find("td")[5].innerText.substring(1)
				if (imagen.length>0) {
					imagen = '<img src="https://storage.googleapis.com/images.fletx.co/uploads/staging/request/route_image/'+imagen+'" width="100%"/>'
				}
				$('#route_vehicle_third_party').html(imagen);
				
				$(this).addClass("highlight");
				vehicles_filter = [];
				var filter = $(this).find("td")[0].innerText.toUpperCase();
				for (var j = vehicles.to_map.length - 1; j >= 0; j--) {
					if (vehicles.to_map[j].license_plate) {
						if (vehicles.to_map[j].license_plate.toUpperCase().indexOf(filter) > -1){
							vehicles_filter.push(vehicles.to_map[j]);
						}
					}
				}
				if (vehicles_filter.length > 0) { load_office_markers_gmap(vehicles_filter); }
			}else{
				$('.detail-vehicle-third-party').addClass('hide');
				vehicles_filter = [];
				load_office_markers_gmap(vehicles.to_map);
			}
		}
	});
})
function validGoalForm() {
	var isSuccess = true;
	$(".quantity_car_type").each(function() {
		if(!$(this).val()) {
			$(this).css("border-color", "#db006e");
			isSuccess = false;
		}else{
			if(Number($(this).val()) >= 0) {
				$(this).css("border-color", "#707070");
			}else{
				$(this).css("border-color", "#db006e");
				isSuccess = false;
			}
		}
	});
	$(".total_car_type").each(function() {
		if(!$(this).val()) {
			$(this).css("border-color", "#db006e");
			isSuccess = false;
		}else{
			if(Number($(this).val()) >= 0) {
				$(this).css("border-color", "#707070");
			}else{
				$(this).css("border-color", "#db006e");
				isSuccess = false;
			}
		}
	});
	return isSuccess;
}

function validCommercialGoalForm() {
	var isSuccess = true;
	$(".goal_commercial").each(function() {
		if(!$(this).val()) {
			$(this).css("border-color", "#db006e");
			isSuccess = false;
		}else{
			if(Number($(this).val()) >= 0) {
				$(this).css("border-color", "#707070");
			}else{
				$(this).css("border-color", "#db006e");
				isSuccess = false;
			}
		}
	});
	return isSuccess;
}

function load_table_office_vehicles(vehicles) {
	var tbody = $("#third-party-tbody-vehicles");

	var icon1 = document.createElement("img");
	icon1.src = "../../../assets/ic_message.svg";

	var ic_notebook = document.createElement("img");
	ic_notebook.src = "../../../assets/ic_notebook.svg";

	for (var i = 0; i < vehicles.length; i++) {
		var driver = vehicles[i]["driver_id"]
		var third_state = "Sin estado (sin conductor asignado)"
		var full_name_driver = "Sin conductor asignado"
		var btnHistorical = "<td width='5%'> <a id = 'btnHistorical"+ driver.id  + "' href='' data-toggle='modal' data-target='#historicalDriverModal' onclick='seeHistoricalThrid("+JSON.stringify(driver)+"); return 0;'><img src='"+ic_notebook.src+"'/></a></td>";
		if (driver) {
			third_state = vehicles[i]["thirdstate"]
			full_name_driver = vehicles[i]["drivername"]
			var btnChat = "<td width='5%'> <a id = 'btnChat"+ driver  + "' href='' data-toggle='modal' data-target='#chatDriverModal' onclick='showChatDriver("+driver+")'><img src='"+icon1.src+"'/></a></td>";
		}else{
			var btnChat = "<td width='10%'></td>";
		}
		tbody.append('<tr><td width="10%">'+vehicles[i].placa+'</td>'+
		'<td width="5%">'+vehicles[i]["carconfig"] +'</td>'+
		'<td width="15%">'+third_state+'</td>'+
		'<td width="25%">'+full_name_driver+'<br><i class="fas fa-mobile-alt"></i> '+vehicles[i]["mobile"]+'</td>'+
		'<td width="15%">'+vehicles[i]["driver_thirdstate"]+'</td>'+
		'<td width="5%">'+(vehicles[i]["last_request_id"]==0 ? "-" : "R"+vehicles[i]["last_request_id"])+'</td>'+
		'<td width="15%">'+vehicles[i]["requeststatus"]+'</td>'+
		btnHistorical+
		btnChat+
		'<td style="display:none; visibility:hidden">'+vehicles[i]["route_image"]+'</td>'+
		'</tr>');
	}
}

function showChatDriver(driver) {
	$.ajax({
		url: '/api/v1/logs/getchat/'+driver,
		type: "GET",
		dataType: "json",
		async: false,
		success: function(result) {
			if (result.success == true) {

				driver = result.data.driver
				driver.chat_office = result.data.chat

				$("#chatDriverModalLabel").html("Chat de conductor "+ driver.full_name)
				var contentMessagesChatDriver = document.getElementById("contentMessagesChatDriver");
				contentMessagesChatDriver.innerHTML = null;
				var messages = result.data.chat
				if (messages != null)
				for(var i = 0; i < messages.length; i++) {
					if (messages[i].user_id == $("#currentUserId").val() || messages[i].person_id == driver.id) {
						var messageDriver = document.createElement("div");
						if (messages[i].user_id == $("#currentUserId").val()) {
							messageDriver.setAttribute("class", "col-md-10 col-md-offset-2 ownMessage text-right");
						}else{
							messageDriver.setAttribute("class", "col-md-10 messageDriver");
						}
						var messageChat = document.createElement("p");
						messageChat.setAttribute("class", "messageChat");
						messageChat.innerHTML = messages[i].message;
						messageDriver.append(messageChat);
						contentMessagesChatDriver.append(messageDriver);
					}
				}
				document.getElementById("btnSendMessageChat").setAttribute("onclick", "saveMessageChat("+JSON.stringify(driver)+ ", '/notifications/wall/messages/add/comments', 'office')");

			}else{
				alert(result.message);
				value = result;
			}
		},
		error: function(result){
			console.log(result);
			alert(result);
			value = result;
		}
	})
	return 0;
}

function saveMessageChat(driver, add_comment_url, chat_by) {

	var contentNewMessageToDriver = $(".contentNewMessageToDriver");
	if(contentNewMessageToDriver.val()) {
		var user_id = $("#currentUserId").val();
		chat_id = driver.chat_office.id;
			try {
				var new_messages = JSON.parse(driver.chat_office.note);
			} catch (e) {
				var new_messages = driver.chat_office.note;
			}
			new_messages.push(JSON.parse('{"user_id": "'+ user_id + '", "message": "' + contentNewMessageToDriver.val() + '"}'));
			driver.chat_office.note = new_messages;
			add_new_comment(chat_id, null, user_id, null, true, contentNewMessageToDriver.val(), driver.id, add_comment_url, chat_by);

		contentNewMessageToDriver.css("border-color", "#ccc");
		contentNewMessageToDriver.val(null);
		contentNewMessageToDriver.focus();
	}else{
		contentNewMessageToDriver.css("border-color", "#FF3C3A");
	}
} // end saveMessageChat

function view_icon_car(config) {
	switch (config) {
		case "11":
			return "1.png"
		case "12":
			return "2.png"
		case "3":
			return "7.png"
		case "2s2":
			return "5.png"
		case "2s3":
			return "6.png"
		case "3s2":
			return "8.png"
		case "2":
			return "4.png"
		case "4":
			return "10"
		case "13":
			return "9"
		case "3s3":
			return "9.png"
	}
}

function load_office_markers_gmap(vehicles) {
	map = Gmaps.build('Google');
	map.buildMap({ provider: { mapTypeControl: false }, internal: {id: 'mapOffice'}}, function(){
		markers = map.addMarkers(vehicles);
		map.bounds.extendWith(markers);
		setInterval(function(){
			console.log("Starting setInterval map...");
			if (!vehicles_filter.length) {
				let vehicles = set_vehicles();
				for (var i = 0; i < markers.length; i++) {
					markers[i].setMap(null);
					map.removeMarkers(markers);
				}
				markers = [];
				markers = map.addMarkers(vehicles.to_map);
				map.bounds.extendWith(markers);
			}
			console.log("Finish setInterval map.");
		}, 60000 * 12); // cada 12min
		map.fitMapToBounds();
		map.getMap().setZoom(7);
	});
	setInterval(function() {
		for (var i = 0; i < vehicles.length; i++) {
			$('img[src="'+vehicles[i]['picture']['url']+'"]').css({
				'transform': 'rotate(' + vehicles[i]['rotation'] + 'deg)',
				'transform-origin': '50px 50px'
			});
		}
	}, 60000 * 12); // cada 12min
}

function set_vehicles(){
	var value = ""
	$.ajax({
		url: '/dashboard/office_set_markers_to_gmap',
		type: "GET",
		dataType: "json",
		async: false,
		data: { office_id: $('#officeId').val(), page: page },
		success: function(result) {
			if (result.success == true) {
				value = { "to_map": result.vehicles_to_map, "to_table": result.vehicles_to_table }
				page = result.page
				tot_cars = result.tPage
			}else{
				alert(result.message);
				value = result;
			}
		},
		error: function(result){
			console.log(result);
			alert(result);
			value = result;
		}
	})
	return value;
}

function search_office_vehicle() {
	$("#tableOfficeVehicles tr").removeClass("highlight");
	var input, filter, table, tr, td, i, txtValue;
	input = document.getElementById("inputOfficeVehicle");
	filter = input.value.toUpperCase();
	table = document.getElementById("tableOfficeVehicles");
	tr = table.getElementsByTagName("tr");
	for (i = 0; i < tr.length; i++) {
		td = tr[i].getElementsByTagName("td")[0];
		if (td) {
			txtValue = td.textContent || td.innerText;
			if (txtValue.toUpperCase().indexOf(filter) > -1) {
				tr[i].style.display = "";
			} else {
				tr[i].style.display = "none";
			}
		}
	}
	for (var j = vehicles.to_map.length - 1; j >= 0; j--) {
		if (vehicles.to_map[j].license_plate) {
			if (vehicles.to_map[j].license_plate.toUpperCase().indexOf(filter) > -1){
				vehicles_filter.push(vehicles.to_map[j]);
			}
		}
	}
	if (vehicles_filter.length > 0) { load_office_markers_gmap(vehicles_filter); }
}

function seeHistoricalThrid(driver){
	$.ajax({
		url: '/api/v1/logs/history/'+driver,
		type: "GET",
		dataType: "json",
		async: false,
		success: function(result) {
			if (result.success == true) {
				logs_user = result.data.user_log
				if (logs_user != []) {
					for (i=0; i<logs_user.length; i++) {
						tr += "<tr><td style='width:50%;'>"+logs_user[i].comment+"</td>"
						tr += "<td>"+result.data.estados[ logs_user[i].thirdstate_id ]+"</td>"
						tr += "<td>"+( logs_user[i].notified_push ? "SI" : "NO") +"</td>"
						tr += "<td>"+logs_user[i].created_at.split(".")[0]+"</td></tr>"
					}
				}
				$("#third-party-tbody-historical-user").html( tr )
				logs_car = result.data.car_log
				if (logs_car != []) {
					for (i=0; i<logs_car.length; i++) {
						tr += "<tr><td style='width:50%;'>"+logs_car[i].comment+"</td>"
						tr += "<td>"+result.data.estados[ logs_car[i].thirdstate_id ]+"</td>"
						tr += "<td>"+( logs_car[i].notified_push ? "SI" : "NO") +"</td>"
						tr += "<td>"+logs_car[i].created_at.split(".")[0]+"</td></tr>"
					}
				}
				$("#third-party-tbody-historical-car").html( tr )
				$("#uprofile").html( result.data.uprofile )
				$("#vprofile").html( result.data.vprofile )
				return 0
			}else{
				console.log(result.message);
				return 0
			}
		},
		error: function(result){
			console.log(result);
		}
	})
	var tbodyHistorical = document.getElementById("tableOfficeVehicles"); 
	tbodyHistorical.innerHTML = null;
	for (var i = driver.revision_logs.length - 1; i >= 0; i--) {
		var tr = "<tr>"
		tr += "</tr>"
		tbodyHistorical.innerHTML = tr;
	}
	if (driver.revision_logs.length == 0) tbodyHistorical.innerHTML = '<tr><td style="width: 100%;" colspan="4">Sin revisión aún</td></tr>'
	return 0
}
;
(function() {


}).call(this);
(function() {


}).call(this);
$(document).on('turbolinks:load', function() {
	
	$("#btn_runt_vehicle").on('click', function() {
	  var placa = $("#vehicle_placa").val();
	  if (placa != '') {
	    $.get('/traffic/runt/vehicle/' + placa + '.json', function(data) {
	      console.log(data);
	      $("#vehicle_carline_id").val(data.line_id);
	      $("#vehicle_carmark_id").val(data.mark_id);
	      $("#vehicle_carmark").val(data.response.VariablesVehiculo.Marca_Vehiculo + "/" + data.response.VariablesVehiculo.Linea_vehiculo);
	      $("#vehicle_model").val(data.response.VariablesVehiculo.Modelo_Vehiculo);
	      $("#vehicle_carcolor_id").val(data.color_id);
	      $("#vehicle_carcolor").val(data.response.VariablesVehiculo.Color_vehiculo);
	      $("#vehicle_service").val(data.response.VariablesVehiculo.Tipo_servicio_vehiculo);
	      $("#vehicle_carconfig").val(data.response.VariablesVehiculo.Clase_Vehiculo);
	      $("#vehicle_cylinder_capacity").val(data.response.VariablesVehiculo.Cilindraje);
	      $("input[name='vehicle[profile[soat][date_expired]]'").val(data.response.VariablesVehiculo.Fecha_vencimiento_SOAT_mas_reciente);
	      $("input[name='vehicle[profile[tecnomecanica][date_expired]]").val(data.response.VariablesVehiculo.Fecha_vencimiento_RTM_mas_reciente);
	    });
	  }
	});

    if ($("select#vehicle_carmark_id").val() == "") {
		$("select#person_city_id option").remove();
		var row = "<option value=\"" + "" + "\">" + "Seleccione Linea" + "</option>";
		$(row).appendTo("select#vehicle_carline_id");
	}
	$("select#vehicle_carmark_id").change(function() {
		var id_value_string = $(this).val();
		if (id_value_string == "") {
			$("select#vehicle_carline_id option").remove();
			var row = "<option value=\"" + "" + "\">" + "Seleccione Linea" + "</option>";
			$(row).appendTo("select#vehicle_carline_id");
		} else {      //Send the request and update vehicle_carline_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/system/tables/carlines/get_carline_by_carmark/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { 	       // Clear all options from vehicle_carline_id select
					$("select#vehicle_carline_id option").remove(); 	 //put in a empty default line
					var row = "<option value=\"" + "" + "\">" + "Seleccione Linea" + "</option>";
					$(row).appendTo("select#vehicle_carline_id");   // Fill vehicle_carline_id select
					$.each(data.data, function(i, j) {
						row = "<option value=\"" + j.id + "\">" + j.value + "</option>";
						$(row).appendTo("select#vehicle_carline_id");
					});
				}
			});
		}
	});
	$("#vehicle_owneri").blur(function(){
		var id_value_string = $(this).val();
		if (id_value_string == "") {
			$("#vehicle_owner_id").val("");
		} else {      //Send the request and update vehicle_carline_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/people/get_person_by_document/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { 	       // Clear all options from vehicle_carline_id select
					if (data.success){
						$("#vehicle_owner_id").val(data.data.id);
						$("#propietario").html('<label></label><h4>'+data.data.name+' '+data.data.lastname+'</h4>')
					} else {
						$("#vehicle_owner_id").val("") ;
						$("#propietario").html('<label></label><h4>Documento no encontrado</h4>')
					}
				}
			});
		}
	});
	$('#owner_id').on('change', function() {
	  var data;
	  data = $('#owner_id option:selected').text();
	  $("#propietario").html('<strong>Propietario:</strong> '+data)
	});
	$("#vehicle_holderi").blur(function(){
		var id_value_string = $(this).val();
		if (id_value_string == "") {
			$("#vehicle_holder_id").val("");
		} else {      //Send the request
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/people/get_person_by_document/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { 	       // Clear all options from vehicle_carline_id select
					if (data.success){
						$("#vehicle_holder_id").val(data.data.id);
						$("#tenedor").html('<label></label><h4>'+data.data.name+' '+data.data.lastname+'</h4>')
					} else {
						$("#vehicle_holder_id").val("") ;
						$("#tenedor").html('<label></label><h4>Documento no encontrado</h4>')
					}
				}
			});
		}
	});
	$('#holder_id').on('change', function() {
	  var data;
	  data = $('#holder_id option:selected').text();
	  $("#holder").html('<strong>Poseedor:</strong> '+data)
	});
	$("#vehicle_driveri").blur(function(){
		var id_value_string = $(this).val();
		if (id_value_string == "") {
			$("#vehicle_driver_id").val("");
		} else {      //Send the request
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/people/get_person_by_document/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { 	       // Clear all options from vehicle_carline_id select
					if (data.success){
						$("#vehicle_driver_id").val(data.data.id);
						$("#conductor").html('<label></label><h4>'+data.data.name+' '+data.data.lastname+'</h4>')
					} else {
						$("#vehicle_driver_id").val("") ;
						$("#conductor").html('<label></label><h4>Documento no encontrado</h4>')
					}
				}
			});
		}
	});
	$('#driver_id').on('change', function() {
	  var data;
	  data = $('#driver_id option:selected').text();
	  $("#driver").html('<strong>Conductor:</strong> '+data)
	});
	$("select#vehicle_for_contingency_department").on("change", function() {
		$.ajax({
			url: "../../cities/by_department",
			type: "GET",
			data: { department_id: $("select#vehicle_for_contingency_department").val() }
		});
	});
})

function change_verification(vehicleId, fieldName, changeVerificationTo, btn) {
	if (vehicleId && fieldName && changeVerificationTo) {
		$.ajax({
			url: '../vehicles/change/verification',
			type: "POST",
			data: { vehicle_id: vehicleId, field_name: fieldName, is_verified: changeVerificationTo },
			success: function(result) {
				if (result.success) {
					var tagParent = btn.parentElement;
					if (changeVerificationTo == "true") {
						tagParent.innerHTML = 'Verificado: <span class="label label-success"><i class="fa fa-check"></i></span> - '
						btn.setAttribute("onclick", "change_verification('"+vehicleId+"', '"+fieldName+"', 'false', this)");
						btn.text = "Cambiar a no verificado";
					}else {
						tagParent.innerHTML = 'Verificado: <span class="label label-danger"><i class="fa fa-close"></i></span> - '
						btn.setAttribute("onclick", "change_verification('"+vehicleId+"', '"+fieldName+"', 'true', this)");
						btn.text = "Cambiar a verificado";
					}
					tagParent.appendChild(btn);
				}else {
					alert(result.message);
				}
			}
		});
	}else{
		console.log('Campos incompletos');
	}
}

$(document).on('turbolinks:load', function() {
	if ( $("#vehicles").length ) {
		$('#vehicles').DataTable( {
			"processing": true,
			"serverSide": true,
			"pageLength": 25,
			"cache": false,
			"language": { "url": "/assets/datatables/datatables_spanish.json" },
			"ajax": {
				'url': "/vehicles"+( filtro!='' ? '?filtro='+filtro : '')+".json",
				'type': 'GET',
				'beforeSend': function (request) {
					request.setRequestHeader("Authorization", 'Bearer '+$('#vehicles').attr('data-toc') );
				}
			}
		});
	}
	if ( $("#vehiclesows").length ) {
		$('#vehiclesows').DataTable( {
			"processing": true,
			"serverSide": true,
			"pageLength": 25,
			"cache": false,
			"language": { "url": "/assets/datatables/datatables_spanish.json" },
			"ajax": {
				'url': "/vehicles/index_owns"+( filtro!='' ? '?filtro='+filtro : '')+".json",
				'type': 'GET',
				'beforeSend': function (request) {
					request.setRequestHeader("Authorization", 'Bearer '+$('#vehiclesows').attr('data-toc') );
				}
			}
		});
	}

});

function validVehicleForVehicleForm(){
	var isSuccess = false;
	var vehicle_for_contingency_city = $("#vehicle_for_contingency_city");
	var vehicle_for_contingency_comment = $('#vehicle_for_contingency_comment');
	var vehicle_for_contingency_department = $("#vehicle_for_contingency_department");
	if (vehicle_for_contingency_department.length) {
		if (vehicle_for_contingency_department.val()) {
			vehicle_for_contingency_department.css("borderColor", "#ccc");
			isSuccess = true;
		}else{
			vehicle_for_contingency_department.css("borderColor", "#FF3C3A");
			$('#err_form').html("Campos incompletos");
			isSuccess = false;
		}
	}
	if (vehicle_for_contingency_city.length) {
		if (vehicle_for_contingency_city.val()) {
			vehicle_for_contingency_city.css("borderColor", "#ccc");
			isSuccess = true;
		}else{
			vehicle_for_contingency_city.css("borderColor", "#FF3C3A");
			$('#err_form').html("Campos incompletos");
			isSuccess = false;
		}
	}
	if (vehicle_for_contingency_comment.length) {
		if (vehicle_for_contingency_comment.val()) {
			vehicle_for_contingency_comment.css("borderColor", "#ccc");
			isSuccess = true;
		}else{
			vehicle_for_contingency_comment.css("borderColor", "#FF3C3A");
			$('#err_form').html("Campos incompletos");
			isSuccess = false;
		}
	}
	return isSuccess;
}

;
$(document).on('turbolinks:load', function() {
	$("select#vehicular_group_carmark_id").change(function() {
		var id_value_string = $(this).val();
		if (id_value_string == "") {
			$("select#vehicular_group_carline_id option").remove();
			var row = "<option value=\"" + "" + "\">" + "Seleccione Linea" + "</option>";
			$(row).appendTo("select#vehicular_group_carline_id");
		} else {      //Send the request and update vehicle_carline_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/system/tables/carlines/get_carline_by_carmark/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { 	       // Clear all options from vehicular_group_carline_id select
					$("select#vehicular_group_carline_id option").remove(); 	 //put in a empty default line
					var row = "<option value=\"" + "" + "\">" + "Seleccione Linea" + "</option>";
					$(row).appendTo("select#vehicular_group_carline_id");   // Fill vehicular_group_carline_id select
					$.each(data.data, function(i, j) {
						row = "<option value=\"" + j.id + "\">" + j.value + "</option>";
						$(row).appendTo("select#vehicular_group_carline_id");
					});
				}
			});
		}
	});
})
;
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
$(document).on('turbolinks:load', function() {

	$('#workshop_bug_report_group_type_id').on('change', function(){
		getTypeGroup($('#workshop_bug_report_group_type_id').val());
	})

	function getTypeGroup(group_type){
		var id_value_string =  group_type
		console.log(id_value_string)
		if (id_value_string == "" && all_group == 'false') {
			$('#div_groups_bug_reports').html('')
		} else if (id_value_string != "") {    //Send the request
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/workshop/bug_reports/get_type_group/' + id_value_string + '.json',
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
					$('#div_groups_bug_reports').html('')
				},
				success: function(data) { // Clear all options from person_city_id select
					console.log(data)
					$('#div_groups_bug_reports').html(data.html)
				}
			});
		}
	}

	$('#workshop_bug_report_type_id').on('change', function(){
		if ($(this).val()==185){
			$('#div_bug_compleja').removeClass('ocultar')
		} else {
			$('#div_bug_compleja').addClass('ocultar')
		}
	});

	$('#workshop_bug_report_apply_all_group').on('change', function(){
		if ($(this).val() ==  'false') {
			$('#div_groups_bug_reports').removeClass('ocultar')
			$('#workshop_bug_report_group').prop('required', true)
		} else {
			$('#div_groups_bug_reports').addClass('ocultar')
			$('#workshop_bug_report_group').prop('required', false)
		}
	})

	$('#addBugDetail').on('click', function(){
		num_bug = parseInt(num_bug);
		console.log(num_bug)

		AddBugDetail();
	});


	$("#div_bug_compleja").on('click', '.delete_bug_detail',  function(){
		$id = $(this).parent().parent().parent().find('.id')
		console.log($id.val())
		console.log(isNaN(parseInt($id.val())))
		if (isNaN(parseInt($id.val()))){
			$(this).parent().parent().parent().remove()
		} else {
			delete_bug_detail($id.val(),$(this))
		}
		
		return false
	})


	function AddBugDetail(){
		var $bug_detail = $("#div_bug_report_detail").clone();
		
		$bug_detail.find('.workshop_validation_task_id').attr('name', 'workshop_bug_report[bug_report_detail_attributes]['+num_bug+'][workshop_validation_task_id]');
		$bug_detail.find('.modified_id').attr('name', 'workshop_bug_report[bug_report_detail_attributes]['+num_bug+'][modified_id]');
		$bug_detail.find('.creator_id').attr('name', 'workshop_bug_report[bug_report_detail_attributes]['+num_bug+'][creator_id]');
		$bug_detail.find('.workshop_action_perform_for_task_id').attr('name', 'workshop_bug_report[bug_report_detail_attributes]['+num_bug+'][workshop_action_perform_for_task_id]');
		$("#another_bug_detail").append($bug_detail);
		num_bug = num_bug + 1;
		console.log(num_bug)
	}

	function delete_bug_detail(id,this1){
		$.ajax({
			dataType: "json",
			cache: false,
			method: 'delete',
			url: '/workshop/bug_reports/destroy_detail/' + id +'.json',
			timeout: 5000,
			'beforeSend': function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+token);
			},
			error: function(XMLHttpRequest, errorTextStatus, error) {
				alert("Failed to submit : " + errorTextStatus + " ;" + error);
			},
			success: function(data) { // Clear all options from person_city_id select
				console.log(data)
				$(this1).parent().parent().parent().remove()
			}
		});
	}
});
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
$(document).on('turbolinks:load', function() {

  $(document).on('change', 'select#workshop_headquarter_department_id', function() {
    var id_value_string = $(this).val();
    var select = $('select.city');
    if (id_value_string == "") {
     $(select).children('option').remove();
     var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
     $(row).appendTo(select);
		} else {      //Send the request and update person_city_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/system/tables/cities/getcitiesbydepartment/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					alert("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { // Clear all options from person_city_id select
					if (data.success){
						$(select).children('option').remove();  //put in a empty default line
						var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
						$(row).appendTo(select);   // Fill person_city_id select
						$.each(data.data, function(i, j) {
							row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
							$(row).appendTo(select);
						});
					}
				}
			});
		}
	});

  if ($('#workshop_headquarter_address_text').length) {
		// para habilitar el campo workshop_headquarter_address_text
		$("#workshop_headquarter_city_id").on('change', function() {
			if ( $("#workshop_headquarter_city_id").val() != "") $('#workshop_headquarter_address_text').removeAttr("disabled")
		});
		// Rellena el campo ciudad
		if ($("#workshop_headquarter_department_id").val() != "") {
			var row = "<option value=\"" + cdad_id + "\" selected >" + cdad + "</option>";
			$(row).appendTo("#workshop_headquarter_city_id");
		}
	}

});


function initializeWorkshopHeadquarter() {
	function geocodeAddress(geocoder, resultsMap) {
		var address = $('#workshop_headquarter_address_text').val()+", "+$('#workshop_headquarter_city_id option:selected').text()+", "+$('#workshop_headquarter_department_id option:selected').text();
		geocoder.geocode({'address': address}, function(results, status) {
      if (status === 'OK') {
       resultsMap.setCenter(results[0].geometry.location);
       marker = new google.maps.Marker({
         map: resultsMap,
         position: results[0].geometry.location,
         draggable: true
       });

       document.getElementById("workshop_headquarter_lat").value = marker.getPosition().lat().toString();
       document.getElementById("workshop_headquarter_lng").value = marker.getPosition().lng().toString();

       google.maps.event.addListener(marker,'dragend',function(event) {
        document.getElementById("workshop_headquarter_lat").value = this.getPosition().lat().toString();
        document.getElementById("workshop_headquarter_lng").value = this.getPosition().lng().toString();
      });
     } else {
       alert('Geocode was not successful for the following reason: ' + status);
     }
   });
	}
	var geocoder = new google.maps.Geocoder();
  if (document.getElementById('workshop_headquarter_address_text') != null ){
    document.getElementById('workshop_headquarter_address_text').addEventListener('change', function() {
      geocodeAddress(geocoder, map);
    });
  }

}

// https://developers.google.com/maps/documentation/javascript/examples/drawing-tools
var map, drawingManager, currentPolygon, currentCircle;

function initMapWorkshopHeadquarter() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: parseFloat($("#workshop_headquarter_lat").val()) || 4.678457, lng: parseFloat($("#workshop_headquarter_lng").val()) || -74.059232},
    zoom: 18
  });

  drawingManager = new google.maps.drawing.DrawingManager({
    drawingMode: google.maps.drawing.OverlayType.POLYGON,
    drawingControl: false,
    circleOptions: {
    	fillColor: '#b4b4ff',
    	fillOpacity: 0.4,
    	strokeColor: "#000000",
    	strokeWeight: 3,
    	clickable: false,
    	editable: true,
    	zIndex: 1
    },
    polygonOptions: {
    	fillColor: '#FF0000',
    	fillOpacity: 0.4,
    	strokeColor: "#000000",
    	strokeWeight: 3,
    	clickable: false,
    	editable: true
    }
  });
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setMap(map);
  }

  if ( $("#workshop_headquarter_lat").val() && parseInt( $("#workshop_headquarter_lat").val()) != 0  && $("#workshop_headquarter_polypoints").val()) {
  	drawingManager.setDrawingMode(null);
    $("#reDraw").removeClass("hide");

    var polygonCoords = [];
    JSON.parse($("#workshop_headquarter_polypoints").val()).forEach(function(polypoint) {
      polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
    })
    var polygon = new google.maps.Polygon({
     paths: polygonCoords,
     strokeColor: '#000000',
     fillColor: '#FF0000',
     fillOpacity: 0.4,
     editable: true
   });
    currentPolygon = polygon;
    google.maps.event.addListener(polygon.getPath(), 'set_at', function() {
     $("#workshop_headquarter_polypoints").val(setPolypoints(polygon.getPath()));
   });
    google.maps.event.addListener(polygon.getPath(), 'insert_at', function() {
     $("#workshop_headquarter_polypoints").val(setPolypoints(polygon.getPath()));
   });
    polygon.setMap(map);

    var center = new google.maps.LatLng($("#workshop_headquarter_lat").val(), $("#workshop_headquarter_lng").val());
    var circle = new google.maps.Circle({
     center: center,
     map: map,
     radius: parseInt($("#address_radio_distance_m").val()),
     fillColor: '#b4b4ff',
     fillOpacity: 0.4,
     strokeColor: "#000000",
     editable: true
   });
    currentCircle = circle;
    google.maps.event.addListener(circle, 'radius_changed', function(){
      $("#address_radio_distance_m").val(parseInt(this.getRadius()));
    })
    google.maps.event.addListener(circle, 'center_changed', function(){
      $("#workshop_headquarter_lat").val(this.getCenter().lat().toString());
      $("#workshop_headquarter_lng").val(this.getCenter().lng().toString());
    })
  }
  google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
  	if (event.type == 'polygon') {
  		drawingManager.setDrawingMode(google.maps.drawing.OverlayType.CIRCLE);
  		$("#reDraw").removeClass("hide");
  		$("#workshop_headquarter_polypoints").val(setPolypoints(event.overlay.getPath()));
  		currentPolygon = event.overlay;
      google.maps.event.addListener(event.overlay.getPath(), 'set_at', function() {
        $("#workshop_headquarter_polypoints").val(setPolypoints(event.overlay.getPath()));
      });
      google.maps.event.addListener(event.overlay.getPath(), 'insert_at', function() {
        $("#workshop_headquarter_polypoints").val(setPolypoints(event.overlay.getPath()));
      });
    }else if (event.type == 'circle') {
      $("#workshop_headquarter_lat").val(event.overlay.getCenter().lat().toString());
      $("#workshop_headquarter_lng").val(event.overlay.getCenter().lng().toString())
      $("#reDraw").removeClass("hide");
      $("#address_radio_distance_m").val(parseInt(event.overlay.getRadius()));
      drawingManager.setDrawingMode(null);
      currentCircle = event.overlay;
      google.maps.event.addListener(event.overlay, 'radius_changed', function(){
       $("#address_radio_distance_m").val(parseInt(this.getRadius()));
     })
      google.maps.event.addListener(event.overlay, 'center_changed', function(){
        $("#workshop_headquarter_lat").val(this.getCenter().lat().toString());
        $("#workshop_headquarter_lng").val(this.getCenter().lng().toString());
      })
    }
  });
  google.maps.event.addDomListener(window, 'load', initializeWorkshopHeadquarter);
}

function initMapWorkshopHeadquarterShow() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: parseFloat($("#workshop_headquarter_lat").val()) || 4.678457, lng: parseFloat($("#workshop_headquarter_lng").val()) || -74.059232},
    zoom: 18
  });
  if ( $("#workshop_headquarter_lat").val() && $("#workshop_headquarter_polypoints").val()) {
    var polygonCoords = [];
    JSON.parse($("#workshop_headquarter_polypoints").val()).forEach(function(polypoint) {
      polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
    })
    var polygon = new google.maps.Polygon({
      paths: polygonCoords,
      strokeColor: '#000000',
      fillColor: '#FF0000',
      fillOpacity: 0.4,
      editable: false
    });
    currentPolygon = polygon;
    google.maps.event.addListener(polygon.getPath(), 'set_at', function() {
      $("#workshop_headquarter_polypoints").val(setPolypoints(polygon.getPath()));
    });
    google.maps.event.addListener(polygon.getPath(), 'insert_at', function() {
      $("#workshop_headquarter_polypoints").val(setPolypoints(polygon.getPath()));
    });
    polygon.setMap(map);
  }
}

function removePolyline() {
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
  }
  if (currentPolygon) {
    currentPolygon.setMap(null);
    currentPolygon = null;
  }
  if (currentCircle) {
    currentCircle.setMap(null);
    currentCircle = null;
  }
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setMap(map);
  }
  $("#reDraw").addClass("hide");
}

function getBoundsForPoly(poly) {
	var bounds = new google.maps.LatLngBounds;
	poly.getPath().forEach(function(latLng) {
		bounds.extend(latLng);
	});
	return bounds;
}

function setPolypoints(path){
	var polypoints = [];
	path.forEach(function(element) {
    polypoints.push('{"lat": "' + element.lat() + '", "lng": "' + element.lng() + '"}');
  })
  return '['+polypoints.toString()+']';
}

function setPolypointsGeo(path){
  var polypoints = [];
  $.each(path, function(k, element) {
    polypoints.push('{"lat": "' + element.lat + '", "lng": "' + element.lng + '"}');
  });
  return '['+polypoints.toString()+']';
}

function validPolyline(){
  // if ($("#address_status").val() == true){
  //   if (!currentPolygon || !currentCircle || !$("#workshop_headquarter_polypoints").val() || !document.getElementById("workshop_headquarter_lat").value || !document.getElementById("workshop_headquarter_lng").value){
  //     alert('Debe agregar una polígono en el mapa para continuar');
  //     return false;
  //   }
  // }
}

;
$(document).on('turbolinks:load', function() {

  $(document).on('change', 'select#workshop_history_of_alert_department_id', function() {
    var id_value_string = $(this).val();
    var select = $('select.city');
    if (id_value_string == "") {
     $(select).children('option').remove();
     var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
     $(row).appendTo(select);
		} else {      //Send the request and update person_city_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/system/tables/cities/getcitiesbydepartment/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					alert("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { // Clear all options from person_city_id select
					if (data.success){
						$(select).children('option').remove();  //put in a empty default line
						var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
						$(row).appendTo(select);   // Fill person_city_id select
						$.each(data.data, function(i, j) {
							row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
							$(row).appendTo(select);
						});
					}
				}
			});
		}
	});

  if ($('#workshop_history_of_alert_address_text').length) {
		// para habilitar el campo workshop_history_of_alert_address_text
		$("#workshop_history_of_alert_city_id").on('change', function() {
			if ( $("#workshop_history_of_alert_city_id").val() != "") $('#workshop_history_of_alert_address_text').removeAttr("disabled")
		});
		// Rellena el campo ciudad
		if ($("#workshop_history_of_alert_department_id").val() != "") {
			var row = "<option value=\"" + cdad_id + "\" selected >" + cdad + "</option>";
			$(row).appendTo("#workshop_history_of_alert_city_id");
		}
	}

});


function initializeHistoryOfAlert() {
	function geocodeAddress(geocoder, resultsMap) {
		var address = $('#workshop_history_of_alert_address_text').val()+", "+$('#workshop_history_of_alert_city_id option:selected').text()+", "+$('#workshop_history_of_alert_department_id option:selected').text();
		geocoder.geocode({'address': address}, function(results, status) {
      if (status === 'OK') {
       resultsMap.setCenter(results[0].geometry.location);
       marker = new google.maps.Marker({
         map: resultsMap,
         position: results[0].geometry.location,
         draggable: true
       });
       document.getElementById("workshop_history_of_alert_lat").value = marker.getPosition().lat().toString();
       document.getElementById("workshop_history_of_alert_lng").value = marker.getPosition().lng().toString();

       google.maps.event.addListener(marker,'dragend',function(event) {
        document.getElementById("workshop_history_of_alert_lat").value = this.getPosition().lat().toString();
        document.getElementById("workshop_history_of_alert_lng").value = this.getPosition().lng().toString();
      });
     } else {
       alert('Geocode was not successful for the following reason: ' + status);
     }
   });
	}
	var geocoder = new google.maps.Geocoder();
  if (document.getElementById('workshop_history_of_alert_address_text') != null ){
    document.getElementById('workshop_history_of_alert_address_text').addEventListener('change', function() {
      geocodeAddress(geocoder, map);
    });
  }

}

// https://developers.google.com/maps/documentation/javascript/examples/drawing-tools
var map, drawingManager, currentPolygon;

function initMapHistoryOfAlert() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: parseFloat($("#workshop_history_of_alert_lat").val()) || 4.678457, lng: parseFloat($("#workshop_history_of_alert_lng").val()) || -74.059232},
    zoom: 18
  });

  drawingManager = new google.maps.drawing.DrawingManager({
    drawingControl: false,
    polygonOptions: {
    	fillColor: '#FF0000',
    	fillOpacity: 0.4,
    	strokeColor: "#000000",
    	strokeWeight: 3,
    	clickable: false,
    	editable: true
    }
  });
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setMap(map);
  }

  if ( $("#workshop_history_of_alert_lat").val() && parseInt( $("#workshop_history_of_alert_lat").val()) != 0  && $("#workshop_history_of_alert_polypoints").val()) {
  	drawingManager.setDrawingMode(null);
    $("#reDraw").removeClass("hide");

    var polygonCoords = [];
    JSON.parse($("#workshop_history_of_alert_polypoints").val()).forEach(function(polypoint) {
      polygonCoords.push({lat: parseFloat(polypoint.lat), lng: parseFloat(polypoint.lng)})
    })
    var polygon = new google.maps.Polygon({
     paths: polygonCoords,
     strokeColor: '#000000',
     fillColor: '#FF0000',
     fillOpacity: 0.4,
     editable: true
   });
    currentPolygon = polygon;
    google.maps.event.addListener(polygon.getPath(), 'set_at', function() {
     $("#workshop_history_of_alert_polypoints").val(setPolypoints(polygon.getPath()));
   });
    google.maps.event.addListener(polygon.getPath(), 'insert_at', function() {
     $("#workshop_history_of_alert_polypoints").val(setPolypoints(polygon.getPath()));
   });
    polygon.setMap(map);

    var center = new google.maps.LatLng($("#workshop_history_of_alert_lat").val(), $("#workshop_history_of_alert_lng").val());

  }
  google.maps.event.addListener(drawingManager, 'overlaycomplete', function(event) {
  	if (event.type == 'polygon') {
  		$("#reDraw").removeClass("hide");
  		$("#workshop_history_of_alert_polypoints").val(setPolypoints(event.overlay.getPath()));
  		currentPolygon = event.overlay;
      google.maps.event.addListener(event.overlay.getPath(), 'set_at', function() {
        $("#workshop_history_of_alert_polypoints").val(setPolypoints(event.overlay.getPath()));
      });
      google.maps.event.addListener(event.overlay.getPath(), 'insert_at', function() {
        $("#workshop_history_of_alert_polypoints").val(setPolypoints(event.overlay.getPath()));
      });
      drawingManager.setDrawingMode(null);
    }
  });
  google.maps.event.addDomListener(window, 'load', initializeHistoryOfAlert);
}

function initMapHistoryOfAlertShow() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: parseFloat($("#workshop_history_of_alert_lat").val()) || 4.678457, lng: parseFloat($("#workshop_history_of_alert_lng").val()) || -74.059232},
    zoom: 18
  });
  marker = new google.maps.Marker({
    map: map,
    position: {lat: parseFloat($("#workshop_history_of_alert_lat").val()) || 4.678457, lng: parseFloat($("#workshop_history_of_alert_lng").val()) || -74.059232},
    draggable: true
  });
  marker.setMap(map);
}

function removePolyline() {
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setDrawingMode(google.maps.drawing.OverlayType.POLYGON);
  }
  if (currentPolygon) {
    currentPolygon.setMap(null);
    currentPolygon = null;
  }
  if ($("#route_checkpoint_check").length == 0){
    drawingManager.setMap(map);
  }
  $("#reDraw").addClass("hide");
}

function getBoundsForPoly(poly) {
	var bounds = new google.maps.LatLngBounds;
	poly.getPath().forEach(function(latLng) {
		bounds.extend(latLng);
	});
	return bounds;
}

function setPolypoints(path){
	var polypoints = [];
	path.forEach(function(element) {
    polypoints.push('{"lat": "' + element.lat() + '", "lng": "' + element.lng() + '"}');
  })
  return '['+polypoints.toString()+']';
}

function setPolypointsGeo(path){
  var polypoints = [];
  $.each(path, function(k, element) {
    polypoints.push('{"lat": "' + element.lat + '", "lng": "' + element.lng + '"}');
  });
  return '['+polypoints.toString()+']';
}

function initMapMultiplePointsShow() {
  map = new google.maps.Map(document.getElementById('map'), {
    center: {lat: parseFloat(points[0][0]) || 4.678457, lng: parseFloat(points[0][1]) || -74.059232},
    zoom: 15
  });
  for (var i = 0; i < points.length; i++) {
    marker = new google.maps.Marker({
      map: map,
      position: {lat: parseFloat(points[i][0]) || 4.678457, lng: parseFloat(points[i][1]) || -74.059232},
      draggable: true
    });
    marker.setMap(map);
  }
}
;
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
$(document).on('turbolinks:load', function() {

	$('#workshop_maintenance_routine_group_type_id').on('change', function(){
		getTypeGroup($('#workshop_maintenance_routine_group_type_id').val());
	})

	function getTypeGroup(group_type){
		var id_value_string =  group_type
		console.log(id_value_string)
		if (id_value_string == "" && all_group == 'false') {
			$('#div_groups_routines').html('')
		} else if (id_value_string != "") {    //Send the request
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/workshop/maintenance_routines/get_type_group/' + id_value_string + '.json',
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
					$('#div_groups_routines').html('')
				},
				success: function(data) { // Clear all options from person_city_id select
					console.log(data)
					$('#div_groups_routines').html(data.html)
				}
			});
		}
	}

	$('#addRoutineException').on('click', function(){
		AddExceptionRoutine();
	})

	function AddExceptionRoutine(){
		console.log(numExceptionRoutine)
		var $exception = $('#add_periodicity_routine').find("#exception_div").clone();
		var val = $('#workshop_maintenance_routine_group_type_id').val();
		$exception.find('.change_id_routine').attr('id', 'select_exception_routine_' + numExceptionRoutine)
		$exception.find('.kms_of_execution').attr('name', 'workshop_maintenance_routine[maintenance_routine_periodicity_attributes]['+numExceptionRoutine+'][kms]')
		$exception.find('.type_periodicity').attr('name', 'workshop_maintenance_routine[maintenance_routine_periodicity_attributes]['+numExceptionRoutine+'][type_periodicity_id]')
		$exception.find('.periodicity').attr('name', 'workshop_maintenance_routine[maintenance_routine_periodicity_attributes]['+numExceptionRoutine+'][periodicity]')
		$exception.find('.type_item_id').attr('name', 'workshop_maintenance_routine[maintenance_routine_periodicity_attributes]['+numExceptionRoutine+'][type_item_id]')
		$exception.find('.item_type').attr('name', 'workshop_maintenance_routine[maintenance_routine_periodicity_attributes]['+numExceptionRoutine+'][item_type]')
		$exception.find('.type_item_id').val(val)
		$exception.find('.item_type').val(val)
		switch (parseInt(val)) {
			case 229:
			console.log('229')
			$exception.find('.change_id_routine').attr('name', 'workshop_maintenance_routine[maintenance_routine_periodicity_attributes]['+numExceptionRoutine+'][vehicle_id]')
			break;
			case 230:
			console.log('230')
			$exception.find('.change_id_routine').attr('name', 'workshop_maintenance_routine[maintenance_routine_periodicity_attributes]['+numExceptionRoutine+'][trailer_id]')
			break;
			case 231:
			console.log('231')
			$exception.find('.change_id_routine').attr('name', 'workshop_maintenance_routine[maintenance_routine_periodicity_attributes]['+numExceptionRoutine+'][item_id]')
			break;
			case 232:
			console.log('232')
			$exception.find('.change_id_routine').attr('name', 'workshop_maintenance_routine[maintenance_routine_periodicity_attributes]['+numExceptionRoutine+'][item_id]')
			break;
			case 233:
			console.log('233')
			$exception.find('.change_id_routine').attr('name', 'workshop_maintenance_routine[maintenance_routine_periodicity_attributes]['+numExceptionRoutine+'][item_id]')
			break;
			case 234:
			console.log('234')
			$exception.find('.change_id_routine').attr('name', 'workshop_maintenance_routine[maintenance_routine_periodicity_attributes]['+numExceptionRoutine+'][item_id]')
			break;

		}
		$("#another_routine_exceptions").append($exception)
		switch (parseInt(val)) {
			case 229:
			console.log('229')
			set_select('select_exception_routine_' + numExceptionRoutine, vehicles)
			break;
			case 230:
			console.log('230')
			set_select('select_exception_routine_' + numExceptionRoutine, trailers)
			break;
			case 231:
			console.log('231')
			set_select('select_exception_routine_' + numExceptionRoutine, tools)
			break;
			case 232:
			console.log('232')
			set_select('select_exception_routine_' + numExceptionRoutine, headquarters)
			break;
			case 233:
			console.log('233')
			set_select('select_exception_routine_' + numExceptionRoutine, warehouse)
			case 234:
			console.log('234')
			set_select('select_exception_routine_' + numExceptionRoutine, replacements)
			break;

		}
		set_select2_routine(numExceptionRoutine)
		numExceptionRoutine++;
	}

	function set_select2_routine(id){
		$('#select_exception_routine_' + id).select2({
			language: "es"
		});
	}

	function set_select(select_id, data){
		console.log(select_id)
		console.log(data)
		$('#' +select_id).children('option').remove();
		$.each(data, function(key, value) {
			console.log(key)
			console.log(value['name'])
			$('#' +select_id)
			.append($("<option></option>")
				.attr("value", value['id'])
				.text(value['name'])); 
		});
		$('#' +select_id).trigger('change')

	}

	
	$('#addRoutineTask').on('click', function(){
		numTaskRoutine = parseInt(numTaskRoutine)
		AddRoutineTask();
	})

	function AddRoutineTask(){
		console.log(numTaskRoutine)
		var $task = $('#add_task_routine').find("#routine_task_div").clone();
		$task.find('.task_name').attr('name', 'workshop_maintenance_routine[maintenance_routine_task_attributes]['+numTaskRoutine+'][workshop_maintenance_task_id]')
		$task.find('.sort').attr('name', 'workshop_maintenance_routine[maintenance_routine_task_attributes]['+numTaskRoutine+'][sort]')
		$task.find('.sort').val(parseInt(numTaskRoutine+1))		
		$("#another_routine_task").append($task)
		numTaskRoutine++;
	}

	$("#divs_exceptions").on('click', '.delete_routine_exception',  function(){
		$id = $(this).parent().parent().parent().find('.id')
		console.log($id.val())
		console.log(isNaN(parseInt($id.val())))
		if (isNaN(parseInt($id.val()))){
			$(this).parent().parent().parent().remove()
		} else {
			delete_peridicity_routine($id.val(),$(this))
		}
		
		return false
	})
	function delete_peridicity_routine(id,this1){
		$.ajax({
			dataType: "json",
			cache: false,
			method: 'delete',
			url: '/workshop/maintenance_routines/destroy_periodicity/' + id +'.json',
			timeout: 5000,
			'beforeSend': function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+token);
			},
			error: function(XMLHttpRequest, errorTextStatus, error) {
				alert("Failed to submit : " + errorTextStatus + " ;" + error);
			},
			success: function(data) { // Clear all options from person_city_id select
				console.log(data)
				$(this1).parent().parent().parent().remove()
			}
		});
	}


	$("#divs_routine_task").on('click', '.delete_routine_task',  function(){
		$id = $(this).parent().parent().parent().find('.id')
		console.log($id.val())
		console.log(isNaN(parseInt($id.val())))
		if (isNaN(parseInt($id.val()))){
			$(this).parent().parent().parent().remove()
		} else {
			delete_task_routine($id.val(),$(this))
		}
		
		return false
	})
	function delete_task_routine(id,this1){
		$.ajax({
			dataType: "json",
			cache: false,
			method: 'delete',
			url: '/workshop/maintenance_routines/destroy_task_routine/' + id +'.json',
			timeout: 5000,
			'beforeSend': function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+token);
			},
			error: function(XMLHttpRequest, errorTextStatus, error) {
				alert("Failed to submit : " + errorTextStatus + " ;" + error);
			},
			success: function(data) { // Clear all options from person_city_id select
				console.log(data)
				$(this1).parent().parent().parent().remove()
			}
		});
	}

});
$(document).on('turbolinks:load', function() {

	$(document).on('change', 'select#workshop_maintenance_task_workshop_system_id', function() {
		var id_value_string = $(this).val();
		var select = $('select#workshop_maintenance_task_workshop_subsystem_id');
		if (id_value_string == "") {
			$(select).children('option').remove();
			var row = "<option value=\"" + "" + "\">" + "Seleccione Subsistema" + "</option>";
			$(row).appendTo(select);
		} else {      //Send the request and update person_city_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/workshop/subsystems/get_subsystems_by_system/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					alert("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { // Clear all options from person_city_id select
					console.log(data)
					if (data.success){
						$(select).children('option').remove();  //put in a empty default line
						var row = "<option value=\"" + "" + "\">" + "Seleccione Subsistema" + "</option>";
						$(row).appendTo(select);   // Fill person_city_id select
						$.each(data.data, function(i, j) {
							row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
							$(row).appendTo(select);
						});
					}
				}
			});
		}
	});

	$('#workshop_maintenance_task_requires_outside_work').on('change', function(){
		if ($(this).val() == 'false'){
			$('#div_subtask').addClass('ocultar')
			$('#workshop_maintenance_task_sub_tasks').prop('required',false);
		} else {
			$('#div_subtask').removeClass('ocultar')
			$('#workshop_maintenance_task_sub_tasks').prop('required',true);
		}
	});

	$('#workshop_maintenance_task_level_of_complexity_id').on('change', function(){
		if ($(this).val() == 185){
			console.log('complejo')
			$('#div_tasks_complexities').removeClass('ocultar')
		} else {
			console.log('sencillo')
			$('#div_tasks_complexities').addClass('ocultar')
		}
	});

	$('#vehicular_group').on('change', function(){
		addFieldGroups($('#workshop_maintenance_task_apply_all_group').val(), 'vehicular');
	})

	$('#trailer_group').on('change', function(){
		addFieldGroups($('#apply_all_group_trailer').val(), 'trailer');
	})

	$('#apply_all_group_trailer').on('change', function(){
		if ($(this).val() == 'true'){
			$('#trailer_group_input').attr('hidden', true)
			$('#trailer_group').prop('required',false);
		} else {
			$('#trailer_group_input').removeAttr('hidden')
			$('#trailer_group').prop('required',true);
		}
		addFieldGroups($('#apply_all_group_trailer').val(), 'trailer');
	});

	$('#workshop_maintenance_task_apply_all_group').on('change', function(){
		if ($(this).val() == 'true'){
			$('#vehicular_group_input').attr('hidden', true)
			$('#vehicular_group').prop('required',false);
		} else {
			$('#vehicular_group_input').removeAttr('hidden')
			$('#vehicular_group').prop('required',true);
		}
		addFieldGroups($('#workshop_maintenance_task_apply_all_group').val(), 'vehicular');
	});

	function addFieldGroups(all_group, option){
		if (option == 'trailer') {
			var id_value_string =  $('#trailer_group').val();
			var element_type_id = 230
		}else if (option == 'vehicular') {
			var id_value_string =  $('#vehicular_group').val();
			var element_type_id = 229
		}
		console.log(all_group)
		console.log(id_value_string)
		if (id_value_string == "" && all_group == 'false' && element_type_id == 229) {
			$('#div_group_vehicular').html('')
		} else if (id_value_string != "" && all_group == 'false' && element_type_id == 229) {    //Send the request
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/workshop/maintenance_tasks/get_groups/' + all_group + '/' + id_value_string + '/' + element_type_id,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
					$('#div_group_vehicular').html('')
				},
				success: function(data) { // Clear all options from person_city_id select
					console.log(data)
					$('#div_group_vehicular').html(data.html)
				}
			});
		} else if (all_group == 'true' && element_type_id == 229) {    //Send the request
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/workshop/maintenance_tasks/get_groups/' + all_group + '/nil'  + '/' + element_type_id,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
					$('#div_group_vehicular').html('')
				},
				success: function(data) { // Clear all options from person_city_id select
					console.log(data)
					$('#div_group_vehicular').html(data.html)
				}
			});
		}

		if (id_value_string == "" && all_group == 'false' && element_type_id == 230) {
			$('#div_group_trailer').html('')
		} else if (id_value_string != "" && all_group == 'false' && element_type_id == 230) {    //Send the request
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/workshop/maintenance_tasks/get_groups/' + all_group + '/' + id_value_string + '/' + element_type_id,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
					$('#div_group_trailer').html('')
				},
				success: function(data) { // Clear all options from person_city_id select
					console.log(data)
					$('#div_group_trailer').html(data.html)
				}
			});
		} else if (all_group == 'true' && element_type_id == 230) {    //Send the request
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/workshop/maintenance_tasks/get_groups/' + all_group + '/nil'  + '/' + element_type_id,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
					$('#div_group_trailer').html('')
				},
				success: function(data) { // Clear all options from person_city_id select
					console.log(data)
					$('#div_group_trailer').html(data.html)
				}
			});
		}
	}

	$('#number_person').on('change', function(){
		var number = $(this).val()
		console.log(number)
		if (number >= 1){
			AddNroSpeciality(number)

		} else {
			$("#div_specialty").html('')
			$('#div_specialty_env').addClass('ocultar')
		}
	});

	$('#addTask').on('click', function(){
		AddTask();
	})

	$('#addTool').on('click', function(){
		AddTool();
	})

	$('#addSupply').on('click', function(){
		AddSupply();
	})

	$('#addReplacement').on('click', function(){
		AddReplacement();
	})

	$("#divs_tools").on('click', '.delete_tool',  function(){
		$(this).parent().parent().parent().remove()
		return false
	})

	$("#divs_supplies").on('click', '.delete_supply',  function(){
		$(this).parent().parent().parent().remove()
		return false
	})

	$("#divs_replacements").on('click', '.delete_replacement',  function(){
		$(this).parent().parent().parent().remove()
		return false
	})

	$("#div_tasks_complexities").on('click', '.delete_task',  function(){
		$(this).parent().parent().parent().remove()
		return false
	})

	function AddNroSpeciality(number){
		$("#div_specialty").html('')
		if (number > 0) {
			$('#div_specialty_env').removeClass('ocultar');
			for (var i=0; i<number; i++) {
				var $specialty = $("#div_form_speciality").clone();
				$specialty.find('.specialty_id').attr('name', 'workshop_maintenance_task[number_people][details][][specialty_id]');
				$specialty.find('.number_person').attr('name', 'workshop_maintenance_task[number_people][details][][number]');
				$("#div_specialty").append($specialty)
			}
		} else {
			$('#div_specialty_env').addClass('ocultar')
		}
	}

	function AddTool(){
		var $tool = $("#tool_div").clone();
		$("#another_tools").append($tool)
	}


	function AddSupply(){
		var $supply = $("#supply_div").clone();
		$("#another_supplies").append($supply)
	}

	function AddReplacement(){
		var $replacement = $("#replacement_div").clone();
		$("#another_replacements").append($replacement)
	}

	function AddTask(){
		var $task = $("#task_div").clone();
		var numItems = $('.tasks_complexity_numbers').length
		console.log(numItems)
		$task.find('.order').val(numItems)
		$task.find('.task_id').prop('required', true)
		$task.find('.order').prop('required', true)
		$("#another_tasks").append($task)
	}


});
(function() {


}).call(this);
(function() {


}).call(this);
$(document).on('turbolinks:load', function() {

	$('#workshop_preoperational_inspection_group_type_id').on('change', function(){
		getTypeGroupPreoperational($('#workshop_preoperational_inspection_group_type_id').val());
	})

	function getTypeGroupPreoperational(group_type){
		var id_value_string =  group_type
		console.log(id_value_string)
		if (id_value_string == "" && all_group == 'false') {
			$('#div_groups_routines').html('')
		} else if (id_value_string != "") {    //Send the request
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/workshop/preoperational_inspections/get_type_group/' + id_value_string + '.json',
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
					$('#div_groups_preoperationals').html('')
				},
				success: function(data) { // Clear all options from person_city_id select
					$('#div_groups_preoperationals').html(data.html)
				}
			});
		}
	}


	function getTypeGroupPreoperationalEdit(group_type, selected_values){
		console.log(selected_values)
		var id_value_string =  group_type
		console.log(id_value_string)
		if (id_value_string == "" && all_group == 'false') {
			$('#div_groups_routines').html('')
		} else if (id_value_string != "") {    //Send the request
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/workshop/preoperational_inspections/get_type_group/' + id_value_string + '.json',
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
					$('#edit_form_preoperacional_modal').find('#div_groups_preoperationals').html('')
				},
				success: function(data) { // Clear all options from person_city_id select
					$('#edit_form_preoperacional_modal').find('#div_groups_preoperationals').html(data.html)
					$('#edit_form_preoperacional_modal').find('#workshop_preoperational_inspection_group').val(selected_values)
					$('#edit_form_preoperacional_modal').find('#workshop_preoperational_inspection_group').trigger('change')
				}
			});
		}
	}

	$('.open_edit_inspection').on('click', function(){
		console.log($(this).data( "id" ))
		load_edit_data($(this).data( "id" ))
		$('#edit_form_preoperacional_modal').attr('action', '/workshop/preoperational_inspections/' + $(this).data( "id" ));
	})

	function load_click_edit(){
		$('.open_edit_inspection').on('click', function(){
			console.log($(this).data( "id" ))
			load_edit_data($(this).data( "id" ))
			$('#edit_form_preoperacional_modal').attr('action', '/workshop/preoperational_inspections/' + $(this).data( "id" ));
		})
	}

	function load_edit_data(id){
		 $.ajax({
            dataType: "json",
            cache: false,
            url: '/workshop/preoperational_inspections/'+id+'.json',
            timeout: 5000,
            beforeSend: function (request) {
                request.setRequestHeader("Authorization", 'Bearer '+token);
            },
            error: function(XMLHttpRequest, errorTextStatus, error) {
                alert("Failed to submit : " + errorTextStatus + " ;" + error);
            },
            success: function(data) { 
            	console.log(data)
                if (data.success){
                	values = data.data
                	$('#edit_form_preoperacional_modal').find('#workshop_preoperational_inspection_name').val(values.name)
                	$('#edit_form_preoperacional_modal').find('#workshop_preoperational_inspection_type_id').val(values.type_id)
                	$('#edit_form_preoperacional_modal').find('#workshop_preoperational_inspection_group_type_id').val(values.group_type_id)
                	$('#edit_form_preoperacional_modal').find('#workshop_preoperational_inspection_periodicity_id').val(values.periodicity_id)
                	$('#edit_form_preoperacional_modal').find('#workshop_preoperational_inspection_periodicity_number').val(values.periodicity_number)
                	$('#edit_form_preoperacional_modal').find('#workshop_preoperational_inspection_status').val(values.status.toString())
                	getTypeGroupPreoperationalEdit(values.group_type_id, values.group);
                	load_click_edit();               	
					
                }
            }
        });
	}

	$('#workshop_preoperational_inspection_apply_all_group').on('change', function(){
		if ($(this).val() ==  'false') {
			$('#div_groups_preoperationals').removeClass('ocultar')
			$('#workshop_preoperational_inspection_group').prop('required', true)
		} else {
			$('#div_groups_preoperationals').addClass('ocultar')
			$('#workshop_preoperational_inspection_group').prop('required', false)
		}
	})

});
(function() {


}).call(this);
$(document).on('turbolinks:load', function() {
	var numeroSequence = 0
	$('.open_edit_item').on('click', function(){
		numeroSequence = 0
		console.log($(this).data( "id" ))
		load_edit_data_item($(this).data( "id" ))
		$('#form_edit_preoperacional_item_modal').attr('action', '/workshop/preoperational_items/' + $(this).data( "id" ));
	})

	function load_click_edit(){
		$('.open_edit_item').on('click', function(){
			numeroSequence = 0
			console.log($(this).data( "id" ))
			load_edit_data_item($(this).data( "id" ))
			$('#form_edit_preoperacional_item_modal').attr('action', '/workshop/preoperational_items/' + $(this).data( "id" ));
		})
	}

	$('.AddFormItemPreoperational').on('click', function(){
		numeroSequence = 0
	})

	function load_edit_data_item(id){
		$.ajax({
			dataType: "json",
			cache: false,
			url: '/workshop/preoperational_items/get/'+id+'.json',
			timeout: 5000,
			beforeSend: function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+token);
			},
			error: function(XMLHttpRequest, errorTextStatus, error) {
				alert("Failed to submit : " + errorTextStatus + " ;" + error);
			},
			success: function(data) { 
				console.log(data)
				if (data.success){
					values = data.data
					$('#form_edit_preoperacional_item_modal').find('#workshop_preoperational_item_name').val(values.name)
					$('#form_edit_preoperacional_item_modal').find('#workshop_preoperational_item_required').val(values.required.toString())
					$('#form_edit_preoperacional_item_modal').find('#workshop_preoperational_item_status').val(values.status)
					try {
						$('#form_edit_preoperacional_item_modal').find('#workshop_preoperational_item_have_comment').val(values.have_comment.toString())
						$('#form_edit_preoperacional_item_modal').find('#workshop_preoperational_item_has_attachments').val(values.has_attachments.toString())
					} catch(e){

					}
					$('#form_edit_preoperacional_item_modal').find("#another_options").html('')
					$.each( values.item_options, function( index, value ){
						EditItemOption(value)

					});
					load_click_edit();
				}
			}
		});
	}

	$('.addItemOption').on('click', function(){
		id = $(this).parent().parent().parent().parent().parent().parent().parent().parent().parent().parent().attr("id")
		AddItemOption(id);
	})

	function AddItemOption(id){
		var $option = $('#row_option').find("#div_item_option").clone();
		$option.find('.name').prop('required', true)
		$option.find('.name').attr('id', 'name_option' + numeroSequence)
		$option.find('.name').attr('is_critical', 'name_option' + numeroSequence)
		$option.find('.name').attr('name', 'workshop_preoperational_item[item_options_attributes]['+numeroSequence+'][name]')
		$option.find('.id').attr('name', 'workshop_preoperational_item[item_options_attributes]['+numeroSequence+'][id]')
		$option.find('.is_critical').attr('name', 'workshop_preoperational_item[item_options_attributes]['+numeroSequence+'][is_critical]')
		$('#'+id).find("#another_options").append($option)
		numeroSequence++
	}

	function EditItemOption(data){
		var $option = $('#row_option').find("#div_item_option").clone();
		$option.find('.name').prop('required', true)
		$option.find('.name').attr('id', 'name_option' + numeroSequence)
		$option.find('.name').attr('is_critical', 'name_option' + numeroSequence)
		$option.find('.name').attr('name', 'workshop_preoperational_item[item_options_attributes]['+numeroSequence+'][name]')
		$option.find('.name').attr('value', data.name)
		$option.find('.id').attr('name', 'workshop_preoperational_item[item_options_attributes]['+numeroSequence+'][id]')
		$option.find('.id').attr('value', data.id)
		$option.find('.is_critical').attr('name', 'workshop_preoperational_item[item_options_attributes]['+numeroSequence+'][is_critical]')
		$option.find('.is_critical').attr('checked', data.is_critical)
		$('#form_edit_preoperacional_item_modal').find("#another_options").append($option)
		delete_edit_option();
		numeroSequence++
	}
	$("#form_preoperacional_item_modal").on('click', '.delete_item_option',  function(){
		$id = $(this).parent().parent().find('.id')
		console.log($id.val())
		console.log(isNaN(parseInt($id.val())))
		$(this).parent().parent().parent().remove()
		if (isNaN(parseInt($id.val()))){
			$(this).parent().parent().parent().remove()
		} else {
			delete_item_option($id.val(),$(this))
		}
		return false
	})
	function delete_edit_option(){
		$("#form_edit_preoperacional_item_modal").on('click', '.delete_item_option',  function(){
			$id = $(this).parent().parent().find('.id')
			console.log($id.val())
			console.log(isNaN(parseInt($id.val())))
			$(this).parent().parent().parent().remove()
			if (isNaN(parseInt($id.val()))){
				$(this).parent().parent().parent().remove()
			} else {
				delete_item_option($id.val(),$(this))
			}
			return false
		})
	}
	
	function delete_item_option(id,this1){
		$.ajax({
			dataType: "json",
			cache: false,
			method: 'delete',
			url: '/workshop/preoperational_items/destroy_option/' + id +'.json',
			timeout: 5000,
			'beforeSend': function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+token);
			},
			error: function(XMLHttpRequest, errorTextStatus, error) {
				alert("Failed to submit : " + errorTextStatus + " ;" + error);
			},
			success: function(data) { // Clear all options from person_city_id select
				console.log(data)
				$(this1).parent().parent().parent().remove()
			}
		});
	}

});
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
$(document).on('turbolinks:load', function() {

	$('#workshop_routine_sequence_group_type_id').on('change', function(){
		getTypeGroup($('#workshop_routine_sequence_group_type_id').val());
	})

	function getTypeGroup(group_type){
		var id_value_string =  group_type
		console.log(id_value_string)
		if (id_value_string == "" && all_group == 'false') {
			$('#div_groups_routines').html('')
		} else if (id_value_string != "") {    //Send the request
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/workshop/routine_sequences/get_type_group/' + id_value_string + '.json',
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
					$('#div_groups_routines').html('')
				},
				success: function(data) { // Clear all options from person_city_id select
					console.log(data)
					$('#div_groups_routines').html(data.html)
				}
			});
		}
	}

	$('#addSequenceRoutine').on('click', function(){
		AddSequenceRoutine();
	})

	function AddSequenceRoutine(){
		console.log(numeroSequence)
		var $sequence = $('#add_sequence_routine').find("#sequence_routine_div").clone();
		var val = $('#workshop_maintenance_routine_group_type_id').val();
		$sequence.find('.select_routine_class').attr('id', 'select_sequence_routine_' + numeroSequence)
		$sequence.find('.select_routine_class').attr('name', 'workshop_routine_sequence[routine_sequence_detail_attributes]['+numeroSequence+'][workshop_maintenance_routine_id]')
		$sequence.find('.select_next_routine_class').attr('name', 'workshop_routine_sequence[routine_sequence_detail_attributes]['+numeroSequence+'][next_seq_position]')
		$sequence.find('.position').attr('name', 'workshop_routine_sequence[routine_sequence_detail_attributes]['+numeroSequence+'][position]')
		$sequence.find('.position').val(numeroSequence);
		$sequence.find('.kms_of_execution').attr('name', 'workshop_routine_sequence[routine_sequence_detail_attributes]['+numeroSequence+'][kms]')
		$sequence.find('.type_periodicity_id').attr('name', 'workshop_routine_sequence[routine_sequence_detail_attributes]['+numeroSequence+'][type_periodicity_id]')
		$sequence.find('.periodicity').attr('name', 'workshop_routine_sequence[routine_sequence_detail_attributes]['+numeroSequence+'][periodicity]')
		type_sequence_routine = $('#workshop_routine_sequence_sequence_type_id').val()
		if (parseInt(type_sequence_routine) == 244) {
			$('#routines_sequence').find('.seq_next').addClass('ocultar')
			$('#routines_sequence').find('.select_next_routine_class').attr('required', false)
			$sequence.find('.seq_next').removeClass('ocultar')
			$sequence.find('.select_next_routine_class').attr('required', true)
		}  else {
			$('#routines_sequence').find('.seq_next').addClass('ocultar')
			$('#routines_sequence').find('.select_next_routine_class').attr('required', false);
		}
		

		$("#another_sequences").append($sequence)
		set_select2_routine(numeroSequence)
		set_next_sequence();
		numeroSequence++;
	}

	$("#routines_sequence").on('click', '.delete_sequence_routine',  function(){
		$id = $(this).parent().parent().parent().find('.id')
		console.log($id.val())
		console.log(isNaN(parseInt($id.val())))
		if (isNaN(parseInt($id.val()))){
			$(this).parent().parent().parent().parent().parent().remove()
		} else {
			delete_sequence_routine($id.val(),$(this))
		}		
		return false
	})
	function delete_sequence_routine(id,this1){
		$.ajax({
			dataType: "json",
			cache: false,
			method: 'delete',
			url: '/workshop/routine_sequences/destroy_sequence/' + id +'.json',
			timeout: 5000,
			'beforeSend': function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+token);
			},
			error: function(XMLHttpRequest, errorTextStatus, error) {
				alert("Failed to submit : " + errorTextStatus + " ;" + error);
			},
			success: function(data) { // Clear all options from person_city_id select
				console.log(data)
				$(this1).parent().parent().parent().parent().parent().remove()
			}
		});
	}


	function set_select2_routine(id){
		$('#select_sequence_routine_' + id).select2({
			language: "es"
		});
	}

	function set_next_sequence(){
		var values = [];
		$('#routine_sequence').find('.select_next_routine_class').children('option').remove();
		$('#routines_sequence').find('.position').each(function(index, currentElement) {
			values.push(parseInt($(this).val()));
			$('#routines_sequence').find('.select_next_routine_class:last').append($('<option>').val($(this).val()).text($(this).val()));
		});
			
	
		
		console.log(values);
	}

});
$( document ).on('turbolinks:load', function() {
  $("#workshop_schedule_entry_is_external").on('change', function() {
    var is_external = $( "#workshop_schedule_entry_is_external" ).is(':checked');
    if(is_external==true){
      $("#ingresa_id").addClass("hidden");
      $("#ingresa_externo_id").removeClass("hidden");
    }else
    {
      $("#ingresa_id").removeClass("hidden");
      $("#ingresa_externo_id").addClass("hidden");
    }
  });

  $('#workshop_schedule_entry_element_type_id').on('change', function(){
    console.log('change')
    getTypeEntry($('#workshop_schedule_entry_element_type_id').val());
  })

  function getTypeEntry(entry_tye){
    var id_value_string =  entry_tye
    console.log(id_value_string)
    if (id_value_string == "") {
      $('#ingresa_id').html('')
    } else if (id_value_string != "") {    //Send the request
      $.ajax({
        dataType: "json",
        cache: false,
        url: '/workshop/schedule_entries/get_type_entry/' + id_value_string + '.json',
        timeout: 5000,
        error: function(XMLHttpRequest, errorTextStatus, error) {
          console.log("Failed to submit : " + errorTextStatus + " ;" + error);
          $('#ingresa_id').html('')
        },
        success: function(data) { // Clear all options from person_city_id select
          console.log(data)
          $('#ingresa_id').html(data.html)
        }
      });
    }
  }
})
;
(function() {


}).call(this);
(function() {


}).call(this);
(function() {


}).call(this);
$(document).on('turbolinks:load', function() {


	$('#workshop_supplier_request_doctype_id').on('change', function(){
		if ($(this).val() == 2){
			$('#supplier_lastname_form').addClass('ocultar')
			$('#label_razon_supplier_form').html('Razón social')
			$('#workshop_supplier_request_lastname').prop('required',false);
		} else {
			$('#supplier_lastname_form').removeClass('ocultar')
			$('#label_razon_supplier_form').html('Nombre')
			$('#workshop_supplier_request_lastname').prop('required',true);
		}
	});

	$('#workshop_supplier_request_consignment_inventory_applies').on('change', function(){
		if ($(this).val() == 'true') {
			$('#date_inventory').removeClass('ocultar')
		} else {
			$('#date_inventory').addClass('ocultar')
		}
	});

	

	$('#addHeadquarterSupplies').on('click', function(){
		num_headquarter = parseInt(num_headquarter);
		console.log(num_headquarter)
		AddHeadquarterSupplies();
	})
	$("#divs_headquarters").on('click', '.delete_headquarters_supplies',  function(){
		$(this).parent().parent().parent().remove()
		return false
	})

	function AddHeadquarterSupplies(){
		var $spectec = $('#new_row_headuarter_use').find("#headquarters_div").clone();
		$spectec.find('.dep').attr('name', 'workshop_supplier_request[detail]['+num_headquarter+'][department_id]');
		$spectec.find('.city_supplies').attr('name', 'workshop_supplier_request[detail]['+num_headquarter+'][city_id]');
		$spectec.find('.address_text').attr('name', 'workshop_supplier_request[detail]['+num_headquarter+'][address]');
		$("#another_headquarters_supplies").append($spectec)
		change_department();
		num_headquarter = num_headquarter + 1;
		$('input').keyup(function() {
			$(this).val($(this).val().toUpperCase());
		});
	}

	function change_department(){
		$(document).on('change', 'select.dep', function() {
		    var id_value_string = $(this).val();
		    var select = $(this).parent().parent().find('.city_supplies');
		    console.log(select)
		    if (id_value_string == "") {
		     $(select).children('option').remove();
		     var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
		     $(row).appendTo(select);
				} else {      //Send the request and update person_city_id dropdown
					$.ajax({
						dataType: "json",
						cache: false,
						url: '/system/tables/cities/getcitiesbydepartment/' + id_value_string,
						timeout: 5000,
						error: function(XMLHttpRequest, errorTextStatus, error) {
							alert("Failed to submit : " + errorTextStatus + " ;" + error);
						},
						success: function(data) { // Clear all options from person_city_id select
							if (data.success){
								$(select).children('option').remove();  //put in a empty default line
								var row = "<option value=\"" + "" + "\">" + "Seleccione Ciudad/Municipio" + "</option>";
								$(row).appendTo(select);   // Fill person_city_id select
								$.each(data.data, function(i, j) {
									row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
									$(row).appendTo(select);
								});
							}
						}
					});
				}
			});
	}


	


});
$(document).on('turbolinks:load', function() {

	$(document).on('change', 'select#workshop_supply_workshop_headquarter_id', function() {
		var id_value_string = $(this).val();
		var select = $('select#workshop_supply_workshop_warehouse_id');
		if (id_value_string == "") {
			$(select).children('option').remove();
			var row = "<option value=\"" + "" + "\">" + "Seleccione Almacen" + "</option>";
			$(row).appendTo(select);
		} else {      //Send the request and update person_city_id dropdown
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/workshop/warehouses/get_warehouses_by_headquarter/' + id_value_string,
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					alert("Failed to submit : " + errorTextStatus + " ;" + error);
				},
				success: function(data) { // Clear all options from person_city_id select
					console.log(data)
					$(select).children('option').remove();  //put in a empty default line
					var row = "<option value=\"" + "" + "\">" + "Seleccione Almacen" + "</option>";
					$(row).appendTo(select);
					if (data.success){
						$.each(data.data, function(i, j) {
							row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
							$(row).appendTo(select);
						});
					}
				}
			});
		}
	});

	$('#workshop_supply_applies_for_a_tool_or_equipment').on('change', function(){
		if ($(this).val() == 'false'){
			$('#select2_tools').addClass('ocultar')
			$('#workshop_supply_tools').prop('required',false);
		} else {
			$('#select2_tools').removeClass('ocultar')
			$('#workshop_supply_tools').prop('required',true);
		}
	});

	$('#workshop_supply_minimum_inventory').on('change','load',function(){
		$('#workshop_supply_maximum_inventory').prop('min', parseInt($(this).val()));
	})


	$('#addTechnicalSpecication').on('click', function(){
		AddTechnicalSpecication();
	})
	$("#divs_spec_tec").on('click', '.delete_technical_specification',  function(){
		$(this).parent().parent().parent().remove()
		return false
	})

	function AddTechnicalSpecication(){
		var $spectec = $('#new_row_spec_tech').find("#technical_specifications_div").clone();
		$("#another_technical_specification").append($spectec)
		$('input').keyup(function() {
			$(this).val($(this).val().toUpperCase());
		});
	}

	$('#addStorageConditions').on('click', function(){
		AddStorageCondition();
	})
	$("#divs_storage_conditions").on('click', '.delete_storage_conditions',  function(){
		$(this).parent().parent().parent().remove()
		return false
	})

	function AddStorageCondition(){
		var $row = $('#new_row_conditions_storage').find("#storage_conditions_div").clone();
		$("#another_storage_conditions").append($row)
		$('input').keyup(function() {
			$(this).val($(this).val().toUpperCase());
		});
	}

	$('#addConditionHandlingUse').on('click', function(){
		AddConditionHandlingUse();
	})
	$("#divs_conditions_handling_use").on('click', '.delete_conditions_handling_use',  function(){
		$(this).parent().parent().parent().remove()
		return false
	})

	function AddConditionHandlingUse(){
		var $row = $('#new_row_conditions_handling_use').find("#conditions_handling_use_div").clone();
		$("#another_conditions_handling_use").append($row)
		$('input').keyup(function() {
			$(this).val($(this).val().toUpperCase());
		});
	}


	$('#addFeatureSpecication').on('click', function(){
		AddFeatureSpecication();
	})
	$("#divs_feature").on('click', '.delete_feature_specification',  function(){
		$(this).parent().parent().parent().remove()
		return false
	})

	function AddFeatureSpecication(){
		var $spectec = $('#new_row_featu_spec').find("#feature_specifications_div").clone();
		$("#another_feature_specification").append($spectec)
		$('input').keyup(function() {
			$(this).val($(this).val().toUpperCase());
		});
	}

	$('#addHandlingSpecication').on('click', function(){
		AddHandlingSpecication();
	})
	$("#divs_handling").on('click', '.delete_handling_specification',  function(){
		$(this).parent().parent().parent().remove()
		return false
	})

	function AddHandlingSpecication(){
		var $spectec = $('#new_row_hand_spec').find("#handling_specifications_div").clone();
		$("#another_handling_specification").append($spectec)
		$('input').keyup(function() {
			$(this).val($(this).val().toUpperCase());
		});
	}


});
(function() {


}).call(this);
$(document).on('turbolinks:load', function() {
	$('#workshop_template_assignment_type_id').on('change', function(){
		var id_value_string =  $(this).val();
		console.log(id_value_string)
		if (id_value_string == "" && all_group == 'false') {
			$('#asociated_to').html('')
		} else if (id_value_string != "") {    //Send the request
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/workshop/template_assignments/get_type_group/' + id_value_string + '.json',
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
					$('#asociated_to').html('')
				},
				success: function(data) { // Clear all options from person_city_id select
					console.log(data)
					$('#asociated_to').html(data.html)
				}
			});
		}
	});

	$('#workshop_template_item_catalog_id').on('change', function(){
		console.log('changeitem')
		var id_value_string =  $(this).val();
		console.log(id_value_string)
		if (id_value_string == "" && all_group == 'false') {
			$('#input_catalog').html('')
		} else if (id_value_string != "") {    //Send the request
			$.ajax({
				dataType: "json",
				cache: false,
				url: '/workshop/template_items/get_catalog/' + id_value_string + '.json',
				timeout: 5000,
				error: function(XMLHttpRequest, errorTextStatus, error) {
					console.log("Failed to submit : " + errorTextStatus + " ;" + error);
					$('#input_catalog').html('')
				},
				success: function(data) { // Clear all options from person_city_id select
					console.log(data)
					$('#input_catalog').html(data.html)
				}
			});
		}
	})
});

(function() {


}).call(this);
(function() {


}).call(this);
$('.end_date_picker').on('change', function() {
  var start_times = document.getElementsByClassName('start_date_picker')
  var end_times = document.getElementsByClassName('end_date_picker')
  get_values(start_times, end_times)
})
$('.start_date_picker').on('change', function() {
  var start_times = document.getElementsByClassName('start_date_picker')
  var end_times = document.getElementsByClassName('end_date_picker')
  get_values(start_times, end_times)
})

function get_values (start_times, end_times) {
  var total_time = []
  for (var i = 0; i < start_times.length; i++) {
    $('.time_to_work').empty()
    time_start = start_times[i].value
    time_end = end_times[i].value
    start = time_start.split(":");
    end = time_end.split(":");
    total_time_end = diff(start, end)
    total_time.push(total_time_end)
  }
}

function diff(start, end) {
  var startDate = new Date(0, 0, 0, start[0], start[1], 0);
  var endDate = new Date(0, 0, 0, end[0], end[1], 0);
  var diff = endDate.getTime() - startDate.getTime();
  var hours = Math.floor(diff / 1000 / 60 / 60);
  diff -= hours * 1000 * 60 * 60;
  var minutes = Math.floor(diff / 1000 / 60);

  // If using time pickers with 24 hours format, add the below line get exact hours
  if (hours < 0)
  hours = hours + 24;
  return (hours <= 9 ? "0" : "") + hours + ":" + (minutes <= 9 ? "0" : "") + minutes;
}
;
(function() {


}).call(this);
$(document).on('turbolinks:load', function() {

	$('#addCriterium').on('click', function(){
		num = parseInt(num)
		AddCriterium();
	})


	$("#div_criteria").on('click', '.delete_criterium',  function(){
		$id = $(this).parent().parent().parent().find('.id')
		console.log($id.val())
		console.log(isNaN(parseInt($id.val())))
		if (isNaN(parseInt($id.val()))){
			$(this).parent().parent().parent().remove()
		} else {
			delete_criterium($id.val(),$(this))
		}
		
		return false
	})


	function AddCriterium(){
		var $criterium = $("#div_criterium_new").clone();
		$criterium.find('.name_criterium').val('');
		$criterium.find('.name_criterium').attr('name', 'workshop_validation_task[evaluation_criterium_attributes]['+num+'][name]');
		$criterium.find('.modified_id').attr('name', 'workshop_validation_task[evaluation_criterium_attributes]['+num+'][modified_id]');
		$criterium.find('.creator_id').attr('name', 'workshop_validation_task[evaluation_criterium_attributes]['+num+'][creator_id]');
		$criterium.find('.in_good_state').attr('name', 'workshop_validation_task[evaluation_criterium_attributes]['+num+'][in_good_state]');
		$criterium.find('.id').val('');
		$("#another_criterium").append($criterium);
		num = num + 1;
		console.log(num)
	}

	function delete_criterium(id,this1){
		$.ajax({
			dataType: "json",
			cache: false,
			method: 'delete',
			url: '/workshop/evaluation_criteria/' + id +'.json',
			timeout: 5000,
			error: function(XMLHttpRequest, errorTextStatus, error) {
				alert("Failed to submit : " + errorTextStatus + " ;" + error);
			},
			success: function(data) { // Clear all options from person_city_id select
				console.log(data)
				$(this1).parent().parent().parent().remove()
			}
		});
	}
});
$(document).on('turbolinks:load', function() {
	$('#workshop_warranty_catalog_id').on('change', function(){
		SetItemSelect($(this).val());
	})

	function SetItemSelect(catalog_id){
		console.log(catalog_id)
		$.ajax({
			dataType: "json",
			cache: false,
			url: '/workshop/warranties/get_items/'+catalog_id+'.json',
			timeout: 5000,
			beforeSend: function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+token);
			},
			error: function(XMLHttpRequest, errorTextStatus, error) {
				alert("Failed to submit : " + errorTextStatus + " ;" + error);
			},
			success: function(data) { 
				console.log(data)
				if (data.success){
					$("select#workshop_warranty_element_id option").remove(); 
					var row = "<option value=\"" + "" + "\">" + "Seleccione ítem" + "</option>";
					$(row).appendTo("select#workshop_warranty_element_id");
					$.each(data.data, function(i, j) {
						row = "<option value=\"" + j.id + "\">" + j.name + "</option>";
						$(row).appendTo("select#workshop_warranty_element_id");
					});
				}
			}
		});
	}

});
$(document).on('turbolinks:load', function() {
	var numeroSequenceWorkOrderTask = 1

	$('#addWorkOrderTask').on('click', function(){
		AddWorkOrderTask();
	})

	function AddWorkOrderTask(){
		console.log(numeroSequenceWorkOrderTask)
		var $row = $('#add_workorder_task').find("#div_workorder_task").clone();
		var val = $('#workshop_maintenance_routine_group_type_id').val();
		$row.find('.task_name').attr('id', 'workshop_work_order_task_' + numeroSequenceWorkOrderTask)
		$row.find('.task_name').attr('name', 'workshop_work_order[workshop_work_order_details_attributes]['+numeroSequenceWorkOrderTask+'][workshop_maintenance_task_id]')
		$row.find('.type').attr('id', 'workshop_work_order_type_' + numeroSequenceWorkOrderTask)
		$row.find('.type').attr('name', 'workshop_work_order[workshop_work_order_details_attributes]['+numeroSequenceWorkOrderTask+'][workshop_type_id]')
		$row.find('.allows_simultaneous_work').attr('id', 'workshop_work_order_allows_simultaneous_work_' + numeroSequenceWorkOrderTask)
		$row.find('.allows_simultaneous_work').attr('name', 'workshop_work_order[workshop_work_order_details_attributes]['+numeroSequenceWorkOrderTask+'][allows_simultaneous_work]')
		$row.find('#other_task').attr('id', 'other_task_' + numeroSequenceWorkOrderTask)

		$("#another_workorder_tasks").append($row)
		numeroSequenceWorkOrderTask++;
	}


});
/*!
 * Bootstrap v3.4.1 (https://getbootstrap.com/)
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under the MIT license
 */


if (typeof jQuery === 'undefined') {
  throw new Error('Bootstrap\'s JavaScript requires jQuery')
}

+function ($) {
  'use strict';
  var version = $.fn.jquery.split(' ')[0].split('.')
  if ((version[0] < 2 && version[1] < 9) || (version[0] == 1 && version[1] == 9 && version[2] < 1) || (version[0] > 3)) {
    throw new Error('Bootstrap\'s JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4')
  }
}(jQuery);

/* ========================================================================
 * Bootstrap: transition.js v3.4.1
 * https://getbootstrap.com/docs/3.4/javascript/#transitions
 * ========================================================================
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // CSS TRANSITION SUPPORT (Shoutout: https://modernizr.com/)
  // ============================================================

  function transitionEnd() {
    var el = document.createElement('bootstrap')

    var transEndEventNames = {
      WebkitTransition : 'webkitTransitionEnd',
      MozTransition    : 'transitionend',
      OTransition      : 'oTransitionEnd otransitionend',
      transition       : 'transitionend'
    }

    for (var name in transEndEventNames) {
      if (el.style[name] !== undefined) {
        return { end: transEndEventNames[name] }
      }
    }

    return false // explicit for ie8 (  ._.)
  }

  // https://blog.alexmaccaw.com/css-transitions
  $.fn.emulateTransitionEnd = function (duration) {
    var called = false
    var $el = this
    $(this).one('bsTransitionEnd', function () { called = true })
    var callback = function () { if (!called) $($el).trigger($.support.transition.end) }
    setTimeout(callback, duration)
    return this
  }

  $(function () {
    $.support.transition = transitionEnd()

    if (!$.support.transition) return

    $.event.special.bsTransitionEnd = {
      bindType: $.support.transition.end,
      delegateType: $.support.transition.end,
      handle: function (e) {
        if ($(e.target).is(this)) return e.handleObj.handler.apply(this, arguments)
      }
    }
  })

}(jQuery);

/* ========================================================================
 * Bootstrap: alert.js v3.4.1
 * https://getbootstrap.com/docs/3.4/javascript/#alerts
 * ========================================================================
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // ALERT CLASS DEFINITION
  // ======================

  var dismiss = '[data-dismiss="alert"]'
  var Alert   = function (el) {
    $(el).on('click', dismiss, this.close)
  }

  Alert.VERSION = '3.4.1'

  Alert.TRANSITION_DURATION = 150

  Alert.prototype.close = function (e) {
    var $this    = $(this)
    var selector = $this.attr('data-target')

    if (!selector) {
      selector = $this.attr('href')
      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
    }

    selector    = selector === '#' ? [] : selector
    var $parent = $(document).find(selector)

    if (e) e.preventDefault()

    if (!$parent.length) {
      $parent = $this.closest('.alert')
    }

    $parent.trigger(e = $.Event('close.bs.alert'))

    if (e.isDefaultPrevented()) return

    $parent.removeClass('in')

    function removeElement() {
      // detach from parent, fire event then clean up data
      $parent.detach().trigger('closed.bs.alert').remove()
    }

    $.support.transition && $parent.hasClass('fade') ?
      $parent
        .one('bsTransitionEnd', removeElement)
        .emulateTransitionEnd(Alert.TRANSITION_DURATION) :
      removeElement()
  }


  // ALERT PLUGIN DEFINITION
  // =======================

  function Plugin(option) {
    return this.each(function () {
      var $this = $(this)
      var data  = $this.data('bs.alert')

      if (!data) $this.data('bs.alert', (data = new Alert(this)))
      if (typeof option == 'string') data[option].call($this)
    })
  }

  var old = $.fn.alert

  $.fn.alert             = Plugin
  $.fn.alert.Constructor = Alert


  // ALERT NO CONFLICT
  // =================

  $.fn.alert.noConflict = function () {
    $.fn.alert = old
    return this
  }


  // ALERT DATA-API
  // ==============

  $(document).on('click.bs.alert.data-api', dismiss, Alert.prototype.close)

}(jQuery);

/* ========================================================================
 * Bootstrap: button.js v3.4.1
 * https://getbootstrap.com/docs/3.4/javascript/#buttons
 * ========================================================================
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // BUTTON PUBLIC CLASS DEFINITION
  // ==============================

  var Button = function (element, options) {
    this.$element  = $(element)
    this.options   = $.extend({}, Button.DEFAULTS, options)
    this.isLoading = false
  }

  Button.VERSION  = '3.4.1'

  Button.DEFAULTS = {
    loadingText: 'loading...'
  }

  Button.prototype.setState = function (state) {
    var d    = 'disabled'
    var $el  = this.$element
    var val  = $el.is('input') ? 'val' : 'html'
    var data = $el.data()

    state += 'Text'

    if (data.resetText == null) $el.data('resetText', $el[val]())

    // push to event loop to allow forms to submit
    setTimeout($.proxy(function () {
      $el[val](data[state] == null ? this.options[state] : data[state])

      if (state == 'loadingText') {
        this.isLoading = true
        $el.addClass(d).attr(d, d).prop(d, true)
      } else if (this.isLoading) {
        this.isLoading = false
        $el.removeClass(d).removeAttr(d).prop(d, false)
      }
    }, this), 0)
  }

  Button.prototype.toggle = function () {
    var changed = true
    var $parent = this.$element.closest('[data-toggle="buttons"]')

    if ($parent.length) {
      var $input = this.$element.find('input')
      if ($input.prop('type') == 'radio') {
        if ($input.prop('checked')) changed = false
        $parent.find('.active').removeClass('active')
        this.$element.addClass('active')
      } else if ($input.prop('type') == 'checkbox') {
        if (($input.prop('checked')) !== this.$element.hasClass('active')) changed = false
        this.$element.toggleClass('active')
      }
      $input.prop('checked', this.$element.hasClass('active'))
      if (changed) $input.trigger('change')
    } else {
      this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
      this.$element.toggleClass('active')
    }
  }


  // BUTTON PLUGIN DEFINITION
  // ========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.button')
      var options = typeof option == 'object' && option

      if (!data) $this.data('bs.button', (data = new Button(this, options)))

      if (option == 'toggle') data.toggle()
      else if (option) data.setState(option)
    })
  }

  var old = $.fn.button

  $.fn.button             = Plugin
  $.fn.button.Constructor = Button


  // BUTTON NO CONFLICT
  // ==================

  $.fn.button.noConflict = function () {
    $.fn.button = old
    return this
  }


  // BUTTON DATA-API
  // ===============

  $(document)
    .on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
      var $btn = $(e.target).closest('.btn')
      Plugin.call($btn, 'toggle')
      if (!($(e.target).is('input[type="radio"], input[type="checkbox"]'))) {
        // Prevent double click on radios, and the double selections (so cancellation) on checkboxes
        e.preventDefault()
        // The target component still receive the focus
        if ($btn.is('input,button')) $btn.trigger('focus')
        else $btn.find('input:visible,button:visible').first().trigger('focus')
      }
    })
    .on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
      $(e.target).closest('.btn').toggleClass('focus', /^focus(in)?$/.test(e.type))
    })

}(jQuery);

/* ========================================================================
 * Bootstrap: carousel.js v3.4.1
 * https://getbootstrap.com/docs/3.4/javascript/#carousel
 * ========================================================================
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // CAROUSEL CLASS DEFINITION
  // =========================

  var Carousel = function (element, options) {
    this.$element    = $(element)
    this.$indicators = this.$element.find('.carousel-indicators')
    this.options     = options
    this.paused      = null
    this.sliding     = null
    this.interval    = null
    this.$active     = null
    this.$items      = null

    this.options.keyboard && this.$element.on('keydown.bs.carousel', $.proxy(this.keydown, this))

    this.options.pause == 'hover' && !('ontouchstart' in document.documentElement) && this.$element
      .on('mouseenter.bs.carousel', $.proxy(this.pause, this))
      .on('mouseleave.bs.carousel', $.proxy(this.cycle, this))
  }

  Carousel.VERSION  = '3.4.1'

  Carousel.TRANSITION_DURATION = 600

  Carousel.DEFAULTS = {
    interval: 5000,
    pause: 'hover',
    wrap: true,
    keyboard: true
  }

  Carousel.prototype.keydown = function (e) {
    if (/input|textarea/i.test(e.target.tagName)) return
    switch (e.which) {
      case 37: this.prev(); break
      case 39: this.next(); break
      default: return
    }

    e.preventDefault()
  }

  Carousel.prototype.cycle = function (e) {
    e || (this.paused = false)

    this.interval && clearInterval(this.interval)

    this.options.interval
      && !this.paused
      && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))

    return this
  }

  Carousel.prototype.getItemIndex = function (item) {
    this.$items = item.parent().children('.item')
    return this.$items.index(item || this.$active)
  }

  Carousel.prototype.getItemForDirection = function (direction, active) {
    var activeIndex = this.getItemIndex(active)
    var willWrap = (direction == 'prev' && activeIndex === 0)
                || (direction == 'next' && activeIndex == (this.$items.length - 1))
    if (willWrap && !this.options.wrap) return active
    var delta = direction == 'prev' ? -1 : 1
    var itemIndex = (activeIndex + delta) % this.$items.length
    return this.$items.eq(itemIndex)
  }

  Carousel.prototype.to = function (pos) {
    var that        = this
    var activeIndex = this.getItemIndex(this.$active = this.$element.find('.item.active'))

    if (pos > (this.$items.length - 1) || pos < 0) return

    if (this.sliding)       return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) // yes, "slid"
    if (activeIndex == pos) return this.pause().cycle()

    return this.slide(pos > activeIndex ? 'next' : 'prev', this.$items.eq(pos))
  }

  Carousel.prototype.pause = function (e) {
    e || (this.paused = true)

    if (this.$element.find('.next, .prev').length && $.support.transition) {
      this.$element.trigger($.support.transition.end)
      this.cycle(true)
    }

    this.interval = clearInterval(this.interval)

    return this
  }

  Carousel.prototype.next = function () {
    if (this.sliding) return
    return this.slide('next')
  }

  Carousel.prototype.prev = function () {
    if (this.sliding) return
    return this.slide('prev')
  }

  Carousel.prototype.slide = function (type, next) {
    var $active   = this.$element.find('.item.active')
    var $next     = next || this.getItemForDirection(type, $active)
    var isCycling = this.interval
    var direction = type == 'next' ? 'left' : 'right'
    var that      = this

    if ($next.hasClass('active')) return (this.sliding = false)

    var relatedTarget = $next[0]
    var slideEvent = $.Event('slide.bs.carousel', {
      relatedTarget: relatedTarget,
      direction: direction
    })
    this.$element.trigger(slideEvent)
    if (slideEvent.isDefaultPrevented()) return

    this.sliding = true

    isCycling && this.pause()

    if (this.$indicators.length) {
      this.$indicators.find('.active').removeClass('active')
      var $nextIndicator = $(this.$indicators.children()[this.getItemIndex($next)])
      $nextIndicator && $nextIndicator.addClass('active')
    }

    var slidEvent = $.Event('slid.bs.carousel', { relatedTarget: relatedTarget, direction: direction }) // yes, "slid"
    if ($.support.transition && this.$element.hasClass('slide')) {
      $next.addClass(type)
      if (typeof $next === 'object' && $next.length) {
        $next[0].offsetWidth // force reflow
      }
      $active.addClass(direction)
      $next.addClass(direction)
      $active
        .one('bsTransitionEnd', function () {
          $next.removeClass([type, direction].join(' ')).addClass('active')
          $active.removeClass(['active', direction].join(' '))
          that.sliding = false
          setTimeout(function () {
            that.$element.trigger(slidEvent)
          }, 0)
        })
        .emulateTransitionEnd(Carousel.TRANSITION_DURATION)
    } else {
      $active.removeClass('active')
      $next.addClass('active')
      this.sliding = false
      this.$element.trigger(slidEvent)
    }

    isCycling && this.cycle()

    return this
  }


  // CAROUSEL PLUGIN DEFINITION
  // ==========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.carousel')
      var options = $.extend({}, Carousel.DEFAULTS, $this.data(), typeof option == 'object' && option)
      var action  = typeof option == 'string' ? option : options.slide

      if (!data) $this.data('bs.carousel', (data = new Carousel(this, options)))
      if (typeof option == 'number') data.to(option)
      else if (action) data[action]()
      else if (options.interval) data.pause().cycle()
    })
  }

  var old = $.fn.carousel

  $.fn.carousel             = Plugin
  $.fn.carousel.Constructor = Carousel


  // CAROUSEL NO CONFLICT
  // ====================

  $.fn.carousel.noConflict = function () {
    $.fn.carousel = old
    return this
  }


  // CAROUSEL DATA-API
  // =================

  var clickHandler = function (e) {
    var $this   = $(this)
    var href    = $this.attr('href')
    if (href) {
      href = href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7
    }

    var target  = $this.attr('data-target') || href
    var $target = $(document).find(target)

    if (!$target.hasClass('carousel')) return

    var options = $.extend({}, $target.data(), $this.data())
    var slideIndex = $this.attr('data-slide-to')
    if (slideIndex) options.interval = false

    Plugin.call($target, options)

    if (slideIndex) {
      $target.data('bs.carousel').to(slideIndex)
    }

    e.preventDefault()
  }

  $(document)
    .on('click.bs.carousel.data-api', '[data-slide]', clickHandler)
    .on('click.bs.carousel.data-api', '[data-slide-to]', clickHandler)

  $(window).on('load', function () {
    $('[data-ride="carousel"]').each(function () {
      var $carousel = $(this)
      Plugin.call($carousel, $carousel.data())
    })
  })

}(jQuery);

/* ========================================================================
 * Bootstrap: collapse.js v3.4.1
 * https://getbootstrap.com/docs/3.4/javascript/#collapse
 * ========================================================================
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */

/* jshint latedef: false */

+function ($) {
  'use strict';

  // COLLAPSE PUBLIC CLASS DEFINITION
  // ================================

  var Collapse = function (element, options) {
    this.$element      = $(element)
    this.options       = $.extend({}, Collapse.DEFAULTS, options)
    this.$trigger      = $('[data-toggle="collapse"][href="#' + element.id + '"],' +
                           '[data-toggle="collapse"][data-target="#' + element.id + '"]')
    this.transitioning = null

    if (this.options.parent) {
      this.$parent = this.getParent()
    } else {
      this.addAriaAndCollapsedClass(this.$element, this.$trigger)
    }

    if (this.options.toggle) this.toggle()
  }

  Collapse.VERSION  = '3.4.1'

  Collapse.TRANSITION_DURATION = 350

  Collapse.DEFAULTS = {
    toggle: true
  }

  Collapse.prototype.dimension = function () {
    var hasWidth = this.$element.hasClass('width')
    return hasWidth ? 'width' : 'height'
  }

  Collapse.prototype.show = function () {
    if (this.transitioning || this.$element.hasClass('in')) return

    var activesData
    var actives = this.$parent && this.$parent.children('.panel').children('.in, .collapsing')

    if (actives && actives.length) {
      activesData = actives.data('bs.collapse')
      if (activesData && activesData.transitioning) return
    }

    var startEvent = $.Event('show.bs.collapse')
    this.$element.trigger(startEvent)
    if (startEvent.isDefaultPrevented()) return

    if (actives && actives.length) {
      Plugin.call(actives, 'hide')
      activesData || actives.data('bs.collapse', null)
    }

    var dimension = this.dimension()

    this.$element
      .removeClass('collapse')
      .addClass('collapsing')[dimension](0)
      .attr('aria-expanded', true)

    this.$trigger
      .removeClass('collapsed')
      .attr('aria-expanded', true)

    this.transitioning = 1

    var complete = function () {
      this.$element
        .removeClass('collapsing')
        .addClass('collapse in')[dimension]('')
      this.transitioning = 0
      this.$element
        .trigger('shown.bs.collapse')
    }

    if (!$.support.transition) return complete.call(this)

    var scrollSize = $.camelCase(['scroll', dimension].join('-'))

    this.$element
      .one('bsTransitionEnd', $.proxy(complete, this))
      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)[dimension](this.$element[0][scrollSize])
  }

  Collapse.prototype.hide = function () {
    if (this.transitioning || !this.$element.hasClass('in')) return

    var startEvent = $.Event('hide.bs.collapse')
    this.$element.trigger(startEvent)
    if (startEvent.isDefaultPrevented()) return

    var dimension = this.dimension()

    this.$element[dimension](this.$element[dimension]())[0].offsetHeight

    this.$element
      .addClass('collapsing')
      .removeClass('collapse in')
      .attr('aria-expanded', false)

    this.$trigger
      .addClass('collapsed')
      .attr('aria-expanded', false)

    this.transitioning = 1

    var complete = function () {
      this.transitioning = 0
      this.$element
        .removeClass('collapsing')
        .addClass('collapse')
        .trigger('hidden.bs.collapse')
    }

    if (!$.support.transition) return complete.call(this)

    this.$element
      [dimension](0)
      .one('bsTransitionEnd', $.proxy(complete, this))
      .emulateTransitionEnd(Collapse.TRANSITION_DURATION)
  }

  Collapse.prototype.toggle = function () {
    this[this.$element.hasClass('in') ? 'hide' : 'show']()
  }

  Collapse.prototype.getParent = function () {
    return $(document).find(this.options.parent)
      .find('[data-toggle="collapse"][data-parent="' + this.options.parent + '"]')
      .each($.proxy(function (i, element) {
        var $element = $(element)
        this.addAriaAndCollapsedClass(getTargetFromTrigger($element), $element)
      }, this))
      .end()
  }

  Collapse.prototype.addAriaAndCollapsedClass = function ($element, $trigger) {
    var isOpen = $element.hasClass('in')

    $element.attr('aria-expanded', isOpen)
    $trigger
      .toggleClass('collapsed', !isOpen)
      .attr('aria-expanded', isOpen)
  }

  function getTargetFromTrigger($trigger) {
    var href
    var target = $trigger.attr('data-target')
      || (href = $trigger.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') // strip for ie7

    return $(document).find(target)
  }


  // COLLAPSE PLUGIN DEFINITION
  // ==========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.collapse')
      var options = $.extend({}, Collapse.DEFAULTS, $this.data(), typeof option == 'object' && option)

      if (!data && options.toggle && /show|hide/.test(option)) options.toggle = false
      if (!data) $this.data('bs.collapse', (data = new Collapse(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.collapse

  $.fn.collapse             = Plugin
  $.fn.collapse.Constructor = Collapse


  // COLLAPSE NO CONFLICT
  // ====================

  $.fn.collapse.noConflict = function () {
    $.fn.collapse = old
    return this
  }


  // COLLAPSE DATA-API
  // =================

  $(document).on('click.bs.collapse.data-api', '[data-toggle="collapse"]', function (e) {
    var $this   = $(this)

    if (!$this.attr('data-target')) e.preventDefault()

    var $target = getTargetFromTrigger($this)
    var data    = $target.data('bs.collapse')
    var option  = data ? 'toggle' : $this.data()

    Plugin.call($target, option)
  })

}(jQuery);

/* ========================================================================
 * Bootstrap: dropdown.js v3.4.1
 * https://getbootstrap.com/docs/3.4/javascript/#dropdowns
 * ========================================================================
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // DROPDOWN CLASS DEFINITION
  // =========================

  var backdrop = '.dropdown-backdrop'
  var toggle   = '[data-toggle="dropdown"]'
  var Dropdown = function (element) {
    $(element).on('click.bs.dropdown', this.toggle)
  }

  Dropdown.VERSION = '3.4.1'

  function getParent($this) {
    var selector = $this.attr('data-target')

    if (!selector) {
      selector = $this.attr('href')
      selector = selector && /#[A-Za-z]/.test(selector) && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
    }

    var $parent = selector !== '#' ? $(document).find(selector) : null

    return $parent && $parent.length ? $parent : $this.parent()
  }

  function clearMenus(e) {
    if (e && e.which === 3) return
    $(backdrop).remove()
    $(toggle).each(function () {
      var $this         = $(this)
      var $parent       = getParent($this)
      var relatedTarget = { relatedTarget: this }

      if (!$parent.hasClass('open')) return

      if (e && e.type == 'click' && /input|textarea/i.test(e.target.tagName) && $.contains($parent[0], e.target)) return

      $parent.trigger(e = $.Event('hide.bs.dropdown', relatedTarget))

      if (e.isDefaultPrevented()) return

      $this.attr('aria-expanded', 'false')
      $parent.removeClass('open').trigger($.Event('hidden.bs.dropdown', relatedTarget))
    })
  }

  Dropdown.prototype.toggle = function (e) {
    var $this = $(this)

    if ($this.is('.disabled, :disabled')) return

    var $parent  = getParent($this)
    var isActive = $parent.hasClass('open')

    clearMenus()

    if (!isActive) {
      if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) {
        // if mobile we use a backdrop because click events don't delegate
        $(document.createElement('div'))
          .addClass('dropdown-backdrop')
          .insertAfter($(this))
          .on('click', clearMenus)
      }

      var relatedTarget = { relatedTarget: this }
      $parent.trigger(e = $.Event('show.bs.dropdown', relatedTarget))

      if (e.isDefaultPrevented()) return

      $this
        .trigger('focus')
        .attr('aria-expanded', 'true')

      $parent
        .toggleClass('open')
        .trigger($.Event('shown.bs.dropdown', relatedTarget))
    }

    return false
  }

  Dropdown.prototype.keydown = function (e) {
    if (!/(38|40|27|32)/.test(e.which) || /input|textarea/i.test(e.target.tagName)) return

    var $this = $(this)

    e.preventDefault()
    e.stopPropagation()

    if ($this.is('.disabled, :disabled')) return

    var $parent  = getParent($this)
    var isActive = $parent.hasClass('open')

    if (!isActive && e.which != 27 || isActive && e.which == 27) {
      if (e.which == 27) $parent.find(toggle).trigger('focus')
      return $this.trigger('click')
    }

    var desc = ' li:not(.disabled):visible a'
    var $items = $parent.find('.dropdown-menu' + desc)

    if (!$items.length) return

    var index = $items.index(e.target)

    if (e.which == 38 && index > 0)                 index--         // up
    if (e.which == 40 && index < $items.length - 1) index++         // down
    if (!~index)                                    index = 0

    $items.eq(index).trigger('focus')
  }


  // DROPDOWN PLUGIN DEFINITION
  // ==========================

  function Plugin(option) {
    return this.each(function () {
      var $this = $(this)
      var data  = $this.data('bs.dropdown')

      if (!data) $this.data('bs.dropdown', (data = new Dropdown(this)))
      if (typeof option == 'string') data[option].call($this)
    })
  }

  var old = $.fn.dropdown

  $.fn.dropdown             = Plugin
  $.fn.dropdown.Constructor = Dropdown


  // DROPDOWN NO CONFLICT
  // ====================

  $.fn.dropdown.noConflict = function () {
    $.fn.dropdown = old
    return this
  }


  // APPLY TO STANDARD DROPDOWN ELEMENTS
  // ===================================

  $(document)
    .on('click.bs.dropdown.data-api', clearMenus)
    .on('click.bs.dropdown.data-api', '.dropdown form', function (e) { e.stopPropagation() })
    .on('click.bs.dropdown.data-api', toggle, Dropdown.prototype.toggle)
    .on('keydown.bs.dropdown.data-api', toggle, Dropdown.prototype.keydown)
    .on('keydown.bs.dropdown.data-api', '.dropdown-menu', Dropdown.prototype.keydown)

}(jQuery);

/* ========================================================================
 * Bootstrap: modal.js v3.4.1
 * https://getbootstrap.com/docs/3.4/javascript/#modals
 * ========================================================================
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // MODAL CLASS DEFINITION
  // ======================

  var Modal = function (element, options) {
    this.options = options
    this.$body = $(document.body)
    this.$element = $(element)
    this.$dialog = this.$element.find('.modal-dialog')
    this.$backdrop = null
    this.isShown = null
    this.originalBodyPad = null
    this.scrollbarWidth = 0
    this.ignoreBackdropClick = false
    this.fixedContent = '.navbar-fixed-top, .navbar-fixed-bottom'

    if (this.options.remote) {
      this.$element
        .find('.modal-content')
        .load(this.options.remote, $.proxy(function () {
          this.$element.trigger('loaded.bs.modal')
        }, this))
    }
  }

  Modal.VERSION = '3.4.1'

  Modal.TRANSITION_DURATION = 300
  Modal.BACKDROP_TRANSITION_DURATION = 150

  Modal.DEFAULTS = {
    backdrop: true,
    keyboard: true,
    show: true
  }

  Modal.prototype.toggle = function (_relatedTarget) {
    return this.isShown ? this.hide() : this.show(_relatedTarget)
  }

  Modal.prototype.show = function (_relatedTarget) {
    var that = this
    var e = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })

    this.$element.trigger(e)

    if (this.isShown || e.isDefaultPrevented()) return

    this.isShown = true

    this.checkScrollbar()
    this.setScrollbar()
    this.$body.addClass('modal-open')

    this.escape()
    this.resize()

    this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))

    this.$dialog.on('mousedown.dismiss.bs.modal', function () {
      that.$element.one('mouseup.dismiss.bs.modal', function (e) {
        if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
      })
    })

    this.backdrop(function () {
      var transition = $.support.transition && that.$element.hasClass('fade')

      if (!that.$element.parent().length) {
        that.$element.appendTo(that.$body) // don't move modals dom position
      }

      that.$element
        .show()
        .scrollTop(0)

      that.adjustDialog()

      if (transition) {
        that.$element[0].offsetWidth // force reflow
      }

      that.$element.addClass('in')

      that.enforceFocus()

      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })

      transition ?
        that.$dialog // wait for modal to slide in
          .one('bsTransitionEnd', function () {
            that.$element.trigger('focus').trigger(e)
          })
          .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
        that.$element.trigger('focus').trigger(e)
    })
  }

  Modal.prototype.hide = function (e) {
    if (e) e.preventDefault()

    e = $.Event('hide.bs.modal')

    this.$element.trigger(e)

    if (!this.isShown || e.isDefaultPrevented()) return

    this.isShown = false

    this.escape()
    this.resize()

    $(document).off('focusin.bs.modal')

    this.$element
      .removeClass('in')
      .off('click.dismiss.bs.modal')
      .off('mouseup.dismiss.bs.modal')

    this.$dialog.off('mousedown.dismiss.bs.modal')

    $.support.transition && this.$element.hasClass('fade') ?
      this.$element
        .one('bsTransitionEnd', $.proxy(this.hideModal, this))
        .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
      this.hideModal()
  }

  Modal.prototype.enforceFocus = function () {
    $(document)
      .off('focusin.bs.modal') // guard against infinite focus loop
      .on('focusin.bs.modal', $.proxy(function (e) {
        if (document !== e.target &&
          this.$element[0] !== e.target &&
          !this.$element.has(e.target).length) {
          this.$element.trigger('focus')
        }
      }, this))
  }

  Modal.prototype.escape = function () {
    if (this.isShown && this.options.keyboard) {
      this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
        e.which == 27 && this.hide()
      }, this))
    } else if (!this.isShown) {
      this.$element.off('keydown.dismiss.bs.modal')
    }
  }

  Modal.prototype.resize = function () {
    if (this.isShown) {
      $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
    } else {
      $(window).off('resize.bs.modal')
    }
  }

  Modal.prototype.hideModal = function () {
    var that = this
    this.$element.hide()
    this.backdrop(function () {
      that.$body.removeClass('modal-open')
      that.resetAdjustments()
      that.resetScrollbar()
      that.$element.trigger('hidden.bs.modal')
    })
  }

  Modal.prototype.removeBackdrop = function () {
    this.$backdrop && this.$backdrop.remove()
    this.$backdrop = null
  }

  Modal.prototype.backdrop = function (callback) {
    var that = this
    var animate = this.$element.hasClass('fade') ? 'fade' : ''

    if (this.isShown && this.options.backdrop) {
      var doAnimate = $.support.transition && animate

      this.$backdrop = $(document.createElement('div'))
        .addClass('modal-backdrop ' + animate)
        .appendTo(this.$body)

      this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
        if (this.ignoreBackdropClick) {
          this.ignoreBackdropClick = false
          return
        }
        if (e.target !== e.currentTarget) return
        this.options.backdrop == 'static'
          ? this.$element[0].focus()
          : this.hide()
      }, this))

      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow

      this.$backdrop.addClass('in')

      if (!callback) return

      doAnimate ?
        this.$backdrop
          .one('bsTransitionEnd', callback)
          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
        callback()

    } else if (!this.isShown && this.$backdrop) {
      this.$backdrop.removeClass('in')

      var callbackRemove = function () {
        that.removeBackdrop()
        callback && callback()
      }
      $.support.transition && this.$element.hasClass('fade') ?
        this.$backdrop
          .one('bsTransitionEnd', callbackRemove)
          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
        callbackRemove()

    } else if (callback) {
      callback()
    }
  }

  // these following methods are used to handle overflowing modals

  Modal.prototype.handleUpdate = function () {
    this.adjustDialog()
  }

  Modal.prototype.adjustDialog = function () {
    var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight

    this.$element.css({
      paddingLeft: !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
      paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
    })
  }

  Modal.prototype.resetAdjustments = function () {
    this.$element.css({
      paddingLeft: '',
      paddingRight: ''
    })
  }

  Modal.prototype.checkScrollbar = function () {
    var fullWindowWidth = window.innerWidth
    if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
      var documentElementRect = document.documentElement.getBoundingClientRect()
      fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
    }
    this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
    this.scrollbarWidth = this.measureScrollbar()
  }

  Modal.prototype.setScrollbar = function () {
    var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
    this.originalBodyPad = document.body.style.paddingRight || ''
    var scrollbarWidth = this.scrollbarWidth
    if (this.bodyIsOverflowing) {
      this.$body.css('padding-right', bodyPad + scrollbarWidth)
      $(this.fixedContent).each(function (index, element) {
        var actualPadding = element.style.paddingRight
        var calculatedPadding = $(element).css('padding-right')
        $(element)
          .data('padding-right', actualPadding)
          .css('padding-right', parseFloat(calculatedPadding) + scrollbarWidth + 'px')
      })
    }
  }

  Modal.prototype.resetScrollbar = function () {
    this.$body.css('padding-right', this.originalBodyPad)
    $(this.fixedContent).each(function (index, element) {
      var padding = $(element).data('padding-right')
      $(element).removeData('padding-right')
      element.style.paddingRight = padding ? padding : ''
    })
  }

  Modal.prototype.measureScrollbar = function () { // thx walsh
    var scrollDiv = document.createElement('div')
    scrollDiv.className = 'modal-scrollbar-measure'
    this.$body.append(scrollDiv)
    var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
    this.$body[0].removeChild(scrollDiv)
    return scrollbarWidth
  }


  // MODAL PLUGIN DEFINITION
  // =======================

  function Plugin(option, _relatedTarget) {
    return this.each(function () {
      var $this = $(this)
      var data = $this.data('bs.modal')
      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)

      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
      if (typeof option == 'string') data[option](_relatedTarget)
      else if (options.show) data.show(_relatedTarget)
    })
  }

  var old = $.fn.modal

  $.fn.modal = Plugin
  $.fn.modal.Constructor = Modal


  // MODAL NO CONFLICT
  // =================

  $.fn.modal.noConflict = function () {
    $.fn.modal = old
    return this
  }


  // MODAL DATA-API
  // ==============

  $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
    var $this = $(this)
    var href = $this.attr('href')
    var target = $this.attr('data-target') ||
      (href && href.replace(/.*(?=#[^\s]+$)/, '')) // strip for ie7

    var $target = $(document).find(target)
    var option = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())

    if ($this.is('a')) e.preventDefault()

    $target.one('show.bs.modal', function (showEvent) {
      if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
      $target.one('hidden.bs.modal', function () {
        $this.is(':visible') && $this.trigger('focus')
      })
    })
    Plugin.call($target, option, this)
  })

}(jQuery);

/* ========================================================================
 * Bootstrap: tooltip.js v3.4.1
 * https://getbootstrap.com/docs/3.4/javascript/#tooltip
 * Inspired by the original jQuery.tipsy by Jason Frame
 * ========================================================================
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */

+function ($) {
  'use strict';

  var DISALLOWED_ATTRIBUTES = ['sanitize', 'whiteList', 'sanitizeFn']

  var uriAttrs = [
    'background',
    'cite',
    'href',
    'itemtype',
    'longdesc',
    'poster',
    'src',
    'xlink:href'
  ]

  var ARIA_ATTRIBUTE_PATTERN = /^aria-[\w-]*$/i

  var DefaultWhitelist = {
    // Global attributes allowed on any supplied element below.
    '*': ['class', 'dir', 'id', 'lang', 'role', ARIA_ATTRIBUTE_PATTERN],
    a: ['target', 'href', 'title', 'rel'],
    area: [],
    b: [],
    br: [],
    col: [],
    code: [],
    div: [],
    em: [],
    hr: [],
    h1: [],
    h2: [],
    h3: [],
    h4: [],
    h5: [],
    h6: [],
    i: [],
    img: ['src', 'alt', 'title', 'width', 'height'],
    li: [],
    ol: [],
    p: [],
    pre: [],
    s: [],
    small: [],
    span: [],
    sub: [],
    sup: [],
    strong: [],
    u: [],
    ul: []
  }

  /**
   * A pattern that recognizes a commonly useful subset of URLs that are safe.
   *
   * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
   */
  var SAFE_URL_PATTERN = /^(?:(?:https?|mailto|ftp|tel|file):|[^&:/?#]*(?:[/?#]|$))/gi

  /**
   * A pattern that matches safe data URLs. Only matches image, video and audio types.
   *
   * Shoutout to Angular 7 https://github.com/angular/angular/blob/7.2.4/packages/core/src/sanitization/url_sanitizer.ts
   */
  var DATA_URL_PATTERN = /^data:(?:image\/(?:bmp|gif|jpeg|jpg|png|tiff|webp)|video\/(?:mpeg|mp4|ogg|webm)|audio\/(?:mp3|oga|ogg|opus));base64,[a-z0-9+/]+=*$/i

  function allowedAttribute(attr, allowedAttributeList) {
    var attrName = attr.nodeName.toLowerCase()

    if ($.inArray(attrName, allowedAttributeList) !== -1) {
      if ($.inArray(attrName, uriAttrs) !== -1) {
        return Boolean(attr.nodeValue.match(SAFE_URL_PATTERN) || attr.nodeValue.match(DATA_URL_PATTERN))
      }

      return true
    }

    var regExp = $(allowedAttributeList).filter(function (index, value) {
      return value instanceof RegExp
    })

    // Check if a regular expression validates the attribute.
    for (var i = 0, l = regExp.length; i < l; i++) {
      if (attrName.match(regExp[i])) {
        return true
      }
    }

    return false
  }

  function sanitizeHtml(unsafeHtml, whiteList, sanitizeFn) {
    if (unsafeHtml.length === 0) {
      return unsafeHtml
    }

    if (sanitizeFn && typeof sanitizeFn === 'function') {
      return sanitizeFn(unsafeHtml)
    }

    // IE 8 and below don't support createHTMLDocument
    if (!document.implementation || !document.implementation.createHTMLDocument) {
      return unsafeHtml
    }

    var createdDocument = document.implementation.createHTMLDocument('sanitization')
    createdDocument.body.innerHTML = unsafeHtml

    var whitelistKeys = $.map(whiteList, function (el, i) { return i })
    var elements = $(createdDocument.body).find('*')

    for (var i = 0, len = elements.length; i < len; i++) {
      var el = elements[i]
      var elName = el.nodeName.toLowerCase()

      if ($.inArray(elName, whitelistKeys) === -1) {
        el.parentNode.removeChild(el)

        continue
      }

      var attributeList = $.map(el.attributes, function (el) { return el })
      var whitelistedAttributes = [].concat(whiteList['*'] || [], whiteList[elName] || [])

      for (var j = 0, len2 = attributeList.length; j < len2; j++) {
        if (!allowedAttribute(attributeList[j], whitelistedAttributes)) {
          el.removeAttribute(attributeList[j].nodeName)
        }
      }
    }

    return createdDocument.body.innerHTML
  }

  // TOOLTIP PUBLIC CLASS DEFINITION
  // ===============================

  var Tooltip = function (element, options) {
    this.type       = null
    this.options    = null
    this.enabled    = null
    this.timeout    = null
    this.hoverState = null
    this.$element   = null
    this.inState    = null

    this.init('tooltip', element, options)
  }

  Tooltip.VERSION  = '3.4.1'

  Tooltip.TRANSITION_DURATION = 150

  Tooltip.DEFAULTS = {
    animation: true,
    placement: 'top',
    selector: false,
    template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
    trigger: 'hover focus',
    title: '',
    delay: 0,
    html: false,
    container: false,
    viewport: {
      selector: 'body',
      padding: 0
    },
    sanitize : true,
    sanitizeFn : null,
    whiteList : DefaultWhitelist
  }

  Tooltip.prototype.init = function (type, element, options) {
    this.enabled   = true
    this.type      = type
    this.$element  = $(element)
    this.options   = this.getOptions(options)
    this.$viewport = this.options.viewport && $(document).find($.isFunction(this.options.viewport) ? this.options.viewport.call(this, this.$element) : (this.options.viewport.selector || this.options.viewport))
    this.inState   = { click: false, hover: false, focus: false }

    if (this.$element[0] instanceof document.constructor && !this.options.selector) {
      throw new Error('`selector` option must be specified when initializing ' + this.type + ' on the window.document object!')
    }

    var triggers = this.options.trigger.split(' ')

    for (var i = triggers.length; i--;) {
      var trigger = triggers[i]

      if (trigger == 'click') {
        this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
      } else if (trigger != 'manual') {
        var eventIn  = trigger == 'hover' ? 'mouseenter' : 'focusin'
        var eventOut = trigger == 'hover' ? 'mouseleave' : 'focusout'

        this.$element.on(eventIn  + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
        this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
      }
    }

    this.options.selector ?
      (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
      this.fixTitle()
  }

  Tooltip.prototype.getDefaults = function () {
    return Tooltip.DEFAULTS
  }

  Tooltip.prototype.getOptions = function (options) {
    var dataAttributes = this.$element.data()

    for (var dataAttr in dataAttributes) {
      if (dataAttributes.hasOwnProperty(dataAttr) && $.inArray(dataAttr, DISALLOWED_ATTRIBUTES) !== -1) {
        delete dataAttributes[dataAttr]
      }
    }

    options = $.extend({}, this.getDefaults(), dataAttributes, options)

    if (options.delay && typeof options.delay == 'number') {
      options.delay = {
        show: options.delay,
        hide: options.delay
      }
    }

    if (options.sanitize) {
      options.template = sanitizeHtml(options.template, options.whiteList, options.sanitizeFn)
    }

    return options
  }

  Tooltip.prototype.getDelegateOptions = function () {
    var options  = {}
    var defaults = this.getDefaults()

    this._options && $.each(this._options, function (key, value) {
      if (defaults[key] != value) options[key] = value
    })

    return options
  }

  Tooltip.prototype.enter = function (obj) {
    var self = obj instanceof this.constructor ?
      obj : $(obj.currentTarget).data('bs.' + this.type)

    if (!self) {
      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
      $(obj.currentTarget).data('bs.' + this.type, self)
    }

    if (obj instanceof $.Event) {
      self.inState[obj.type == 'focusin' ? 'focus' : 'hover'] = true
    }

    if (self.tip().hasClass('in') || self.hoverState == 'in') {
      self.hoverState = 'in'
      return
    }

    clearTimeout(self.timeout)

    self.hoverState = 'in'

    if (!self.options.delay || !self.options.delay.show) return self.show()

    self.timeout = setTimeout(function () {
      if (self.hoverState == 'in') self.show()
    }, self.options.delay.show)
  }

  Tooltip.prototype.isInStateTrue = function () {
    for (var key in this.inState) {
      if (this.inState[key]) return true
    }

    return false
  }

  Tooltip.prototype.leave = function (obj) {
    var self = obj instanceof this.constructor ?
      obj : $(obj.currentTarget).data('bs.' + this.type)

    if (!self) {
      self = new this.constructor(obj.currentTarget, this.getDelegateOptions())
      $(obj.currentTarget).data('bs.' + this.type, self)
    }

    if (obj instanceof $.Event) {
      self.inState[obj.type == 'focusout' ? 'focus' : 'hover'] = false
    }

    if (self.isInStateTrue()) return

    clearTimeout(self.timeout)

    self.hoverState = 'out'

    if (!self.options.delay || !self.options.delay.hide) return self.hide()

    self.timeout = setTimeout(function () {
      if (self.hoverState == 'out') self.hide()
    }, self.options.delay.hide)
  }

  Tooltip.prototype.show = function () {
    var e = $.Event('show.bs.' + this.type)

    if (this.hasContent() && this.enabled) {
      this.$element.trigger(e)

      var inDom = $.contains(this.$element[0].ownerDocument.documentElement, this.$element[0])
      if (e.isDefaultPrevented() || !inDom) return
      var that = this

      var $tip = this.tip()

      var tipId = this.getUID(this.type)

      this.setContent()
      $tip.attr('id', tipId)
      this.$element.attr('aria-describedby', tipId)

      if (this.options.animation) $tip.addClass('fade')

      var placement = typeof this.options.placement == 'function' ?
        this.options.placement.call(this, $tip[0], this.$element[0]) :
        this.options.placement

      var autoToken = /\s?auto?\s?/i
      var autoPlace = autoToken.test(placement)
      if (autoPlace) placement = placement.replace(autoToken, '') || 'top'

      $tip
        .detach()
        .css({ top: 0, left: 0, display: 'block' })
        .addClass(placement)
        .data('bs.' + this.type, this)

      this.options.container ? $tip.appendTo($(document).find(this.options.container)) : $tip.insertAfter(this.$element)
      this.$element.trigger('inserted.bs.' + this.type)

      var pos          = this.getPosition()
      var actualWidth  = $tip[0].offsetWidth
      var actualHeight = $tip[0].offsetHeight

      if (autoPlace) {
        var orgPlacement = placement
        var viewportDim = this.getPosition(this.$viewport)

        placement = placement == 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top'    :
                    placement == 'top'    && pos.top    - actualHeight < viewportDim.top    ? 'bottom' :
                    placement == 'right'  && pos.right  + actualWidth  > viewportDim.width  ? 'left'   :
                    placement == 'left'   && pos.left   - actualWidth  < viewportDim.left   ? 'right'  :
                    placement

        $tip
          .removeClass(orgPlacement)
          .addClass(placement)
      }

      var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)

      this.applyPlacement(calculatedOffset, placement)

      var complete = function () {
        var prevHoverState = that.hoverState
        that.$element.trigger('shown.bs.' + that.type)
        that.hoverState = null

        if (prevHoverState == 'out') that.leave(that)
      }

      $.support.transition && this.$tip.hasClass('fade') ?
        $tip
          .one('bsTransitionEnd', complete)
          .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
        complete()
    }
  }

  Tooltip.prototype.applyPlacement = function (offset, placement) {
    var $tip   = this.tip()
    var width  = $tip[0].offsetWidth
    var height = $tip[0].offsetHeight

    // manually read margins because getBoundingClientRect includes difference
    var marginTop = parseInt($tip.css('margin-top'), 10)
    var marginLeft = parseInt($tip.css('margin-left'), 10)

    // we must check for NaN for ie 8/9
    if (isNaN(marginTop))  marginTop  = 0
    if (isNaN(marginLeft)) marginLeft = 0

    offset.top  += marginTop
    offset.left += marginLeft

    // $.fn.offset doesn't round pixel values
    // so we use setOffset directly with our own function B-0
    $.offset.setOffset($tip[0], $.extend({
      using: function (props) {
        $tip.css({
          top: Math.round(props.top),
          left: Math.round(props.left)
        })
      }
    }, offset), 0)

    $tip.addClass('in')

    // check to see if placing tip in new offset caused the tip to resize itself
    var actualWidth  = $tip[0].offsetWidth
    var actualHeight = $tip[0].offsetHeight

    if (placement == 'top' && actualHeight != height) {
      offset.top = offset.top + height - actualHeight
    }

    var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)

    if (delta.left) offset.left += delta.left
    else offset.top += delta.top

    var isVertical          = /top|bottom/.test(placement)
    var arrowDelta          = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
    var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'

    $tip.offset(offset)
    this.replaceArrow(arrowDelta, $tip[0][arrowOffsetPosition], isVertical)
  }

  Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
    this.arrow()
      .css(isVertical ? 'left' : 'top', 50 * (1 - delta / dimension) + '%')
      .css(isVertical ? 'top' : 'left', '')
  }

  Tooltip.prototype.setContent = function () {
    var $tip  = this.tip()
    var title = this.getTitle()

    if (this.options.html) {
      if (this.options.sanitize) {
        title = sanitizeHtml(title, this.options.whiteList, this.options.sanitizeFn)
      }

      $tip.find('.tooltip-inner').html(title)
    } else {
      $tip.find('.tooltip-inner').text(title)
    }

    $tip.removeClass('fade in top bottom left right')
  }

  Tooltip.prototype.hide = function (callback) {
    var that = this
    var $tip = $(this.$tip)
    var e    = $.Event('hide.bs.' + this.type)

    function complete() {
      if (that.hoverState != 'in') $tip.detach()
      if (that.$element) { // TODO: Check whether guarding this code with this `if` is really necessary.
        that.$element
          .removeAttr('aria-describedby')
          .trigger('hidden.bs.' + that.type)
      }
      callback && callback()
    }

    this.$element.trigger(e)

    if (e.isDefaultPrevented()) return

    $tip.removeClass('in')

    $.support.transition && $tip.hasClass('fade') ?
      $tip
        .one('bsTransitionEnd', complete)
        .emulateTransitionEnd(Tooltip.TRANSITION_DURATION) :
      complete()

    this.hoverState = null

    return this
  }

  Tooltip.prototype.fixTitle = function () {
    var $e = this.$element
    if ($e.attr('title') || typeof $e.attr('data-original-title') != 'string') {
      $e.attr('data-original-title', $e.attr('title') || '').attr('title', '')
    }
  }

  Tooltip.prototype.hasContent = function () {
    return this.getTitle()
  }

  Tooltip.prototype.getPosition = function ($element) {
    $element   = $element || this.$element

    var el     = $element[0]
    var isBody = el.tagName == 'BODY'

    var elRect    = el.getBoundingClientRect()
    if (elRect.width == null) {
      // width and height are missing in IE8, so compute them manually; see https://github.com/twbs/bootstrap/issues/14093
      elRect = $.extend({}, elRect, { width: elRect.right - elRect.left, height: elRect.bottom - elRect.top })
    }
    var isSvg = window.SVGElement && el instanceof window.SVGElement
    // Avoid using $.offset() on SVGs since it gives incorrect results in jQuery 3.
    // See https://github.com/twbs/bootstrap/issues/20280
    var elOffset  = isBody ? { top: 0, left: 0 } : (isSvg ? null : $element.offset())
    var scroll    = { scroll: isBody ? document.documentElement.scrollTop || document.body.scrollTop : $element.scrollTop() }
    var outerDims = isBody ? { width: $(window).width(), height: $(window).height() } : null

    return $.extend({}, elRect, scroll, outerDims, elOffset)
  }

  Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
    return placement == 'bottom' ? { top: pos.top + pos.height,   left: pos.left + pos.width / 2 - actualWidth / 2 } :
           placement == 'top'    ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
           placement == 'left'   ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
        /* placement == 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }

  }

  Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
    var delta = { top: 0, left: 0 }
    if (!this.$viewport) return delta

    var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
    var viewportDimensions = this.getPosition(this.$viewport)

    if (/right|left/.test(placement)) {
      var topEdgeOffset    = pos.top - viewportPadding - viewportDimensions.scroll
      var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
      if (topEdgeOffset < viewportDimensions.top) { // top overflow
        delta.top = viewportDimensions.top - topEdgeOffset
      } else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
        delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
      }
    } else {
      var leftEdgeOffset  = pos.left - viewportPadding
      var rightEdgeOffset = pos.left + viewportPadding + actualWidth
      if (leftEdgeOffset < viewportDimensions.left) { // left overflow
        delta.left = viewportDimensions.left - leftEdgeOffset
      } else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
        delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
      }
    }

    return delta
  }

  Tooltip.prototype.getTitle = function () {
    var title
    var $e = this.$element
    var o  = this.options

    title = $e.attr('data-original-title')
      || (typeof o.title == 'function' ? o.title.call($e[0]) :  o.title)

    return title
  }

  Tooltip.prototype.getUID = function (prefix) {
    do prefix += ~~(Math.random() * 1000000)
    while (document.getElementById(prefix))
    return prefix
  }

  Tooltip.prototype.tip = function () {
    if (!this.$tip) {
      this.$tip = $(this.options.template)
      if (this.$tip.length != 1) {
        throw new Error(this.type + ' `template` option must consist of exactly 1 top-level element!')
      }
    }
    return this.$tip
  }

  Tooltip.prototype.arrow = function () {
    return (this.$arrow = this.$arrow || this.tip().find('.tooltip-arrow'))
  }

  Tooltip.prototype.enable = function () {
    this.enabled = true
  }

  Tooltip.prototype.disable = function () {
    this.enabled = false
  }

  Tooltip.prototype.toggleEnabled = function () {
    this.enabled = !this.enabled
  }

  Tooltip.prototype.toggle = function (e) {
    var self = this
    if (e) {
      self = $(e.currentTarget).data('bs.' + this.type)
      if (!self) {
        self = new this.constructor(e.currentTarget, this.getDelegateOptions())
        $(e.currentTarget).data('bs.' + this.type, self)
      }
    }

    if (e) {
      self.inState.click = !self.inState.click
      if (self.isInStateTrue()) self.enter(self)
      else self.leave(self)
    } else {
      self.tip().hasClass('in') ? self.leave(self) : self.enter(self)
    }
  }

  Tooltip.prototype.destroy = function () {
    var that = this
    clearTimeout(this.timeout)
    this.hide(function () {
      that.$element.off('.' + that.type).removeData('bs.' + that.type)
      if (that.$tip) {
        that.$tip.detach()
      }
      that.$tip = null
      that.$arrow = null
      that.$viewport = null
      that.$element = null
    })
  }

  Tooltip.prototype.sanitizeHtml = function (unsafeHtml) {
    return sanitizeHtml(unsafeHtml, this.options.whiteList, this.options.sanitizeFn)
  }

  // TOOLTIP PLUGIN DEFINITION
  // =========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.tooltip')
      var options = typeof option == 'object' && option

      if (!data && /destroy|hide/.test(option)) return
      if (!data) $this.data('bs.tooltip', (data = new Tooltip(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.tooltip

  $.fn.tooltip             = Plugin
  $.fn.tooltip.Constructor = Tooltip


  // TOOLTIP NO CONFLICT
  // ===================

  $.fn.tooltip.noConflict = function () {
    $.fn.tooltip = old
    return this
  }

}(jQuery);

/* ========================================================================
 * Bootstrap: popover.js v3.4.1
 * https://getbootstrap.com/docs/3.4/javascript/#popovers
 * ========================================================================
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // POPOVER PUBLIC CLASS DEFINITION
  // ===============================

  var Popover = function (element, options) {
    this.init('popover', element, options)
  }

  if (!$.fn.tooltip) throw new Error('Popover requires tooltip.js')

  Popover.VERSION  = '3.4.1'

  Popover.DEFAULTS = $.extend({}, $.fn.tooltip.Constructor.DEFAULTS, {
    placement: 'right',
    trigger: 'click',
    content: '',
    template: '<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'
  })


  // NOTE: POPOVER EXTENDS tooltip.js
  // ================================

  Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype)

  Popover.prototype.constructor = Popover

  Popover.prototype.getDefaults = function () {
    return Popover.DEFAULTS
  }

  Popover.prototype.setContent = function () {
    var $tip    = this.tip()
    var title   = this.getTitle()
    var content = this.getContent()

    if (this.options.html) {
      var typeContent = typeof content

      if (this.options.sanitize) {
        title = this.sanitizeHtml(title)

        if (typeContent === 'string') {
          content = this.sanitizeHtml(content)
        }
      }

      $tip.find('.popover-title').html(title)
      $tip.find('.popover-content').children().detach().end()[
        typeContent === 'string' ? 'html' : 'append'
      ](content)
    } else {
      $tip.find('.popover-title').text(title)
      $tip.find('.popover-content').children().detach().end().text(content)
    }

    $tip.removeClass('fade top bottom left right in')

    // IE8 doesn't accept hiding via the `:empty` pseudo selector, we have to do
    // this manually by checking the contents.
    if (!$tip.find('.popover-title').html()) $tip.find('.popover-title').hide()
  }

  Popover.prototype.hasContent = function () {
    return this.getTitle() || this.getContent()
  }

  Popover.prototype.getContent = function () {
    var $e = this.$element
    var o  = this.options

    return $e.attr('data-content')
      || (typeof o.content == 'function' ?
        o.content.call($e[0]) :
        o.content)
  }

  Popover.prototype.arrow = function () {
    return (this.$arrow = this.$arrow || this.tip().find('.arrow'))
  }


  // POPOVER PLUGIN DEFINITION
  // =========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.popover')
      var options = typeof option == 'object' && option

      if (!data && /destroy|hide/.test(option)) return
      if (!data) $this.data('bs.popover', (data = new Popover(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.popover

  $.fn.popover             = Plugin
  $.fn.popover.Constructor = Popover


  // POPOVER NO CONFLICT
  // ===================

  $.fn.popover.noConflict = function () {
    $.fn.popover = old
    return this
  }

}(jQuery);

/* ========================================================================
 * Bootstrap: scrollspy.js v3.4.1
 * https://getbootstrap.com/docs/3.4/javascript/#scrollspy
 * ========================================================================
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // SCROLLSPY CLASS DEFINITION
  // ==========================

  function ScrollSpy(element, options) {
    this.$body          = $(document.body)
    this.$scrollElement = $(element).is(document.body) ? $(window) : $(element)
    this.options        = $.extend({}, ScrollSpy.DEFAULTS, options)
    this.selector       = (this.options.target || '') + ' .nav li > a'
    this.offsets        = []
    this.targets        = []
    this.activeTarget   = null
    this.scrollHeight   = 0

    this.$scrollElement.on('scroll.bs.scrollspy', $.proxy(this.process, this))
    this.refresh()
    this.process()
  }

  ScrollSpy.VERSION  = '3.4.1'

  ScrollSpy.DEFAULTS = {
    offset: 10
  }

  ScrollSpy.prototype.getScrollHeight = function () {
    return this.$scrollElement[0].scrollHeight || Math.max(this.$body[0].scrollHeight, document.documentElement.scrollHeight)
  }

  ScrollSpy.prototype.refresh = function () {
    var that          = this
    var offsetMethod  = 'offset'
    var offsetBase    = 0

    this.offsets      = []
    this.targets      = []
    this.scrollHeight = this.getScrollHeight()

    if (!$.isWindow(this.$scrollElement[0])) {
      offsetMethod = 'position'
      offsetBase   = this.$scrollElement.scrollTop()
    }

    this.$body
      .find(this.selector)
      .map(function () {
        var $el   = $(this)
        var href  = $el.data('target') || $el.attr('href')
        var $href = /^#./.test(href) && $(href)

        return ($href
          && $href.length
          && $href.is(':visible')
          && [[$href[offsetMethod]().top + offsetBase, href]]) || null
      })
      .sort(function (a, b) { return a[0] - b[0] })
      .each(function () {
        that.offsets.push(this[0])
        that.targets.push(this[1])
      })
  }

  ScrollSpy.prototype.process = function () {
    var scrollTop    = this.$scrollElement.scrollTop() + this.options.offset
    var scrollHeight = this.getScrollHeight()
    var maxScroll    = this.options.offset + scrollHeight - this.$scrollElement.height()
    var offsets      = this.offsets
    var targets      = this.targets
    var activeTarget = this.activeTarget
    var i

    if (this.scrollHeight != scrollHeight) {
      this.refresh()
    }

    if (scrollTop >= maxScroll) {
      return activeTarget != (i = targets[targets.length - 1]) && this.activate(i)
    }

    if (activeTarget && scrollTop < offsets[0]) {
      this.activeTarget = null
      return this.clear()
    }

    for (i = offsets.length; i--;) {
      activeTarget != targets[i]
        && scrollTop >= offsets[i]
        && (offsets[i + 1] === undefined || scrollTop < offsets[i + 1])
        && this.activate(targets[i])
    }
  }

  ScrollSpy.prototype.activate = function (target) {
    this.activeTarget = target

    this.clear()

    var selector = this.selector +
      '[data-target="' + target + '"],' +
      this.selector + '[href="' + target + '"]'

    var active = $(selector)
      .parents('li')
      .addClass('active')

    if (active.parent('.dropdown-menu').length) {
      active = active
        .closest('li.dropdown')
        .addClass('active')
    }

    active.trigger('activate.bs.scrollspy')
  }

  ScrollSpy.prototype.clear = function () {
    $(this.selector)
      .parentsUntil(this.options.target, '.active')
      .removeClass('active')
  }


  // SCROLLSPY PLUGIN DEFINITION
  // ===========================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.scrollspy')
      var options = typeof option == 'object' && option

      if (!data) $this.data('bs.scrollspy', (data = new ScrollSpy(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.scrollspy

  $.fn.scrollspy             = Plugin
  $.fn.scrollspy.Constructor = ScrollSpy


  // SCROLLSPY NO CONFLICT
  // =====================

  $.fn.scrollspy.noConflict = function () {
    $.fn.scrollspy = old
    return this
  }


  // SCROLLSPY DATA-API
  // ==================

  $(window).on('load.bs.scrollspy.data-api', function () {
    $('[data-spy="scroll"]').each(function () {
      var $spy = $(this)
      Plugin.call($spy, $spy.data())
    })
  })

}(jQuery);

/* ========================================================================
 * Bootstrap: tab.js v3.4.1
 * https://getbootstrap.com/docs/3.4/javascript/#tabs
 * ========================================================================
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // TAB CLASS DEFINITION
  // ====================

  var Tab = function (element) {
    // jscs:disable requireDollarBeforejQueryAssignment
    this.element = $(element)
    // jscs:enable requireDollarBeforejQueryAssignment
  }

  Tab.VERSION = '3.4.1'

  Tab.TRANSITION_DURATION = 150

  Tab.prototype.show = function () {
    var $this    = this.element
    var $ul      = $this.closest('ul:not(.dropdown-menu)')
    var selector = $this.data('target')

    if (!selector) {
      selector = $this.attr('href')
      selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') // strip for ie7
    }

    if ($this.parent('li').hasClass('active')) return

    var $previous = $ul.find('.active:last a')
    var hideEvent = $.Event('hide.bs.tab', {
      relatedTarget: $this[0]
    })
    var showEvent = $.Event('show.bs.tab', {
      relatedTarget: $previous[0]
    })

    $previous.trigger(hideEvent)
    $this.trigger(showEvent)

    if (showEvent.isDefaultPrevented() || hideEvent.isDefaultPrevented()) return

    var $target = $(document).find(selector)

    this.activate($this.closest('li'), $ul)
    this.activate($target, $target.parent(), function () {
      $previous.trigger({
        type: 'hidden.bs.tab',
        relatedTarget: $this[0]
      })
      $this.trigger({
        type: 'shown.bs.tab',
        relatedTarget: $previous[0]
      })
    })
  }

  Tab.prototype.activate = function (element, container, callback) {
    var $active    = container.find('> .active')
    var transition = callback
      && $.support.transition
      && ($active.length && $active.hasClass('fade') || !!container.find('> .fade').length)

    function next() {
      $active
        .removeClass('active')
        .find('> .dropdown-menu > .active')
        .removeClass('active')
        .end()
        .find('[data-toggle="tab"]')
        .attr('aria-expanded', false)

      element
        .addClass('active')
        .find('[data-toggle="tab"]')
        .attr('aria-expanded', true)

      if (transition) {
        element[0].offsetWidth // reflow for transition
        element.addClass('in')
      } else {
        element.removeClass('fade')
      }

      if (element.parent('.dropdown-menu').length) {
        element
          .closest('li.dropdown')
          .addClass('active')
          .end()
          .find('[data-toggle="tab"]')
          .attr('aria-expanded', true)
      }

      callback && callback()
    }

    $active.length && transition ?
      $active
        .one('bsTransitionEnd', next)
        .emulateTransitionEnd(Tab.TRANSITION_DURATION) :
      next()

    $active.removeClass('in')
  }


  // TAB PLUGIN DEFINITION
  // =====================

  function Plugin(option) {
    return this.each(function () {
      var $this = $(this)
      var data  = $this.data('bs.tab')

      if (!data) $this.data('bs.tab', (data = new Tab(this)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.tab

  $.fn.tab             = Plugin
  $.fn.tab.Constructor = Tab


  // TAB NO CONFLICT
  // ===============

  $.fn.tab.noConflict = function () {
    $.fn.tab = old
    return this
  }


  // TAB DATA-API
  // ============

  var clickHandler = function (e) {
    e.preventDefault()
    Plugin.call($(this), 'show')
  }

  $(document)
    .on('click.bs.tab.data-api', '[data-toggle="tab"]', clickHandler)
    .on('click.bs.tab.data-api', '[data-toggle="pill"]', clickHandler)

}(jQuery);

/* ========================================================================
 * Bootstrap: affix.js v3.4.1
 * https://getbootstrap.com/docs/3.4/javascript/#affix
 * ========================================================================
 * Copyright 2011-2019 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */


+function ($) {
  'use strict';

  // AFFIX CLASS DEFINITION
  // ======================

  var Affix = function (element, options) {
    this.options = $.extend({}, Affix.DEFAULTS, options)

    var target = this.options.target === Affix.DEFAULTS.target ? $(this.options.target) : $(document).find(this.options.target)

    this.$target = target
      .on('scroll.bs.affix.data-api', $.proxy(this.checkPosition, this))
      .on('click.bs.affix.data-api',  $.proxy(this.checkPositionWithEventLoop, this))

    this.$element     = $(element)
    this.affixed      = null
    this.unpin        = null
    this.pinnedOffset = null

    this.checkPosition()
  }

  Affix.VERSION  = '3.4.1'

  Affix.RESET    = 'affix affix-top affix-bottom'

  Affix.DEFAULTS = {
    offset: 0,
    target: window
  }

  Affix.prototype.getState = function (scrollHeight, height, offsetTop, offsetBottom) {
    var scrollTop    = this.$target.scrollTop()
    var position     = this.$element.offset()
    var targetHeight = this.$target.height()

    if (offsetTop != null && this.affixed == 'top') return scrollTop < offsetTop ? 'top' : false

    if (this.affixed == 'bottom') {
      if (offsetTop != null) return (scrollTop + this.unpin <= position.top) ? false : 'bottom'
      return (scrollTop + targetHeight <= scrollHeight - offsetBottom) ? false : 'bottom'
    }

    var initializing   = this.affixed == null
    var colliderTop    = initializing ? scrollTop : position.top
    var colliderHeight = initializing ? targetHeight : height

    if (offsetTop != null && scrollTop <= offsetTop) return 'top'
    if (offsetBottom != null && (colliderTop + colliderHeight >= scrollHeight - offsetBottom)) return 'bottom'

    return false
  }

  Affix.prototype.getPinnedOffset = function () {
    if (this.pinnedOffset) return this.pinnedOffset
    this.$element.removeClass(Affix.RESET).addClass('affix')
    var scrollTop = this.$target.scrollTop()
    var position  = this.$element.offset()
    return (this.pinnedOffset = position.top - scrollTop)
  }

  Affix.prototype.checkPositionWithEventLoop = function () {
    setTimeout($.proxy(this.checkPosition, this), 1)
  }

  Affix.prototype.checkPosition = function () {
    if (!this.$element.is(':visible')) return

    var height       = this.$element.height()
    var offset       = this.options.offset
    var offsetTop    = offset.top
    var offsetBottom = offset.bottom
    var scrollHeight = Math.max($(document).height(), $(document.body).height())

    if (typeof offset != 'object')         offsetBottom = offsetTop = offset
    if (typeof offsetTop == 'function')    offsetTop    = offset.top(this.$element)
    if (typeof offsetBottom == 'function') offsetBottom = offset.bottom(this.$element)

    var affix = this.getState(scrollHeight, height, offsetTop, offsetBottom)

    if (this.affixed != affix) {
      if (this.unpin != null) this.$element.css('top', '')

      var affixType = 'affix' + (affix ? '-' + affix : '')
      var e         = $.Event(affixType + '.bs.affix')

      this.$element.trigger(e)

      if (e.isDefaultPrevented()) return

      this.affixed = affix
      this.unpin = affix == 'bottom' ? this.getPinnedOffset() : null

      this.$element
        .removeClass(Affix.RESET)
        .addClass(affixType)
        .trigger(affixType.replace('affix', 'affixed') + '.bs.affix')
    }

    if (affix == 'bottom') {
      this.$element.offset({
        top: scrollHeight - height - offsetBottom
      })
    }
  }


  // AFFIX PLUGIN DEFINITION
  // =======================

  function Plugin(option) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.affix')
      var options = typeof option == 'object' && option

      if (!data) $this.data('bs.affix', (data = new Affix(this, options)))
      if (typeof option == 'string') data[option]()
    })
  }

  var old = $.fn.affix

  $.fn.affix             = Plugin
  $.fn.affix.Constructor = Affix


  // AFFIX NO CONFLICT
  // =================

  $.fn.affix.noConflict = function () {
    $.fn.affix = old
    return this
  }


  // AFFIX DATA-API
  // ==============

  $(window).on('load', function () {
    $('[data-spy="affix"]').each(function () {
      var $spy = $(this)
      var data = $spy.data()

      data.offset = data.offset || {}

      if (data.offsetBottom != null) data.offset.bottom = data.offsetBottom
      if (data.offsetTop    != null) data.offset.top    = data.offsetTop

      Plugin.call($spy, data)
    })
  })

}(jQuery);
/* ===========================================================
 * bootstrap-modal.js v2.2.5
 * ===========================================================
 * Copyright 2012 Jordan Schroter
 *
 * 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.
 * ========================================================== */



!function ($) {

	"use strict"; // jshint ;_;

	/* MODAL CLASS DEFINITION
	* ====================== */

	var Modal = function (element, options) {
		this.init(element, options);
	};

	Modal.prototype = {

		constructor: Modal,

		init: function (element, options) {
			var that = this;

			this.options = options;

			this.$element = $(element)
				.delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this));

			this.options.remote && this.$element.find('.modal-body').load(this.options.remote, function () {
				var e = $.Event('loaded');
				that.$element.trigger(e);
			});

			var manager = typeof this.options.manager === 'function' ?
				this.options.manager.call(this) : this.options.manager;

			manager = manager.appendModal ?
				manager : $(manager).modalmanager().data('modalmanager');

			manager.appendModal(this);
		},

		toggle: function () {
			return this[!this.isShown ? 'show' : 'hide']();
		},

		show: function () {
			var e = $.Event('show');

			if (this.isShown) return;

			this.$element.trigger(e);

			if (e.isDefaultPrevented()) return;

			this.escape();

			this.tab();

			this.options.loading && this.loading();
		},

		hide: function (e) {
			e && e.preventDefault();

			e = $.Event('hide');

			this.$element.trigger(e);

			if (!this.isShown || e.isDefaultPrevented()) return;

			this.isShown = false;

			this.escape();

			this.tab();

			this.isLoading && this.loading();

			$(document).off('focusin.modal');

			this.$element
				.removeClass('in')
				.removeClass('animated')
				.removeClass(this.options.attentionAnimation)
				.removeClass('modal-overflow')
				.attr('aria-hidden', true);

			$.support.transition && this.$element.hasClass('fade') ?
				this.hideWithTransition() :
				this.hideModal();
		},

		layout: function () {
			var prop = this.options.height ? 'height' : 'max-height',
				value = this.options.height || this.options.maxHeight;

			if (this.options.width){
				this.$element.css('width', this.options.width);

				var that = this;
				this.$element.css('margin-left', function () {
					if (/%/ig.test(that.options.width)){
						return -(parseInt(that.options.width) / 2) + '%';
					} else {
						return -($(this).width() / 2) + 'px';
					}
				});
			} else {
				this.$element.css('width', '');
				this.$element.css('margin-left', '');
			}

			this.$element.find('.modal-body')
				.css('overflow', '')
				.css(prop, '');

			if (value){
				this.$element.find('.modal-body')
					.css('overflow', 'auto')
					.css(prop, value);
			}

			var modalOverflow = $(window).height() - 10 < this.$element.height();
            
			if (modalOverflow || this.options.modalOverflow) {
				this.$element
					.css('margin-top', 0)
					.addClass('modal-overflow');
			} else {
				this.$element
					.css('margin-top', 0 - this.$element.height() / 2)
					.removeClass('modal-overflow');
			}
		},

		tab: function () {
			var that = this;

			if (this.isShown && this.options.consumeTab) {
				this.$element.on('keydown.tabindex.modal', '[data-tabindex]', function (e) {
			    	if (e.keyCode && e.keyCode == 9){
						var $next = $(this),
							$rollover = $(this);

						that.$element.find('[data-tabindex]:enabled:not([readonly])').each(function (e) {
							if (!e.shiftKey){
						 		$next = $next.data('tabindex') < $(this).data('tabindex') ?
									$next = $(this) :
									$rollover = $(this);
							} else {
								$next = $next.data('tabindex') > $(this).data('tabindex') ?
									$next = $(this) :
									$rollover = $(this);
							}
						});

						$next[0] !== $(this)[0] ?
							$next.focus() : $rollover.focus();

						e.preventDefault();
					}
				});
			} else if (!this.isShown) {
				this.$element.off('keydown.tabindex.modal');
			}
		},

		escape: function () {
			var that = this;
			if (this.isShown && this.options.keyboard) {
				if (!this.$element.attr('tabindex')) this.$element.attr('tabindex', -1);

				this.$element.on('keyup.dismiss.modal', function (e) {
					e.which == 27 && that.hide();
				});
			} else if (!this.isShown) {
				this.$element.off('keyup.dismiss.modal')
			}
		},

		hideWithTransition: function () {
			var that = this
				, timeout = setTimeout(function () {
					that.$element.off($.support.transition.end);
					that.hideModal();
				}, 500);

			this.$element.one($.support.transition.end, function () {
				clearTimeout(timeout);
				that.hideModal();
			});
		},

		hideModal: function () {
			var prop = this.options.height ? 'height' : 'max-height';
			var value = this.options.height || this.options.maxHeight;

			if (value){
				this.$element.find('.modal-body')
					.css('overflow', '')
					.css(prop, '');
			}

			this.$element
				.hide()
				.trigger('hidden');
		},

		removeLoading: function () {
			this.$loading.remove();
			this.$loading = null;
			this.isLoading = false;
		},

		loading: function (callback) {
			callback = callback || function () {};

			var animate = this.$element.hasClass('fade') ? 'fade' : '';

			if (!this.isLoading) {
				var doAnimate = $.support.transition && animate;

				this.$loading = $('<div class="loading-mask ' + animate + '">')
					.append(this.options.spinner)
					.appendTo(this.$element);

				if (doAnimate) this.$loading[0].offsetWidth; // force reflow

				this.$loading.addClass('in');

				this.isLoading = true;

				doAnimate ?
					this.$loading.one($.support.transition.end, callback) :
					callback();

			} else if (this.isLoading && this.$loading) {
				this.$loading.removeClass('in');

				var that = this;
				$.support.transition && this.$element.hasClass('fade')?
					this.$loading.one($.support.transition.end, function () { that.removeLoading() }) :
					that.removeLoading();

			} else if (callback) {
				callback(this.isLoading);
			}
		},

		focus: function () {
			var $focusElem = this.$element.find(this.options.focusOn);

			$focusElem = $focusElem.length ? $focusElem : this.$element;

			$focusElem.focus();
		},

		attention: function (){
			// NOTE: transitionEnd with keyframes causes odd behaviour

			if (this.options.attentionAnimation){
				this.$element
					.removeClass('animated')
					.removeClass(this.options.attentionAnimation);

				var that = this;

				setTimeout(function () {
					that.$element
						.addClass('animated')
						.addClass(that.options.attentionAnimation);
				}, 0);
			}


			this.focus();
		},


		destroy: function () {
			var e = $.Event('destroy');

			this.$element.trigger(e);

			if (e.isDefaultPrevented()) return;

			this.$element
				.off('.modal')
				.removeData('modal')
				.removeClass('in')
				.attr('aria-hidden', true);
			
			if (this.$parent !== this.$element.parent()) {
				this.$element.appendTo(this.$parent);
			} else if (!this.$parent.length) {
				// modal is not part of the DOM so remove it.
				this.$element.remove();
				this.$element = null;
			}

			this.$element.trigger('destroyed');
		}
	};


	/* MODAL PLUGIN DEFINITION
	* ======================= */

	$.fn.modal = function (option, args) {
		return this.each(function () {
			var $this = $(this),
				data = $this.data('modal'),
				options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option);

			if (!data) $this.data('modal', (data = new Modal(this, options)));
			if (typeof option == 'string') data[option].apply(data, [].concat(args));
			else if (options.show) data.show()
		})
	};

	$.fn.modal.defaults = {
		keyboard: true,
		backdrop: true,
		loading: false,
		show: true,
		width: null,
		height: null,
		maxHeight: null,
		modalOverflow: false,
		consumeTab: true,
		focusOn: null,
		replace: false,
		resize: false,
		attentionAnimation: 'shake',
		manager: 'body',
		spinner: '<div class="loading-spinner" style="width: 200px; margin-left: -100px;"><div class="progress progress-striped active"><div class="bar" style="width: 100%;"></div></div></div>',
		backdropTemplate: '<div class="modal-backdrop" />'
	};

	$.fn.modal.Constructor = Modal;


	/* MODAL DATA-API
	* ============== */

	$(function () {
		$(document).off('click.modal').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
			var $this = $(this),
				href = $this.attr('href'),
				$target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))), //strip for ie7
				option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data());

			e.preventDefault();
			$target
				.modal(option)
				.one('hide', function () {
					$this.focus();
				})
		});
	});

}(window.jQuery);
/* ========================================================================
 * Bootstrap: modal.js v3.3.6
 * http://getbootstrap.com/javascript/#modals
 * ========================================================================
 * Copyright 2011-2015 Twitter, Inc.
 * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
 * ======================================================================== */



+function ($) {
  'use strict';

  // MODAL CLASS DEFINITION
  // ======================

  var Modal = function (element, options) {
    this.options             = options
    this.$body               = $(document.body)
    this.$element            = $(element)
    this.$dialog             = this.$element.find('.modal-dialog')
    this.$backdrop           = null
    this.isShown             = null
    this.originalBodyPad     = null
    this.scrollbarWidth      = 0
    this.ignoreBackdropClick = false

    if (this.options.remote) {
      this.$element
        .find('.modal-content')
        .load(this.options.remote, $.proxy(function () {
          this.$element.trigger('loaded.bs.modal')
        }, this))
    }
  }

  Modal.VERSION  = '3.3.6'

  Modal.TRANSITION_DURATION = 300
  Modal.BACKDROP_TRANSITION_DURATION = 150

  Modal.DEFAULTS = {
    backdrop: true,
    keyboard: true,
    show: true
  }

  Modal.prototype.toggle = function (_relatedTarget) {
    return this.isShown ? this.hide() : this.show(_relatedTarget)
  }

  Modal.prototype.show = function (_relatedTarget) {
    var that = this
    var e    = $.Event('show.bs.modal', { relatedTarget: _relatedTarget })

    this.$element.trigger(e)

    if (this.isShown || e.isDefaultPrevented()) return

    this.isShown = true

    this.checkScrollbar()
    this.setScrollbar()
    this.$body.addClass('modal-open')

    this.escape()
    this.resize()

    this.$element.on('click.dismiss.bs.modal', '[data-dismiss="modal"]', $.proxy(this.hide, this))

    this.$dialog.on('mousedown.dismiss.bs.modal', function () {
      that.$element.one('mouseup.dismiss.bs.modal', function (e) {
        if ($(e.target).is(that.$element)) that.ignoreBackdropClick = true
      })
    })

    this.backdrop(function () {
      var transition = $.support.transition && that.$element.hasClass('fade')

      if (!that.$element.parent().length) {
        that.$element.appendTo(that.$body) // don't move modals dom position
      }

      that.$element
        .show()
        .scrollTop(0)

      that.adjustDialog()

      if (transition) {
        that.$element[0].offsetWidth // force reflow
      }

      that.$element.addClass('in')

      that.enforceFocus()

      var e = $.Event('shown.bs.modal', { relatedTarget: _relatedTarget })

      transition ?
        that.$dialog // wait for modal to slide in
          .one('bsTransitionEnd', function () {
            that.$element.trigger('focus').trigger(e)
          })
          .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
        that.$element.trigger('focus').trigger(e)
    })
  }

  Modal.prototype.hide = function (e) {
    if (e) e.preventDefault()

    e = $.Event('hide.bs.modal')

    this.$element.trigger(e)

    if (!this.isShown || e.isDefaultPrevented()) return

    this.isShown = false

    this.escape()
    this.resize()

    $(document).off('focusin.bs.modal')

    this.$element
      .removeClass('in')
      .off('click.dismiss.bs.modal')
      .off('mouseup.dismiss.bs.modal')

    this.$dialog.off('mousedown.dismiss.bs.modal')

    $.support.transition && this.$element.hasClass('fade') ?
      this.$element
        .one('bsTransitionEnd', $.proxy(this.hideModal, this))
        .emulateTransitionEnd(Modal.TRANSITION_DURATION) :
      this.hideModal()
  }

  Modal.prototype.enforceFocus = function () {
    $(document)
      .off('focusin.bs.modal') // guard against infinite focus loop
      .on('focusin.bs.modal', $.proxy(function (e) {
        if (this.$element[0] !== e.target && !this.$element.has(e.target).length) {
          this.$element.trigger('focus')
        }
      }, this))
  }

  Modal.prototype.escape = function () {
    if (this.isShown && this.options.keyboard) {
      this.$element.on('keydown.dismiss.bs.modal', $.proxy(function (e) {
        e.which == 27 && this.hide()
      }, this))
    } else if (!this.isShown) {
      this.$element.off('keydown.dismiss.bs.modal')
    }
  }

  Modal.prototype.resize = function () {
    if (this.isShown) {
      $(window).on('resize.bs.modal', $.proxy(this.handleUpdate, this))
    } else {
      $(window).off('resize.bs.modal')
    }
  }

  Modal.prototype.hideModal = function () {
    var that = this
    this.$element.hide()
    this.backdrop(function () {
      that.$body.removeClass('modal-open')
      that.resetAdjustments()
      that.resetScrollbar()
      that.$element.trigger('hidden.bs.modal')
    })
  }

  Modal.prototype.removeBackdrop = function () {
    this.$backdrop && this.$backdrop.remove()
    this.$backdrop = null
  }

  Modal.prototype.backdrop = function (callback) {
    var that = this
    var animate = this.$element.hasClass('fade') ? 'fade' : ''

    if (this.isShown && this.options.backdrop) {
      var doAnimate = $.support.transition && animate

      this.$backdrop = $(document.createElement('div'))
        .addClass('modal-backdrop ' + animate)
        .appendTo(this.$body)

      this.$element.on('click.dismiss.bs.modal', $.proxy(function (e) {
        if (this.ignoreBackdropClick) {
          this.ignoreBackdropClick = false
          return
        }
        if (e.target !== e.currentTarget) return
        this.options.backdrop == 'static'
          ? this.$element[0].focus()
          : this.hide()
      }, this))

      if (doAnimate) this.$backdrop[0].offsetWidth // force reflow

      this.$backdrop.addClass('in')

      if (!callback) return

      doAnimate ?
        this.$backdrop
          .one('bsTransitionEnd', callback)
          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
        callback()

    } else if (!this.isShown && this.$backdrop) {
      this.$backdrop.removeClass('in')

      var callbackRemove = function () {
        that.removeBackdrop()
        callback && callback()
      }
      $.support.transition && this.$element.hasClass('fade') ?
        this.$backdrop
          .one('bsTransitionEnd', callbackRemove)
          .emulateTransitionEnd(Modal.BACKDROP_TRANSITION_DURATION) :
        callbackRemove()

    } else if (callback) {
      callback()
    }
  }

  // these following methods are used to handle overflowing modals

  Modal.prototype.handleUpdate = function () {
    this.adjustDialog()
  }

  Modal.prototype.adjustDialog = function () {
    var modalIsOverflowing = this.$element[0].scrollHeight > document.documentElement.clientHeight

    this.$element.css({
      paddingLeft:  !this.bodyIsOverflowing && modalIsOverflowing ? this.scrollbarWidth : '',
      paddingRight: this.bodyIsOverflowing && !modalIsOverflowing ? this.scrollbarWidth : ''
    })
  }

  Modal.prototype.resetAdjustments = function () {
    this.$element.css({
      paddingLeft: '',
      paddingRight: ''
    })
  }

  Modal.prototype.checkScrollbar = function () {
    var fullWindowWidth = window.innerWidth
    if (!fullWindowWidth) { // workaround for missing window.innerWidth in IE8
      var documentElementRect = document.documentElement.getBoundingClientRect()
      fullWindowWidth = documentElementRect.right - Math.abs(documentElementRect.left)
    }
    this.bodyIsOverflowing = document.body.clientWidth < fullWindowWidth
    this.scrollbarWidth = this.measureScrollbar()
  }

  Modal.prototype.setScrollbar = function () {
    var bodyPad = parseInt((this.$body.css('padding-right') || 0), 10)
    this.originalBodyPad = document.body.style.paddingRight || ''
    if (this.bodyIsOverflowing) this.$body.css('padding-right', bodyPad + this.scrollbarWidth)
  }

  Modal.prototype.resetScrollbar = function () {
    this.$body.css('padding-right', this.originalBodyPad)
  }

  Modal.prototype.measureScrollbar = function () { // thx walsh
    var scrollDiv = document.createElement('div')
    scrollDiv.className = 'modal-scrollbar-measure'
    this.$body.append(scrollDiv)
    var scrollbarWidth = scrollDiv.offsetWidth - scrollDiv.clientWidth
    this.$body[0].removeChild(scrollDiv)
    return scrollbarWidth
  }


  // MODAL PLUGIN DEFINITION
  // =======================

  function Plugin(option, _relatedTarget) {
    return this.each(function () {
      var $this   = $(this)
      var data    = $this.data('bs.modal')
      var options = $.extend({}, Modal.DEFAULTS, $this.data(), typeof option == 'object' && option)

      if (!data) $this.data('bs.modal', (data = new Modal(this, options)))
      if (typeof option == 'string') data[option](_relatedTarget)
      else if (options.show) data.show(_relatedTarget)
    })
  }

  var old = $.fn.modal

  $.fn.modal             = Plugin
  $.fn.modal.Constructor = Modal


  // MODAL NO CONFLICT
  // =================

  $.fn.modal.noConflict = function () {
    $.fn.modal = old
    return this
  }


  // MODAL DATA-API
  // ==============

  $(document).on('click.bs.modal.data-api', '[data-toggle="modal"]', function (e) {
    var $this   = $(this)
    var href    = $this.attr('href')
    var $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) // strip for ie7
    var option  = $target.data('bs.modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())

    if ($this.is('a')) e.preventDefault()

    $target.one('show.bs.modal', function (showEvent) {
      if (showEvent.isDefaultPrevented()) return // only register focus restorer if modal will actually get shown
      $target.one('hidden.bs.modal', function () {
        $this.is(':visible') && $this.trigger('focus')
      })
    })
    Plugin.call($target, option, this)
  })

}(jQuery);
(function($) {

  var cocoon_element_counter = 0;

  var create_new_id = function() {
    return (new Date().getTime() + cocoon_element_counter++);
  }

  var newcontent_braced = function(id) {
    return '[' + id + ']$1';
  }

  var newcontent_underscord = function(id) {
    return '_' + id + '_$1';
  }

  var getInsertionNodeElem = function(insertionNode, insertionTraversal, $this){

    if (!insertionNode){
      return $this.parent();
    }

    if (typeof insertionNode == 'function'){
      if(insertionTraversal){
        console.warn('association-insertion-traversal is ignored, because association-insertion-node is given as a function.')
      }
      return insertionNode($this);
    }

    if(typeof insertionNode == 'string'){
      if (insertionTraversal){
        return $this[insertionTraversal](insertionNode);
      }else{
        return insertionNode == "this" ? $this : $(insertionNode);
      }
    }

  }

  $(document).on('click', '.add_fields', function(e) {
    e.preventDefault();
    e.stopPropagation();
    
    var $this                 = $(this),
        assoc                 = $this.data('association'),
        assocs                = $this.data('associations'),
        content               = $this.data('association-insertion-template'),
        insertionMethod       = $this.data('association-insertion-method') || $this.data('association-insertion-position') || 'before',
        insertionNode         = $this.data('association-insertion-node'),
        insertionTraversal    = $this.data('association-insertion-traversal'),
        count                 = parseInt($this.data('count'), 10),
        regexp_braced         = new RegExp('\\[new_' + assoc + '\\](.*?\\s)', 'g'),
        regexp_underscord     = new RegExp('_new_' + assoc + '_(\\w*)', 'g'),
        new_id                = create_new_id(),
        new_content           = content.replace(regexp_braced, newcontent_braced(new_id)),
        new_contents          = [],
        originalEvent         = e;


    if (new_content == content) {
      regexp_braced     = new RegExp('\\[new_' + assocs + '\\](.*?\\s)', 'g');
      regexp_underscord = new RegExp('_new_' + assocs + '_(\\w*)', 'g');
      new_content       = content.replace(regexp_braced, newcontent_braced(new_id));
    }

    new_content = new_content.replace(regexp_underscord, newcontent_underscord(new_id));
    new_contents = [new_content];

    count = (isNaN(count) ? 1 : Math.max(count, 1));
    count -= 1;

    while (count) {
      new_id      = create_new_id();
      new_content = content.replace(regexp_braced, newcontent_braced(new_id));
      new_content = new_content.replace(regexp_underscord, newcontent_underscord(new_id));
      new_contents.push(new_content);

      count -= 1;
    }

    var insertionNodeElem = getInsertionNodeElem(insertionNode, insertionTraversal, $this)

    if( !insertionNodeElem || (insertionNodeElem.length == 0) ){
      console.warn("Couldn't find the element to insert the template. Make sure your `data-association-insertion-*` on `link_to_add_association` is correct.")
    }

    $.each(new_contents, function(i, node) {
      var contentNode = $(node);

      var before_insert = jQuery.Event('cocoon:before-insert');
      insertionNodeElem.trigger(before_insert, [contentNode, originalEvent]);

      if (!before_insert.isDefaultPrevented()) {
        // allow any of the jquery dom manipulation methods (after, before, append, prepend, etc)
        // to be called on the node.  allows the insertion node to be the parent of the inserted
        // code and doesn't force it to be a sibling like after/before does. default: 'before'
        var addedContent = insertionNodeElem[insertionMethod](contentNode);

        insertionNodeElem.trigger('cocoon:after-insert', [contentNode,
          originalEvent]);
      }
    });
  });

  $(document).on('click', '.remove_fields.dynamic, .remove_fields.existing', function(e) {
    var $this = $(this),
        wrapper_class = $this.data('wrapper-class') || 'nested-fields',
        node_to_delete = $this.closest('.' + wrapper_class),
        trigger_node = node_to_delete.parent(),
        originalEvent = e;

    e.preventDefault();
    e.stopPropagation();

    var before_remove = jQuery.Event('cocoon:before-remove');
    trigger_node.trigger(before_remove, [node_to_delete, originalEvent]);

    if (!before_remove.isDefaultPrevented()) {
      var timeout = trigger_node.data('remove-timeout') || 0;

      setTimeout(function() {
        if ($this.hasClass('dynamic')) {
            node_to_delete.detach();
        } else {
            $this.prev("input[type=hidden]").val("1");
            node_to_delete.hide();
        }
        trigger_node.trigger('cocoon:after-remove', [node_to_delete,
          originalEvent]);
      }, timeout);
    }
  });


  $(document).on("ready page:load turbolinks:load", function() {
    $('.remove_fields.existing.destroyed').each(function(i, obj) {
      var $this = $(this),
          wrapper_class = $this.data('wrapper-class') || 'nested-fields';

      $this.closest('.' + wrapper_class).hide();
    });
  });

})(jQuery);


/*!
 * Datepicker for Bootstrap v1.10.0 (https://github.com/uxsolutions/bootstrap-datepicker)
 *
 * Licensed under the Apache License v2.0 (https://www.apache.org/licenses/LICENSE-2.0)
 */


(function(factory){
    if (typeof define === 'function' && define.amd) {
        define(['jquery'], factory);
    } else if (typeof exports === 'object') {
        factory(require('jquery'));
    } else {
        factory(jQuery);
    }
}(function($, undefined){
	function UTCDate(){
		return new Date(Date.UTC.apply(Date, arguments));
	}
	function UTCToday(){
		var today = new Date();
		return UTCDate(today.getFullYear(), today.getMonth(), today.getDate());
	}
	function isUTCEquals(date1, date2) {
		return (
			date1.getUTCFullYear() === date2.getUTCFullYear() &&
			date1.getUTCMonth() === date2.getUTCMonth() &&
			date1.getUTCDate() === date2.getUTCDate()
		);
	}
	function alias(method, deprecationMsg){
		return function(){
			if (deprecationMsg !== undefined) {
				$.fn.datepicker.deprecated(deprecationMsg);
			}

			return this[method].apply(this, arguments);
		};
	}
	function isValidDate(d) {
		return d && !isNaN(d.getTime());
	}

	var DateArray = (function(){
		var extras = {
			get: function(i){
				return this.slice(i)[0];
			},
			contains: function(d){
				// Array.indexOf is not cross-browser;
				// $.inArray doesn't work with Dates
				var val = d && d.valueOf();
				for (var i=0, l=this.length; i < l; i++)
          // Use date arithmetic to allow dates with different times to match
          if (0 <= this[i].valueOf() - val && this[i].valueOf() - val < 1000*60*60*24)
						return i;
				return -1;
			},
			remove: function(i){
				this.splice(i,1);
			},
			replace: function(new_array){
				if (!new_array)
					return;
				if (!Array.isArray(new_array))
					new_array = [new_array];
				this.clear();
				this.push.apply(this, new_array);
			},
			clear: function(){
				this.length = 0;
			},
			copy: function(){
				var a = new DateArray();
				a.replace(this);
				return a;
			}
		};

		return function(){
			var a = [];
			a.push.apply(a, arguments);
			$.extend(a, extras);
			return a;
		};
	})();


	// Picker object

	var Datepicker = function(element, options){
		$.data(element, 'datepicker', this);

		this._events = [];
		this._secondaryEvents = [];

		this._process_options(options);

		this.dates = new DateArray();
		this.viewDate = this.o.defaultViewDate;
		this.focusDate = null;

		this.element = $(element);
		this.isInput = this.element.is('input');
		this.inputField = this.isInput ? this.element : this.element.find('input');
		this.component = this.element.hasClass('date') ? this.element.find('.add-on, .input-group-addon, .input-group-append, .input-group-prepend, .btn') : false;
		if (this.component && this.component.length === 0){
			this.component = false;
    }

		if (this.o.isInline === null){
			this.isInline = !this.component && !this.isInput;
		} else {
			this.isInline = this.o.isInline;
		}

		this.picker = $(DPGlobal.template);

		// Checking templates and inserting
		if (this._check_template(this.o.templates.leftArrow)) {
			this.picker.find('.prev').html(this.o.templates.leftArrow);
		}

		if (this._check_template(this.o.templates.rightArrow)) {
			this.picker.find('.next').html(this.o.templates.rightArrow);
		}

		this._buildEvents();
		this._attachEvents();

		if (this.isInline){
			this.picker.addClass('datepicker-inline').appendTo(this.element);
		}
		else {
			this.picker.addClass('datepicker-dropdown dropdown-menu');
		}

		if (this.o.rtl){
			this.picker.addClass('datepicker-rtl');
		}

		if (this.o.calendarWeeks) {
			this.picker.find('.datepicker-days .datepicker-switch, thead .datepicker-title, tfoot .today, tfoot .clear')
				.attr('colspan', function(i, val){
					return Number(val) + 1;
				});
		}

		this._process_options({
			startDate: this._o.startDate,
			endDate: this._o.endDate,
			daysOfWeekDisabled: this.o.daysOfWeekDisabled,
			daysOfWeekHighlighted: this.o.daysOfWeekHighlighted,
			datesDisabled: this.o.datesDisabled
		});

		this._allow_update = false;
		this.setViewMode(this.o.startView);
		this._allow_update = true;

		this.fillDow();
		this.fillMonths();

		this.update();

		if (this.isInline){
			this.show();
		}
	};

	Datepicker.prototype = {
		constructor: Datepicker,

		_resolveViewName: function(view){
			$.each(DPGlobal.viewModes, function(i, viewMode){
				if (view === i || $.inArray(view, viewMode.names) !== -1){
					view = i;
					return false;
				}
			});

			return view;
		},

		_resolveDaysOfWeek: function(daysOfWeek){
			if (!Array.isArray(daysOfWeek))
				daysOfWeek = daysOfWeek.split(/[,\s]*/);
			return $.map(daysOfWeek, Number);
		},

		_check_template: function(tmp){
			try {
				// If empty
				if (tmp === undefined || tmp === "") {
					return false;
				}
				// If no html, everything ok
				if ((tmp.match(/[<>]/g) || []).length <= 0) {
					return true;
				}
				// Checking if html is fine
				var jDom = $(tmp);
				return jDom.length > 0;
			}
			catch (ex) {
				return false;
			}
		},

		_process_options: function(opts){
			// Store raw options for reference
			this._o = $.extend({}, this._o, opts);
			// Processed options
			var o = this.o = $.extend({}, this._o);

			// Check if "de-DE" style date is available, if not language should
			// fallback to 2 letter code eg "de"
			var lang = o.language;
			if (!dates[lang]){
				lang = lang.split('-')[0];
				if (!dates[lang])
					lang = defaults.language;
			}
			o.language = lang;

			// Retrieve view index from any aliases
			o.startView = this._resolveViewName(o.startView);
			o.minViewMode = this._resolveViewName(o.minViewMode);
			o.maxViewMode = this._resolveViewName(o.maxViewMode);

			// Check view is between min and max
			o.startView = Math.max(this.o.minViewMode, Math.min(this.o.maxViewMode, o.startView));

			// true, false, or Number > 0
			if (o.multidate !== true){
				o.multidate = Number(o.multidate) || false;
				if (o.multidate !== false)
					o.multidate = Math.max(0, o.multidate);
			}
			o.multidateSeparator = String(o.multidateSeparator);

			o.weekStart %= 7;
			o.weekEnd = (o.weekStart + 6) % 7;

			var format = DPGlobal.parseFormat(o.format);
			if (o.startDate !== -Infinity){
				if (!!o.startDate){
					if (o.startDate instanceof Date)
						o.startDate = this._local_to_utc(this._zero_time(o.startDate));
					else
						o.startDate = DPGlobal.parseDate(o.startDate, format, o.language, o.assumeNearbyYear);
				}
				else {
					o.startDate = -Infinity;
				}
			}
			if (o.endDate !== Infinity){
				if (!!o.endDate){
					if (o.endDate instanceof Date)
						o.endDate = this._local_to_utc(this._zero_time(o.endDate));
					else
						o.endDate = DPGlobal.parseDate(o.endDate, format, o.language, o.assumeNearbyYear);
				}
				else {
					o.endDate = Infinity;
				}
			}

			o.daysOfWeekDisabled = this._resolveDaysOfWeek(o.daysOfWeekDisabled||[]);
			o.daysOfWeekHighlighted = this._resolveDaysOfWeek(o.daysOfWeekHighlighted||[]);

			o.datesDisabled = o.datesDisabled||[];
			if (!Array.isArray(o.datesDisabled)) {
				o.datesDisabled = o.datesDisabled.split(',');
			}
			o.datesDisabled = $.map(o.datesDisabled, function(d){
				return DPGlobal.parseDate(d, format, o.language, o.assumeNearbyYear);
			});

			var plc = String(o.orientation).toLowerCase().split(/\s+/g),
				_plc = o.orientation.toLowerCase();
			plc = $.grep(plc, function(word){
				return /^auto|left|right|top|bottom$/.test(word);
			});
			o.orientation = {x: 'auto', y: 'auto'};
			if (!_plc || _plc === 'auto')
				; // no action
			else if (plc.length === 1){
				switch (plc[0]){
					case 'top':
					case 'bottom':
						o.orientation.y = plc[0];
						break;
					case 'left':
					case 'right':
						o.orientation.x = plc[0];
						break;
				}
			}
			else {
				_plc = $.grep(plc, function(word){
					return /^left|right$/.test(word);
				});
				o.orientation.x = _plc[0] || 'auto';

				_plc = $.grep(plc, function(word){
					return /^top|bottom$/.test(word);
				});
				o.orientation.y = _plc[0] || 'auto';
			}
			if (o.defaultViewDate instanceof Date || typeof o.defaultViewDate === 'string') {
				o.defaultViewDate = DPGlobal.parseDate(o.defaultViewDate, format, o.language, o.assumeNearbyYear);
			} else if (o.defaultViewDate) {
				var year = o.defaultViewDate.year || new Date().getFullYear();
				var month = o.defaultViewDate.month || 0;
				var day = o.defaultViewDate.day || 1;
				o.defaultViewDate = UTCDate(year, month, day);
			} else {
				o.defaultViewDate = UTCToday();
			}
		},
		_applyEvents: function(evs){
			for (var i=0, el, ch, ev; i < evs.length; i++){
				el = evs[i][0];
				if (evs[i].length === 2){
					ch = undefined;
					ev = evs[i][1];
				} else if (evs[i].length === 3){
					ch = evs[i][1];
					ev = evs[i][2];
				}
				el.on(ev, ch);
			}
		},
		_unapplyEvents: function(evs){
			for (var i=0, el, ev, ch; i < evs.length; i++){
				el = evs[i][0];
				if (evs[i].length === 2){
					ch = undefined;
					ev = evs[i][1];
				} else if (evs[i].length === 3){
					ch = evs[i][1];
					ev = evs[i][2];
				}
				el.off(ev, ch);
			}
		},
		_buildEvents: function(){
            var events = {
                keyup: $.proxy(function(e){
                    if ($.inArray(e.keyCode, [27, 37, 39, 38, 40, 32, 13, 9]) === -1)
                        this.update();
                }, this),
                keydown: $.proxy(this.keydown, this),
                paste: $.proxy(this.paste, this)
            };

            if (this.o.showOnFocus === true) {
                events.focus = $.proxy(this.show, this);
            }

            if (this.isInput) { // single input
                this._events = [
                    [this.element, events]
                ];
            }
            // component: input + button
            else if (this.component && this.inputField.length) {
                this._events = [
                    // For components that are not readonly, allow keyboard nav
                    [this.inputField, events],
                    [this.component, {
                        click: $.proxy(this.show, this)
                    }]
                ];
            }
			else {
				this._events = [
					[this.element, {
						click: $.proxy(this.show, this),
						keydown: $.proxy(this.keydown, this)
					}]
				];
			}
			this._events.push(
				// Component: listen for blur on element descendants
				[this.element, '*', {
					blur: $.proxy(function(e){
						this._focused_from = e.target;
					}, this)
				}],
				// Input: listen for blur on element
				[this.element, {
					blur: $.proxy(function(e){
						this._focused_from = e.target;
					}, this)
				}]
			);

			if (this.o.immediateUpdates) {
				// Trigger input updates immediately on changed year/month
				this._events.push([this.element, {
					'changeYear changeMonth': $.proxy(function(e){
						this.update(e.date);
					}, this)
				}]);
			}

			this._secondaryEvents = [
				[this.picker, {
					click: $.proxy(this.click, this)
				}],
				[this.picker, '.prev, .next', {
					click: $.proxy(this.navArrowsClick, this)
				}],
				[this.picker, '.day:not(.disabled)', {
					click: $.proxy(this.dayCellClick, this)
				}],
				[$(window), {
					resize: $.proxy(this.place, this)
				}],
				[$(document), {
					'mousedown touchstart': $.proxy(function(e){
						// Clicked outside the datepicker, hide it
						if (!(
							this.element.is(e.target) ||
							this.element.find(e.target).length ||
							this.picker.is(e.target) ||
							this.picker.find(e.target).length ||
							this.isInline
						)){
							this.hide();
						}
					}, this)
				}]
			];
		},
		_attachEvents: function(){
			this._detachEvents();
			this._applyEvents(this._events);
		},
		_detachEvents: function(){
			this._unapplyEvents(this._events);
		},
		_attachSecondaryEvents: function(){
			this._detachSecondaryEvents();
			this._applyEvents(this._secondaryEvents);
		},
		_detachSecondaryEvents: function(){
			this._unapplyEvents(this._secondaryEvents);
		},
		_trigger: function(event, altdate){
			var date = altdate || this.dates.get(-1),
				local_date = this._utc_to_local(date);

			this.element.trigger({
				type: event,
				date: local_date,
				viewMode: this.viewMode,
				dates: $.map(this.dates, this._utc_to_local),
				format: $.proxy(function(ix, format){
					if (arguments.length === 0){
						ix = this.dates.length - 1;
						format = this.o.format;
					} else if (typeof ix === 'string'){
						format = ix;
						ix = this.dates.length - 1;
					}
					format = format || this.o.format;
					var date = this.dates.get(ix);
					return DPGlobal.formatDate(date, format, this.o.language);
				}, this)
			});
		},

		show: function(){
			if (this.inputField.is(':disabled') || (this.inputField.prop('readonly') && this.o.enableOnReadonly === false))
				return;
			if (!this.isInline)
				this.picker.appendTo(this.o.container);
			this.place();
			this.picker.show();
			this._attachSecondaryEvents();
			this._trigger('show');
			if ((window.navigator.msMaxTouchPoints || 'ontouchstart' in document) && this.o.disableTouchKeyboard) {
				$(this.element).blur();
			}
			return this;
		},

		hide: function(){
			if (this.isInline || !this.picker.is(':visible'))
				return this;
			this.focusDate = null;
			this.picker.hide().detach();
			this._detachSecondaryEvents();
			this.setViewMode(this.o.startView);

			if (this.o.forceParse && this.inputField.val())
				this.setValue();
			this._trigger('hide');
			return this;
		},

		destroy: function(){
			this.hide();
			this._detachEvents();
			this._detachSecondaryEvents();
			this.picker.remove();
			delete this.element.data().datepicker;
			if (!this.isInput){
				delete this.element.data().date;
			}
			return this;
		},

		paste: function(e){
			var dateString;
			if (e.originalEvent.clipboardData && e.originalEvent.clipboardData.types
				&& $.inArray('text/plain', e.originalEvent.clipboardData.types) !== -1) {
				dateString = e.originalEvent.clipboardData.getData('text/plain');
			} else if (window.clipboardData) {
				dateString = window.clipboardData.getData('Text');
			} else {
				return;
			}
			this.setDate(dateString);
			this.update();
			e.preventDefault();
		},

		_utc_to_local: function(utc){
			if (!utc) {
				return utc;
			}

			var local = new Date(utc.getTime() + (utc.getTimezoneOffset() * 60000));

			if (local.getTimezoneOffset() !== utc.getTimezoneOffset()) {
				local = new Date(utc.getTime() + (local.getTimezoneOffset() * 60000));
			}

			return local;
		},
		_local_to_utc: function(local){
			return local && new Date(local.getTime() - (local.getTimezoneOffset()*60000));
		},
		_zero_time: function(local){
			return local && new Date(local.getFullYear(), local.getMonth(), local.getDate());
		},
		_zero_utc_time: function(utc){
			return utc && UTCDate(utc.getUTCFullYear(), utc.getUTCMonth(), utc.getUTCDate());
		},

		getDates: function(){
			return $.map(this.dates, this._utc_to_local);
		},

		getUTCDates: function(){
			return $.map(this.dates, function(d){
				return new Date(d);
			});
		},

		getDate: function(){
			return this._utc_to_local(this.getUTCDate());
		},

		getUTCDate: function(){
			var selected_date = this.dates.get(-1);
			if (selected_date !== undefined) {
				return new Date(selected_date);
			} else {
				return null;
			}
		},

		clearDates: function(){
			this.inputField.val('');
			this._trigger('changeDate');
			this.update();
			if (this.o.autoclose) {
				this.hide();
			}
		},

		setDates: function(){
			var args = Array.isArray(arguments[0]) ? arguments[0] : arguments;
			this.update.apply(this, args);
			this._trigger('changeDate');
			this.setValue();
			return this;
		},

		setUTCDates: function(){
			var args = Array.isArray(arguments[0]) ? arguments[0] : arguments;
			this.setDates.apply(this, $.map(args, this._utc_to_local));
			return this;
		},

		setDate: alias('setDates'),
		setUTCDate: alias('setUTCDates'),
		remove: alias('destroy', 'Method `remove` is deprecated and will be removed in version 2.0. Use `destroy` instead'),

		setValue: function(){
			var formatted = this.getFormattedDate();
			this.inputField.val(formatted);
			return this;
		},

		getFormattedDate: function(format){
			if (format === undefined)
				format = this.o.format;

			var lang = this.o.language;
			return $.map(this.dates, function(d){
				return DPGlobal.formatDate(d, format, lang);
			}).join(this.o.multidateSeparator);
		},

		getStartDate: function(){
			return this.o.startDate;
		},

		setStartDate: function(startDate){
			this._process_options({startDate: startDate});
			this.update();
			this.updateNavArrows();
			return this;
		},

		getEndDate: function(){
			return this.o.endDate;
		},

		setEndDate: function(endDate){
			this._process_options({endDate: endDate});
			this.update();
			this.updateNavArrows();
			return this;
		},

		setDaysOfWeekDisabled: function(daysOfWeekDisabled){
			this._process_options({daysOfWeekDisabled: daysOfWeekDisabled});
			this.update();
			return this;
		},

		setDaysOfWeekHighlighted: function(daysOfWeekHighlighted){
			this._process_options({daysOfWeekHighlighted: daysOfWeekHighlighted});
			this.update();
			return this;
		},

		setDatesDisabled: function(datesDisabled){
			this._process_options({datesDisabled: datesDisabled});
			this.update();
			return this;
		},

		place: function(){
			if (this.isInline)
				return this;
			var calendarWidth = this.picker.outerWidth(),
				calendarHeight = this.picker.outerHeight(),
				visualPadding = 10,
				container = $(this.o.container),
				windowWidth = container.width(),
				scrollTop = this.o.container === 'body' ? $(document).scrollTop() : container.scrollTop(),
				appendOffset = container.offset();

			var parentsZindex = [0];
			this.element.parents().each(function(){
				var itemZIndex = $(this).css('z-index');
				if (itemZIndex !== 'auto' && Number(itemZIndex) !== 0) parentsZindex.push(Number(itemZIndex));
			});
			var zIndex = Math.max.apply(Math, parentsZindex) + this.o.zIndexOffset;
			var offset = this.component ? this.component.parent().offset() : this.element.offset();
			var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(false);
			var width = this.component ? this.component.outerWidth(true) : this.element.outerWidth(false);
			var left = offset.left - appendOffset.left;
			var top = offset.top - appendOffset.top;

			if (this.o.container !== 'body') {
				top += scrollTop;
			}

			this.picker.removeClass(
				'datepicker-orient-top datepicker-orient-bottom '+
				'datepicker-orient-right datepicker-orient-left'
			);

			if (this.o.orientation.x !== 'auto'){
				this.picker.addClass('datepicker-orient-' + this.o.orientation.x);
				if (this.o.orientation.x === 'right')
					left -= calendarWidth - width;
			}
			// auto x orientation is best-placement: if it crosses a window
			// edge, fudge it sideways
			else {
				if (offset.left < 0) {
					// component is outside the window on the left side. Move it into visible range
					this.picker.addClass('datepicker-orient-left');
					left -= offset.left - visualPadding;
				} else if (left + calendarWidth > windowWidth) {
					// the calendar passes the widow right edge. Align it to component right side
					this.picker.addClass('datepicker-orient-right');
					left += width - calendarWidth;
				} else {
					if (this.o.rtl) {
						// Default to right
						this.picker.addClass('datepicker-orient-right');
					} else {
						// Default to left
						this.picker.addClass('datepicker-orient-left');
					}
				}
			}

			// auto y orientation is best-situation: top or bottom, no fudging,
			// decision based on which shows more of the calendar
			var yorient = this.o.orientation.y,
				top_overflow;
			if (yorient === 'auto'){
				top_overflow = -scrollTop + top - calendarHeight;
				yorient = top_overflow < 0 ? 'bottom' : 'top';
			}

			this.picker.addClass('datepicker-orient-' + yorient);
			if (yorient === 'top')
				top -= calendarHeight + parseInt(this.picker.css('padding-top'));
			else
				top += height;

			if (this.o.rtl) {
				var right = windowWidth - (left + width);
				this.picker.css({
					top: top,
					right: right,
					zIndex: zIndex
				});
			} else {
				this.picker.css({
					top: top,
					left: left,
					zIndex: zIndex
				});
			}
			return this;
		},

		_allow_update: true,
		update: function(){
			if (!this._allow_update)
				return this;

			var oldDates = this.dates.copy(),
				dates = [],
				fromArgs = false;
			if (arguments.length){
				$.each(arguments, $.proxy(function(i, date){
					if (date instanceof Date)
						date = this._local_to_utc(date);
					dates.push(date);
				}, this));
				fromArgs = true;
			} else {
				dates = this.isInput
						? this.element.val()
						: this.element.data('date') || this.inputField.val();
				if (dates && this.o.multidate)
					dates = dates.split(this.o.multidateSeparator);
				else
					dates = [dates];
				delete this.element.data().date;
			}

			dates = $.map(dates, $.proxy(function(date){
				return DPGlobal.parseDate(date, this.o.format, this.o.language, this.o.assumeNearbyYear);
			}, this));
			dates = $.grep(dates, $.proxy(function(date){
				return (
					!this.dateWithinRange(date) ||
					!date
				);
			}, this), true);
			this.dates.replace(dates);

			if (this.o.updateViewDate) {
				if (this.dates.length)
					this.viewDate = new Date(this.dates.get(-1));
				else if (this.viewDate < this.o.startDate)
					this.viewDate = new Date(this.o.startDate);
				else if (this.viewDate > this.o.endDate)
					this.viewDate = new Date(this.o.endDate);
				else
					this.viewDate = this.o.defaultViewDate;
			}

			if (fromArgs){
				// setting date by clicking
				this.setValue();
				this.element.change();
			}
			else if (this.dates.length){
				// setting date by typing
				if (String(oldDates) !== String(this.dates) && fromArgs) {
					this._trigger('changeDate');
					this.element.change();
				}
			}
			if (!this.dates.length && oldDates.length) {
				this._trigger('clearDate');
				this.element.change();
			}

			this.fill();
			return this;
		},

		fillDow: function(){
      if (this.o.showWeekDays) {
			var dowCnt = this.o.weekStart,
				html = '<tr>';
			if (this.o.calendarWeeks){
				html += '<th class="cw">&#160;</th>';
			}
			while (dowCnt < this.o.weekStart + 7){
				html += '<th class="dow';
        if ($.inArray(dowCnt, this.o.daysOfWeekDisabled) !== -1)
          html += ' disabled';
        html += '">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>';
			}
			html += '</tr>';
			this.picker.find('.datepicker-days thead').append(html);
      }
		},

		fillMonths: function(){
      var localDate = this._utc_to_local(this.viewDate);
			var html = '';
			var focused;
			for (var i = 0; i < 12; i++){
				focused = localDate && localDate.getMonth() === i ? ' focused' : '';
				html += '<span class="month' + focused + '">' + dates[this.o.language].monthsShort[i] + '</span>';
			}
			this.picker.find('.datepicker-months td').html(html);
		},

		setRange: function(range){
			if (!range || !range.length)
				delete this.range;
			else
				this.range = $.map(range, function(d){
					return d.valueOf();
				});
			this.fill();
		},

		getClassNames: function(date){
			var cls = [],
				year = this.viewDate.getUTCFullYear(),
				month = this.viewDate.getUTCMonth(),
				today = UTCToday();
			if (date.getUTCFullYear() < year || (date.getUTCFullYear() === year && date.getUTCMonth() < month)){
				cls.push('old');
			} else if (date.getUTCFullYear() > year || (date.getUTCFullYear() === year && date.getUTCMonth() > month)){
				cls.push('new');
			}
			if (this.focusDate && date.valueOf() === this.focusDate.valueOf())
				cls.push('focused');
			// Compare internal UTC date with UTC today, not local today
			if (this.o.todayHighlight && isUTCEquals(date, today)) {
				cls.push('today');
			}
			if (this.dates.contains(date) !== -1)
				cls.push('active');
			if (!this.dateWithinRange(date)){
				cls.push('disabled');
			}
			if (this.dateIsDisabled(date)){
				cls.push('disabled', 'disabled-date');
			}
			if ($.inArray(date.getUTCDay(), this.o.daysOfWeekHighlighted) !== -1){
				cls.push('highlighted');
			}

			if (this.range){
				if (date > this.range[0] && date < this.range[this.range.length-1]){
					cls.push('range');
				}
				if ($.inArray(date.valueOf(), this.range) !== -1){
					cls.push('selected');
				}
				if (date.valueOf() === this.range[0]){
          cls.push('range-start');
        }
        if (date.valueOf() === this.range[this.range.length-1]){
          cls.push('range-end');
        }
			}
			return cls;
		},

		_fill_yearsView: function(selector, cssClass, factor, year, startYear, endYear, beforeFn){
			var html = '';
			var step = factor / 10;
			var view = this.picker.find(selector);
			var startVal = Math.floor(year / factor) * factor;
			var endVal = startVal + step * 9;
			var focusedVal = Math.floor(this.viewDate.getFullYear() / step) * step;
			var selected = $.map(this.dates, function(d){
				return Math.floor(d.getUTCFullYear() / step) * step;
			});

			var classes, tooltip, before;
			for (var currVal = startVal - step; currVal <= endVal + step; currVal += step) {
				classes = [cssClass];
				tooltip = null;

				if (currVal === startVal - step) {
					classes.push('old');
				} else if (currVal === endVal + step) {
					classes.push('new');
				}
				if ($.inArray(currVal, selected) !== -1) {
					classes.push('active');
				}
				if (currVal < startYear || currVal > endYear) {
					classes.push('disabled');
				}
				if (currVal === focusedVal) {
				  classes.push('focused');
        }

				if (beforeFn !== $.noop) {
					before = beforeFn(new Date(currVal, 0, 1));
					if (before === undefined) {
						before = {};
					} else if (typeof before === 'boolean') {
						before = {enabled: before};
					} else if (typeof before === 'string') {
						before = {classes: before};
					}
					if (before.enabled === false) {
						classes.push('disabled');
					}
					if (before.classes) {
						classes = classes.concat(before.classes.split(/\s+/));
					}
					if (before.tooltip) {
						tooltip = before.tooltip;
					}
				}

				html += '<span class="' + classes.join(' ') + '"' + (tooltip ? ' title="' + tooltip + '"' : '') + '>' + currVal + '</span>';
			}

			view.find('.datepicker-switch').text(startVal + '-' + endVal);
			view.find('td').html(html);
		},

		fill: function(){
			var d = new Date(this.viewDate),
				year = d.getUTCFullYear(),
				month = d.getUTCMonth(),
				startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,
				startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,
				endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,
				endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,
				todaytxt = dates[this.o.language].today || dates['en'].today || '',
				cleartxt = dates[this.o.language].clear || dates['en'].clear || '',
        titleFormat = dates[this.o.language].titleFormat || dates['en'].titleFormat,
        todayDate = UTCToday(),
        titleBtnVisible = (this.o.todayBtn === true || this.o.todayBtn === 'linked') && todayDate >= this.o.startDate && todayDate <= this.o.endDate && !this.weekOfDateIsDisabled(todayDate),
				tooltip,
				before;
			if (isNaN(year) || isNaN(month))
				return;
			this.picker.find('.datepicker-days .datepicker-switch')
						.text(DPGlobal.formatDate(d, titleFormat, this.o.language));
			this.picker.find('tfoot .today')
						.text(todaytxt)
            .css('display', titleBtnVisible ? 'table-cell' : 'none');
			this.picker.find('tfoot .clear')
						.text(cleartxt)
						.css('display', this.o.clearBtn === true ? 'table-cell' : 'none');
			this.picker.find('thead .datepicker-title')
						.text(this.o.title)
						.css('display', typeof this.o.title === 'string' && this.o.title !== '' ? 'table-cell' : 'none');
			this.updateNavArrows();
			this.fillMonths();
			var prevMonth = UTCDate(year, month, 0),
				day = prevMonth.getUTCDate();
			prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7);
			var nextMonth = new Date(prevMonth);
			if (prevMonth.getUTCFullYear() < 100){
        nextMonth.setUTCFullYear(prevMonth.getUTCFullYear());
      }
			nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
			nextMonth = nextMonth.valueOf();
			var html = [];
			var weekDay, clsName;
			while (prevMonth.valueOf() < nextMonth){
				weekDay = prevMonth.getUTCDay();
				if (weekDay === this.o.weekStart){
					html.push('<tr>');
					if (this.o.calendarWeeks){
						// ISO 8601: First week contains first thursday.
						// ISO also states week starts on Monday, but we can be more abstract here.
						var
							// Start of current week: based on weekstart/current date
							ws = new Date(+prevMonth + (this.o.weekStart - weekDay - 7) % 7 * 864e5),
							// Thursday of this week
							th = new Date(Number(ws) + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),
							// First Thursday of year, year from thursday
							yth = new Date(Number(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay()) % 7 * 864e5),
							// Calendar week: ms between thursdays, div ms per day, div 7 days
							calWeek = (th - yth) / 864e5 / 7 + 1;
						html.push('<td class="cw">'+ calWeek +'</td>');
					}
				}
				clsName = this.getClassNames(prevMonth);
				clsName.push('day');

				var content = prevMonth.getUTCDate();

				if (this.o.beforeShowDay !== $.noop){
					before = this.o.beforeShowDay(this._utc_to_local(prevMonth));
					if (before === undefined)
						before = {};
					else if (typeof before === 'boolean')
						before = {enabled: before};
					else if (typeof before === 'string')
						before = {classes: before};
					if (before.enabled === false)
						clsName.push('disabled');
					if (before.classes)
						clsName = clsName.concat(before.classes.split(/\s+/));
					if (before.tooltip)
						tooltip = before.tooltip;
					if (before.content)
						content = before.content;
				}

				//Check if uniqueSort exists (supported by jquery >=1.12 and >=2.2)
				//Fallback to unique function for older jquery versions
				if (typeof $.uniqueSort === "function") {
					clsName = $.uniqueSort(clsName);
				} else {
					clsName = $.unique(clsName);
				}

				html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + ' data-date="' + prevMonth.getTime().toString() + '">' + content + '</td>');
				tooltip = null;
				if (weekDay === this.o.weekEnd){
					html.push('</tr>');
				}
				prevMonth.setUTCDate(prevMonth.getUTCDate() + 1);
			}
			this.picker.find('.datepicker-days tbody').html(html.join(''));

			var monthsTitle = dates[this.o.language].monthsTitle || dates['en'].monthsTitle || 'Months';
			var months = this.picker.find('.datepicker-months')
						.find('.datepicker-switch')
							.text(this.o.maxViewMode < 2 ? monthsTitle : year)
							.end()
						.find('tbody span').removeClass('active');

			$.each(this.dates, function(i, d){
				if (d.getUTCFullYear() === year)
					months.eq(d.getUTCMonth()).addClass('active');
			});

			if (year < startYear || year > endYear){
				months.addClass('disabled');
			}
			if (year === startYear){
				months.slice(0, startMonth).addClass('disabled');
			}
			if (year === endYear){
				months.slice(endMonth+1).addClass('disabled');
			}

			if (this.o.beforeShowMonth !== $.noop){
				var that = this;
				$.each(months, function(i, month){
          var moDate = new Date(year, i, 1);
          var before = that.o.beforeShowMonth(moDate);
					if (before === undefined)
						before = {};
					else if (typeof before === 'boolean')
						before = {enabled: before};
					else if (typeof before === 'string')
						before = {classes: before};
					if (before.enabled === false && !$(month).hasClass('disabled'))
					    $(month).addClass('disabled');
					if (before.classes)
					    $(month).addClass(before.classes);
					if (before.tooltip)
					    $(month).prop('title', before.tooltip);
				});
			}

			// Generating decade/years picker
			this._fill_yearsView(
				'.datepicker-years',
				'year',
				10,
				year,
				startYear,
				endYear,
				this.o.beforeShowYear
			);

			// Generating century/decades picker
			this._fill_yearsView(
				'.datepicker-decades',
				'decade',
				100,
				year,
				startYear,
				endYear,
				this.o.beforeShowDecade
			);

			// Generating millennium/centuries picker
			this._fill_yearsView(
				'.datepicker-centuries',
				'century',
				1000,
				year,
				startYear,
				endYear,
				this.o.beforeShowCentury
			);
		},

		updateNavArrows: function(){
			if (!this._allow_update)
				return;

			var d = new Date(this.viewDate),
				year = d.getUTCFullYear(),
				month = d.getUTCMonth(),
				startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,
				startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,
				endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,
				endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,
				prevIsDisabled,
				nextIsDisabled,
				factor = 1;
			switch (this.viewMode){
				case 4:
					factor *= 10;
					/* falls through */
				case 3:
					factor *= 10;
					/* falls through */
				case 2:
					factor *= 10;
					/* falls through */
				case 1:
					prevIsDisabled = Math.floor(year / factor) * factor <= startYear;
					nextIsDisabled = Math.floor(year / factor) * factor + factor > endYear;
					break;
				case 0:
					prevIsDisabled = year <= startYear && month <= startMonth;
					nextIsDisabled = year >= endYear && month >= endMonth;
					break;
			}

			this.picker.find('.prev').toggleClass('disabled', prevIsDisabled);
			this.picker.find('.next').toggleClass('disabled', nextIsDisabled);
		},

		click: function(e){
			e.preventDefault();
			e.stopPropagation();

			var target, dir, day, year, month;
			target = $(e.target);

			// Clicked on the switch
			if (target.hasClass('datepicker-switch') && this.viewMode !== this.o.maxViewMode){
				this.setViewMode(this.viewMode + 1);
			}

			// Clicked on today button
			if (target.hasClass('today') && !target.hasClass('day')){
				this.setViewMode(0);
				this._setDate(UTCToday(), this.o.todayBtn === 'linked' ? null : 'view');
			}

			// Clicked on clear button
			if (target.hasClass('clear')){
				this.clearDates();
			}

			if (!target.hasClass('disabled')){
				// Clicked on a month, year, decade, century
				if (target.hasClass('month')
						|| target.hasClass('year')
						|| target.hasClass('decade')
						|| target.hasClass('century')) {
					this.viewDate.setUTCDate(1);

					day = 1;
					if (this.viewMode === 1){
						month = target.parent().find('span').index(target);
						year = this.viewDate.getUTCFullYear();
						this.viewDate.setUTCMonth(month);
					} else {
						month = 0;
						year = Number(target.text());
						this.viewDate.setUTCFullYear(year);
					}

					this._trigger(DPGlobal.viewModes[this.viewMode - 1].e, this.viewDate);

					if (this.viewMode === this.o.minViewMode){
						this._setDate(UTCDate(year, month, day));
					} else {
						this.setViewMode(this.viewMode - 1);
						this.fill();
					}
				}
			}

			if (this.picker.is(':visible') && this._focused_from){
				this._focused_from.focus();
			}
			delete this._focused_from;
		},

		dayCellClick: function(e){
			var $target = $(e.currentTarget);
			var timestamp = $target.data('date');
			var date = new Date(timestamp);

			if (this.o.updateViewDate) {
				if (date.getUTCFullYear() !== this.viewDate.getUTCFullYear()) {
					this._trigger('changeYear', this.viewDate);
				}

				if (date.getUTCMonth() !== this.viewDate.getUTCMonth()) {
					this._trigger('changeMonth', this.viewDate);
				}
			}
			this._setDate(date);
		},

		// Clicked on prev or next
		navArrowsClick: function(e){
			var $target = $(e.currentTarget);
			var dir = $target.hasClass('prev') ? -1 : 1;
			if (this.viewMode !== 0){
				dir *= DPGlobal.viewModes[this.viewMode].navStep * 12;
			}
			this.viewDate = this.moveMonth(this.viewDate, dir);
			this._trigger(DPGlobal.viewModes[this.viewMode].e, this.viewDate);
			this.fill();
		},

		_toggle_multidate: function(date){
			var ix = this.dates.contains(date);
			if (!date){
				this.dates.clear();
			}

			if (ix !== -1){
				if (this.o.multidate === true || this.o.multidate > 1 || this.o.toggleActive){
					this.dates.remove(ix);
				}
			} else if (this.o.multidate === false) {
				this.dates.clear();
				this.dates.push(date);
			}
			else {
				this.dates.push(date);
			}

			if (typeof this.o.multidate === 'number')
				while (this.dates.length > this.o.multidate)
					this.dates.remove(0);
		},

		_setDate: function(date, which){
			if (!which || which === 'date')
				this._toggle_multidate(date && new Date(date));
			if ((!which && this.o.updateViewDate) || which === 'view')
				this.viewDate = date && new Date(date);

			this.fill();
			this.setValue();
			if (!which || which !== 'view') {
				this._trigger('changeDate');
			}
			this.inputField.trigger('change');
			if (this.o.autoclose && (!which || which === 'date')){
				this.hide();
			}
		},

		moveDay: function(date, dir){
			var newDate = new Date(date);
			newDate.setUTCDate(date.getUTCDate() + dir);

			return newDate;
		},

		moveWeek: function(date, dir){
			return this.moveDay(date, dir * 7);
		},

		moveMonth: function(date, dir){
			if (!isValidDate(date))
				return this.o.defaultViewDate;
			if (!dir)
				return date;
			var new_date = new Date(date.valueOf()),
				day = new_date.getUTCDate(),
				month = new_date.getUTCMonth(),
				mag = Math.abs(dir),
				new_month, test;
			dir = dir > 0 ? 1 : -1;
			if (mag === 1){
				test = dir === -1
					// If going back one month, make sure month is not current month
					// (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
					? function(){
						return new_date.getUTCMonth() === month;
					}
					// If going forward one month, make sure month is as expected
					// (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
					: function(){
						return new_date.getUTCMonth() !== new_month;
					};
				new_month = month + dir;
				new_date.setUTCMonth(new_month);
				// Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
				new_month = (new_month + 12) % 12;
			}
			else {
				// For magnitudes >1, move one month at a time...
				for (var i=0; i < mag; i++)
					// ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
					new_date = this.moveMonth(new_date, dir);
				// ...then reset the day, keeping it in the new month
				new_month = new_date.getUTCMonth();
				new_date.setUTCDate(day);
				test = function(){
					return new_month !== new_date.getUTCMonth();
				};
			}
			// Common date-resetting loop -- if date is beyond end of month, make it
			// end of month
			while (test()){
				new_date.setUTCDate(--day);
				new_date.setUTCMonth(new_month);
			}
			return new_date;
		},

		moveYear: function(date, dir){
			return this.moveMonth(date, dir*12);
		},

		moveAvailableDate: function(date, dir, fn){
			do {
				date = this[fn](date, dir);

				if (!this.dateWithinRange(date))
					return false;

				fn = 'moveDay';
			}
			while (this.dateIsDisabled(date));

			return date;
		},

		weekOfDateIsDisabled: function(date){
			return $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1;
		},

		dateIsDisabled: function(date){
			return (
				this.weekOfDateIsDisabled(date) ||
				$.grep(this.o.datesDisabled, function(d){
					return isUTCEquals(date, d);
				}).length > 0
			);
		},

		dateWithinRange: function(date){
			return date >= this.o.startDate && date <= this.o.endDate;
		},

		keydown: function(e){
			if (!this.picker.is(':visible')){
				if (e.keyCode === 40 || e.keyCode === 27) { // allow down to re-show picker
					this.show();
					e.stopPropagation();
        }
				return;
			}
			var dateChanged = false,
				dir, newViewDate,
				focusDate = this.focusDate || this.viewDate;
			switch (e.keyCode){
				case 27: // escape
					if (this.focusDate){
						this.focusDate = null;
						this.viewDate = this.dates.get(-1) || this.viewDate;
						this.fill();
					}
					else
						this.hide();
					e.preventDefault();
					e.stopPropagation();
					break;
				case 37: // left
				case 38: // up
				case 39: // right
				case 40: // down
					if (!this.o.keyboardNavigation || this.o.daysOfWeekDisabled.length === 7)
						break;
					dir = e.keyCode === 37 || e.keyCode === 38 ? -1 : 1;
          if (this.viewMode === 0) {
  					if (e.ctrlKey){
  						newViewDate = this.moveAvailableDate(focusDate, dir, 'moveYear');

  						if (newViewDate)
  							this._trigger('changeYear', this.viewDate);
  					} else if (e.shiftKey){
  						newViewDate = this.moveAvailableDate(focusDate, dir, 'moveMonth');

  						if (newViewDate)
  							this._trigger('changeMonth', this.viewDate);
  					} else if (e.keyCode === 37 || e.keyCode === 39){
  						newViewDate = this.moveAvailableDate(focusDate, dir, 'moveDay');
  					} else if (!this.weekOfDateIsDisabled(focusDate)){
  						newViewDate = this.moveAvailableDate(focusDate, dir, 'moveWeek');
  					}
          } else if (this.viewMode === 1) {
            if (e.keyCode === 38 || e.keyCode === 40) {
              dir = dir * 4;
            }
            newViewDate = this.moveAvailableDate(focusDate, dir, 'moveMonth');
          } else if (this.viewMode === 2) {
            if (e.keyCode === 38 || e.keyCode === 40) {
              dir = dir * 4;
            }
            newViewDate = this.moveAvailableDate(focusDate, dir, 'moveYear');
          }
					if (newViewDate){
						this.focusDate = this.viewDate = newViewDate;
						this.setValue();
						this.fill();
						e.preventDefault();
					}
					break;
				case 13: // enter
					if (!this.o.forceParse)
						break;
					focusDate = this.focusDate || this.dates.get(-1) || this.viewDate;
					if (this.o.keyboardNavigation) {
						this._toggle_multidate(focusDate);
						dateChanged = true;
					}
					this.focusDate = null;
					this.viewDate = this.dates.get(-1) || this.viewDate;
					this.setValue();
					this.fill();
					if (this.picker.is(':visible')){
						e.preventDefault();
						e.stopPropagation();
						if (this.o.autoclose)
							this.hide();
					}
					break;
				case 9: // tab
					this.focusDate = null;
					this.viewDate = this.dates.get(-1) || this.viewDate;
					this.fill();
					this.hide();
					break;
			}
			if (dateChanged){
				if (this.dates.length)
					this._trigger('changeDate');
				else
					this._trigger('clearDate');
				this.inputField.trigger('change');
			}
		},

		setViewMode: function(viewMode){
			this.viewMode = viewMode;
			this.picker
				.children('div')
				.hide()
				.filter('.datepicker-' + DPGlobal.viewModes[this.viewMode].clsName)
					.show();
			this.updateNavArrows();
      this._trigger('changeViewMode', new Date(this.viewDate));
		}
	};

	var DateRangePicker = function(element, options){
		$.data(element, 'datepicker', this);
		this.element = $(element);
		this.inputs = $.map(options.inputs, function(i){
			return i.jquery ? i[0] : i;
		});
		delete options.inputs;

		this.keepEmptyValues = options.keepEmptyValues;
		delete options.keepEmptyValues;

		datepickerPlugin.call($(this.inputs), options)
			.on('changeDate', $.proxy(this.dateUpdated, this));

		this.pickers = $.map(this.inputs, function(i){
			return $.data(i, 'datepicker');
		});
		this.updateDates();
	};
	DateRangePicker.prototype = {
		updateDates: function(){
			this.dates = $.map(this.pickers, function(i){
				return i.getUTCDate();
			});
			this.updateRanges();
		},
		updateRanges: function(){
			var range = $.map(this.dates, function(d){
				return d.valueOf();
			});
			$.each(this.pickers, function(i, p){
				p.setRange(range);
			});
		},
		clearDates: function(){
			$.each(this.pickers, function(i, p){
				p.clearDates();
			});
		},
		dateUpdated: function(e){
			// `this.updating` is a workaround for preventing infinite recursion
			// between `changeDate` triggering and `setUTCDate` calling.  Until
			// there is a better mechanism.
			if (this.updating)
				return;
			this.updating = true;

			var dp = $.data(e.target, 'datepicker');

			if (dp === undefined) {
				return;
			}

			var new_date = dp.getUTCDate(),
				keep_empty_values = this.keepEmptyValues,
				i = $.inArray(e.target, this.inputs),
				j = i - 1,
				k = i + 1,
				l = this.inputs.length;
			if (i === -1)
				return;

			$.each(this.pickers, function(i, p){
				if (!p.getUTCDate() && (p === dp || !keep_empty_values))
					p.setUTCDate(new_date);
			});

			if (new_date < this.dates[j]){
				// Date being moved earlier/left
				while (j >= 0 && new_date < this.dates[j] && (this.pickers[j].element.val() || "").length > 0) {
					this.pickers[j--].setUTCDate(new_date);
				}
			} else if (new_date > this.dates[k]){
				// Date being moved later/right
				while (k < l && new_date > this.dates[k] && (this.pickers[k].element.val() || "").length > 0) {
					this.pickers[k++].setUTCDate(new_date);
				}
			}
			this.updateDates();

			delete this.updating;
		},
		destroy: function(){
			$.map(this.pickers, function(p){ p.destroy(); });
			$(this.inputs).off('changeDate', this.dateUpdated);
			delete this.element.data().datepicker;
		},
		remove: alias('destroy', 'Method `remove` is deprecated and will be removed in version 2.0. Use `destroy` instead')
	};

	function opts_from_el(el, prefix){
		// Derive options from element data-attrs
		var data = $(el).data(),
			out = {}, inkey,
			replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])');
		prefix = new RegExp('^' + prefix.toLowerCase());
		function re_lower(_,a){
			return a.toLowerCase();
		}
		for (var key in data)
			if (prefix.test(key)){
				inkey = key.replace(replace, re_lower);
				out[inkey] = data[key];
			}
		return out;
	}

	function opts_from_locale(lang){
		// Derive options from locale plugins
		var out = {};
		// Check if "de-DE" style date is available, if not language should
		// fallback to 2 letter code eg "de"
		if (!dates[lang]){
			lang = lang.split('-')[0];
			if (!dates[lang])
				return;
		}
		var d = dates[lang];
		$.each(locale_opts, function(i,k){
			if (k in d)
				out[k] = d[k];
		});
		return out;
	}

	var old = $.fn.datepicker;
	var datepickerPlugin = function(option){
		var args = Array.apply(null, arguments);
		args.shift();
		var internal_return;
		this.each(function(){
			var $this = $(this),
				data = $this.data('datepicker'),
				options = typeof option === 'object' && option;
			if (!data){
				var elopts = opts_from_el(this, 'date'),
					// Preliminary otions
					xopts = $.extend({}, defaults, elopts, options),
					locopts = opts_from_locale(xopts.language),
					// Options priority: js args, data-attrs, locales, defaults
					opts = $.extend({}, defaults, locopts, elopts, options);
				if ($this.hasClass('input-daterange') || opts.inputs){
					$.extend(opts, {
						inputs: opts.inputs || $this.find('input').toArray()
					});
					data = new DateRangePicker(this, opts);
				}
				else {
					data = new Datepicker(this, opts);
				}
				$this.data('datepicker', data);
			}
			if (typeof option === 'string' && typeof data[option] === 'function'){
				internal_return = data[option].apply(data, args);
			}
		});

		if (
			internal_return === undefined ||
			internal_return instanceof Datepicker ||
			internal_return instanceof DateRangePicker
		)
			return this;

		if (this.length > 1)
			throw new Error('Using only allowed for the collection of a single element (' + option + ' function)');
		else
			return internal_return;
	};
	$.fn.datepicker = datepickerPlugin;

	var defaults = $.fn.datepicker.defaults = {
		assumeNearbyYear: false,
		autoclose: false,
		beforeShowDay: $.noop,
		beforeShowMonth: $.noop,
		beforeShowYear: $.noop,
		beforeShowDecade: $.noop,
		beforeShowCentury: $.noop,
		calendarWeeks: false,
		clearBtn: false,
		toggleActive: false,
		daysOfWeekDisabled: [],
		daysOfWeekHighlighted: [],
		datesDisabled: [],
		endDate: Infinity,
		forceParse: true,
		format: 'mm/dd/yyyy',
		isInline: null,
		keepEmptyValues: false,
		keyboardNavigation: true,
		language: 'en',
		minViewMode: 0,
		maxViewMode: 4,
		multidate: false,
		multidateSeparator: ',',
		orientation: "auto",
		rtl: false,
		startDate: -Infinity,
		startView: 0,
		todayBtn: false,
		todayHighlight: false,
		updateViewDate: true,
		weekStart: 0,
		disableTouchKeyboard: false,
		enableOnReadonly: true,
		showOnFocus: true,
		zIndexOffset: 10,
		container: 'body',
		immediateUpdates: false,
		title: '',
		templates: {
			leftArrow: '&#x00AB;',
			rightArrow: '&#x00BB;'
		},
    showWeekDays: true
	};
	var locale_opts = $.fn.datepicker.locale_opts = [
		'format',
		'rtl',
		'weekStart'
	];
	$.fn.datepicker.Constructor = Datepicker;
	var dates = $.fn.datepicker.dates = {
		en: {
			days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
			daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
			daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"],
			months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
			monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
			today: "Today",
			clear: "Clear",
			titleFormat: "MM yyyy"
		}
	};

	var DPGlobal = {
		viewModes: [
			{
				names: ['days', 'month'],
				clsName: 'days',
				e: 'changeMonth'
			},
			{
				names: ['months', 'year'],
				clsName: 'months',
				e: 'changeYear',
				navStep: 1
			},
			{
				names: ['years', 'decade'],
				clsName: 'years',
				e: 'changeDecade',
				navStep: 10
			},
			{
				names: ['decades', 'century'],
				clsName: 'decades',
				e: 'changeCentury',
				navStep: 100
			},
			{
				names: ['centuries', 'millennium'],
				clsName: 'centuries',
				e: 'changeMillennium',
				navStep: 1000
			}
		],
		validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,
		nonpunctuation: /[^ -\/:-@\u5e74\u6708\u65e5\[-`{-~\t\n\r]+/g,
		parseFormat: function(format){
			if (typeof format.toValue === 'function' && typeof format.toDisplay === 'function')
                return format;
            // IE treats \0 as a string end in inputs (truncating the value),
			// so it's a bad format delimiter, anyway
			var separators = format.replace(this.validParts, '\0').split('\0'),
				parts = format.match(this.validParts);
			if (!separators || !separators.length || !parts || parts.length === 0){
				throw new Error("Invalid date format.");
			}
			return {separators: separators, parts: parts};
		},
		parseDate: function(date, format, language, assumeNearby){
			if (!date)
				return undefined;
			if (date instanceof Date)
				return date;
			if (typeof format === 'string')
				format = DPGlobal.parseFormat(format);
			if (format.toValue)
				return format.toValue(date, format, language);
			var fn_map = {
					d: 'moveDay',
					m: 'moveMonth',
					w: 'moveWeek',
					y: 'moveYear'
				},
				dateAliases = {
					yesterday: '-1d',
					today: '+0d',
					tomorrow: '+1d'
				},
				parts, part, dir, i, fn;
			if (date in dateAliases){
				date = dateAliases[date];
			}
			if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/i.test(date)){
				parts = date.match(/([\-+]\d+)([dmwy])/gi);
				date = new Date();
				for (i=0; i < parts.length; i++){
					part = parts[i].match(/([\-+]\d+)([dmwy])/i);
					dir = Number(part[1]);
					fn = fn_map[part[2].toLowerCase()];
					date = Datepicker.prototype[fn](date, dir);
				}
				return Datepicker.prototype._zero_utc_time(date);
			}

			parts = date && date.match(this.nonpunctuation) || [];

			function applyNearbyYear(year, threshold){
				if (threshold === true)
					threshold = 10;

				// if year is 2 digits or less, than the user most likely is trying to get a recent century
				if (year < 100){
					year += 2000;
					// if the new year is more than threshold years in advance, use last century
					if (year > ((new Date()).getFullYear()+threshold)){
						year -= 100;
					}
				}

				return year;
			}

			var parsed = {},
				setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
				setters_map = {
					yyyy: function(d,v){
						return d.setUTCFullYear(assumeNearby ? applyNearbyYear(v, assumeNearby) : v);
					},
					m: function(d,v){
						if (isNaN(d))
							return d;
						v -= 1;
						while (v < 0) v += 12;
						v %= 12;
						d.setUTCMonth(v);
						while (d.getUTCMonth() !== v)
							d.setUTCDate(d.getUTCDate()-1);
						return d;
					},
					d: function(d,v){
						return d.setUTCDate(v);
					}
				},
				val, filtered;
			setters_map['yy'] = setters_map['yyyy'];
			setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
			setters_map['dd'] = setters_map['d'];
			date = UTCToday();
			var fparts = format.parts.slice();
			// Remove noop parts
			if (parts.length !== fparts.length){
				fparts = $(fparts).filter(function(i,p){
					return $.inArray(p, setters_order) !== -1;
				}).toArray();
			}
			// Process remainder
			function match_part(){
				var m = this.slice(0, parts[i].length),
					p = parts[i].slice(0, m.length);
				return m.toLowerCase() === p.toLowerCase();
			}
			if (parts.length === fparts.length){
				var cnt;
				for (i=0, cnt = fparts.length; i < cnt; i++){
					val = parseInt(parts[i], 10);
					part = fparts[i];
					if (isNaN(val)){
						switch (part){
							case 'MM':
								filtered = $(dates[language].months).filter(match_part);
								val = $.inArray(filtered[0], dates[language].months) + 1;
								break;
							case 'M':
								filtered = $(dates[language].monthsShort).filter(match_part);
								val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
								break;
						}
					}
					parsed[part] = val;
				}
				var _date, s;
				for (i=0; i < setters_order.length; i++){
					s = setters_order[i];
					if (s in parsed && !isNaN(parsed[s])){
						_date = new Date(date);
						setters_map[s](_date, parsed[s]);
						if (!isNaN(_date))
							date = _date;
					}
				}
			}
			return date;
		},
		formatDate: function(date, format, language){
			if (!date)
				return '';
			if (typeof format === 'string')
				format = DPGlobal.parseFormat(format);
			if (format.toDisplay)
                return format.toDisplay(date, format, language);
            var val = {
				d: date.getUTCDate(),
				D: dates[language].daysShort[date.getUTCDay()],
				DD: dates[language].days[date.getUTCDay()],
				m: date.getUTCMonth() + 1,
				M: dates[language].monthsShort[date.getUTCMonth()],
				MM: dates[language].months[date.getUTCMonth()],
				yy: date.getUTCFullYear().toString().substring(2),
				yyyy: date.getUTCFullYear()
			};
			val.dd = (val.d < 10 ? '0' : '') + val.d;
			val.mm = (val.m < 10 ? '0' : '') + val.m;
			date = [];
			var seps = $.extend([], format.separators);
			for (var i=0, cnt = format.parts.length; i <= cnt; i++){
				if (seps.length)
					date.push(seps.shift());
				date.push(val[format.parts[i]]);
			}
			return date.join('');
		},
		headTemplate: '<thead>'+
			              '<tr>'+
			                '<th colspan="7" class="datepicker-title"></th>'+
			              '</tr>'+
							'<tr>'+
								'<th class="prev">'+defaults.templates.leftArrow+'</th>'+
								'<th colspan="5" class="datepicker-switch"></th>'+
								'<th class="next">'+defaults.templates.rightArrow+'</th>'+
							'</tr>'+
						'</thead>',
		contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
		footTemplate: '<tfoot>'+
							'<tr>'+
								'<th colspan="7" class="today"></th>'+
							'</tr>'+
							'<tr>'+
								'<th colspan="7" class="clear"></th>'+
							'</tr>'+
						'</tfoot>'
	};
	DPGlobal.template = '<div class="datepicker">'+
							'<div class="datepicker-days">'+
								'<table class="table-condensed">'+
									DPGlobal.headTemplate+
									'<tbody></tbody>'+
									DPGlobal.footTemplate+
								'</table>'+
							'</div>'+
							'<div class="datepicker-months">'+
								'<table class="table-condensed">'+
									DPGlobal.headTemplate+
									DPGlobal.contTemplate+
									DPGlobal.footTemplate+
								'</table>'+
							'</div>'+
							'<div class="datepicker-years">'+
								'<table class="table-condensed">'+
									DPGlobal.headTemplate+
									DPGlobal.contTemplate+
									DPGlobal.footTemplate+
								'</table>'+
							'</div>'+
							'<div class="datepicker-decades">'+
								'<table class="table-condensed">'+
									DPGlobal.headTemplate+
									DPGlobal.contTemplate+
									DPGlobal.footTemplate+
								'</table>'+
							'</div>'+
							'<div class="datepicker-centuries">'+
								'<table class="table-condensed">'+
									DPGlobal.headTemplate+
									DPGlobal.contTemplate+
									DPGlobal.footTemplate+
								'</table>'+
							'</div>'+
						'</div>';

	$.fn.datepicker.DPGlobal = DPGlobal;


	/* DATEPICKER NO CONFLICT
	* =================== */

	$.fn.datepicker.noConflict = function(){
		$.fn.datepicker = old;
		return this;
	};

	/* DATEPICKER VERSION
	 * =================== */
	$.fn.datepicker.version = '1.10.0';

	$.fn.datepicker.deprecated = function(msg){
		var console = window.console;
		if (console && console.warn) {
			console.warn('DEPRECATED: ' + msg);
		}
	};


	/* DATEPICKER DATA-API
	* ================== */

	$(document).on(
		'focus.datepicker.data-api click.datepicker.data-api',
		'[data-provide="datepicker"]',
		function(e){
			var $this = $(this);
			if ($this.data('datepicker'))
				return;
			e.preventDefault();
			// component click requires us to explicitly show it
			datepickerPlugin.call($this, 'show');
		}
	);
	$(function(){
		datepickerPlugin.call($('[data-provide="datepicker-inline"]'));
	});

}));
/**
 * Spanish translation for bootstrap-datepicker
 * Bruno Bonamin <bruno.bonamin@gmail.com>
 */

;(function($){
	$.fn.datepicker.dates['es'] = {
		days: ["Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"],
		daysShort: ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb"],
		daysMin: ["Do", "Lu", "Ma", "Mi", "Ju", "Vi", "Sa"],
		months: ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"],
		monthsShort: ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"],
		today: "Hoy",
		monthsTitle: "Meses",
		clear: "Borrar",
		weekStart: 1,
		format: "dd/mm/yyyy"
	};
}(jQuery));
//! moment.js
//! version : 2.29.4
//! authors : Tim Wood, Iskren Chernev, Moment.js contributors
//! license : MIT
//! momentjs.com

;(function (global, factory) {
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    global.moment = factory()
}(this, (function () { 'use strict';

    var hookCallback;

    function hooks() {
        return hookCallback.apply(null, arguments);
    }

    // This is done to register the method called with moment()
    // without creating circular dependencies.
    function setHookCallback(callback) {
        hookCallback = callback;
    }

    function isArray(input) {
        return (
            input instanceof Array ||
            Object.prototype.toString.call(input) === '[object Array]'
        );
    }

    function isObject(input) {
        // IE8 will treat undefined and null as object if it wasn't for
        // input != null
        return (
            input != null &&
            Object.prototype.toString.call(input) === '[object Object]'
        );
    }

    function hasOwnProp(a, b) {
        return Object.prototype.hasOwnProperty.call(a, b);
    }

    function isObjectEmpty(obj) {
        if (Object.getOwnPropertyNames) {
            return Object.getOwnPropertyNames(obj).length === 0;
        } else {
            var k;
            for (k in obj) {
                if (hasOwnProp(obj, k)) {
                    return false;
                }
            }
            return true;
        }
    }

    function isUndefined(input) {
        return input === void 0;
    }

    function isNumber(input) {
        return (
            typeof input === 'number' ||
            Object.prototype.toString.call(input) === '[object Number]'
        );
    }

    function isDate(input) {
        return (
            input instanceof Date ||
            Object.prototype.toString.call(input) === '[object Date]'
        );
    }

    function map(arr, fn) {
        var res = [],
            i,
            arrLen = arr.length;
        for (i = 0; i < arrLen; ++i) {
            res.push(fn(arr[i], i));
        }
        return res;
    }

    function extend(a, b) {
        for (var i in b) {
            if (hasOwnProp(b, i)) {
                a[i] = b[i];
            }
        }

        if (hasOwnProp(b, 'toString')) {
            a.toString = b.toString;
        }

        if (hasOwnProp(b, 'valueOf')) {
            a.valueOf = b.valueOf;
        }

        return a;
    }

    function createUTC(input, format, locale, strict) {
        return createLocalOrUTC(input, format, locale, strict, true).utc();
    }

    function defaultParsingFlags() {
        // We need to deep clone this object.
        return {
            empty: false,
            unusedTokens: [],
            unusedInput: [],
            overflow: -2,
            charsLeftOver: 0,
            nullInput: false,
            invalidEra: null,
            invalidMonth: null,
            invalidFormat: false,
            userInvalidated: false,
            iso: false,
            parsedDateParts: [],
            era: null,
            meridiem: null,
            rfc2822: false,
            weekdayMismatch: false,
        };
    }

    function getParsingFlags(m) {
        if (m._pf == null) {
            m._pf = defaultParsingFlags();
        }
        return m._pf;
    }

    var some;
    if (Array.prototype.some) {
        some = Array.prototype.some;
    } else {
        some = function (fun) {
            var t = Object(this),
                len = t.length >>> 0,
                i;

            for (i = 0; i < len; i++) {
                if (i in t && fun.call(this, t[i], i, t)) {
                    return true;
                }
            }

            return false;
        };
    }

    function isValid(m) {
        if (m._isValid == null) {
            var flags = getParsingFlags(m),
                parsedParts = some.call(flags.parsedDateParts, function (i) {
                    return i != null;
                }),
                isNowValid =
                    !isNaN(m._d.getTime()) &&
                    flags.overflow < 0 &&
                    !flags.empty &&
                    !flags.invalidEra &&
                    !flags.invalidMonth &&
                    !flags.invalidWeekday &&
                    !flags.weekdayMismatch &&
                    !flags.nullInput &&
                    !flags.invalidFormat &&
                    !flags.userInvalidated &&
                    (!flags.meridiem || (flags.meridiem && parsedParts));

            if (m._strict) {
                isNowValid =
                    isNowValid &&
                    flags.charsLeftOver === 0 &&
                    flags.unusedTokens.length === 0 &&
                    flags.bigHour === undefined;
            }

            if (Object.isFrozen == null || !Object.isFrozen(m)) {
                m._isValid = isNowValid;
            } else {
                return isNowValid;
            }
        }
        return m._isValid;
    }

    function createInvalid(flags) {
        var m = createUTC(NaN);
        if (flags != null) {
            extend(getParsingFlags(m), flags);
        } else {
            getParsingFlags(m).userInvalidated = true;
        }

        return m;
    }

    // Plugins that add properties should also add the key here (null value),
    // so we can properly clone ourselves.
    var momentProperties = (hooks.momentProperties = []),
        updateInProgress = false;

    function copyConfig(to, from) {
        var i,
            prop,
            val,
            momentPropertiesLen = momentProperties.length;

        if (!isUndefined(from._isAMomentObject)) {
            to._isAMomentObject = from._isAMomentObject;
        }
        if (!isUndefined(from._i)) {
            to._i = from._i;
        }
        if (!isUndefined(from._f)) {
            to._f = from._f;
        }
        if (!isUndefined(from._l)) {
            to._l = from._l;
        }
        if (!isUndefined(from._strict)) {
            to._strict = from._strict;
        }
        if (!isUndefined(from._tzm)) {
            to._tzm = from._tzm;
        }
        if (!isUndefined(from._isUTC)) {
            to._isUTC = from._isUTC;
        }
        if (!isUndefined(from._offset)) {
            to._offset = from._offset;
        }
        if (!isUndefined(from._pf)) {
            to._pf = getParsingFlags(from);
        }
        if (!isUndefined(from._locale)) {
            to._locale = from._locale;
        }

        if (momentPropertiesLen > 0) {
            for (i = 0; i < momentPropertiesLen; i++) {
                prop = momentProperties[i];
                val = from[prop];
                if (!isUndefined(val)) {
                    to[prop] = val;
                }
            }
        }

        return to;
    }

    // Moment prototype object
    function Moment(config) {
        copyConfig(this, config);
        this._d = new Date(config._d != null ? config._d.getTime() : NaN);
        if (!this.isValid()) {
            this._d = new Date(NaN);
        }
        // Prevent infinite loop in case updateOffset creates new moment
        // objects.
        if (updateInProgress === false) {
            updateInProgress = true;
            hooks.updateOffset(this);
            updateInProgress = false;
        }
    }

    function isMoment(obj) {
        return (
            obj instanceof Moment || (obj != null && obj._isAMomentObject != null)
        );
    }

    function warn(msg) {
        if (
            hooks.suppressDeprecationWarnings === false &&
            typeof console !== 'undefined' &&
            console.warn
        ) {
            console.warn('Deprecation warning: ' + msg);
        }
    }

    function deprecate(msg, fn) {
        var firstTime = true;

        return extend(function () {
            if (hooks.deprecationHandler != null) {
                hooks.deprecationHandler(null, msg);
            }
            if (firstTime) {
                var args = [],
                    arg,
                    i,
                    key,
                    argLen = arguments.length;
                for (i = 0; i < argLen; i++) {
                    arg = '';
                    if (typeof arguments[i] === 'object') {
                        arg += '\n[' + i + '] ';
                        for (key in arguments[0]) {
                            if (hasOwnProp(arguments[0], key)) {
                                arg += key + ': ' + arguments[0][key] + ', ';
                            }
                        }
                        arg = arg.slice(0, -2); // Remove trailing comma and space
                    } else {
                        arg = arguments[i];
                    }
                    args.push(arg);
                }
                warn(
                    msg +
                        '\nArguments: ' +
                        Array.prototype.slice.call(args).join('') +
                        '\n' +
                        new Error().stack
                );
                firstTime = false;
            }
            return fn.apply(this, arguments);
        }, fn);
    }

    var deprecations = {};

    function deprecateSimple(name, msg) {
        if (hooks.deprecationHandler != null) {
            hooks.deprecationHandler(name, msg);
        }
        if (!deprecations[name]) {
            warn(msg);
            deprecations[name] = true;
        }
    }

    hooks.suppressDeprecationWarnings = false;
    hooks.deprecationHandler = null;

    function isFunction(input) {
        return (
            (typeof Function !== 'undefined' && input instanceof Function) ||
            Object.prototype.toString.call(input) === '[object Function]'
        );
    }

    function set(config) {
        var prop, i;
        for (i in config) {
            if (hasOwnProp(config, i)) {
                prop = config[i];
                if (isFunction(prop)) {
                    this[i] = prop;
                } else {
                    this['_' + i] = prop;
                }
            }
        }
        this._config = config;
        // Lenient ordinal parsing accepts just a number in addition to
        // number + (possibly) stuff coming from _dayOfMonthOrdinalParse.
        // TODO: Remove "ordinalParse" fallback in next major release.
        this._dayOfMonthOrdinalParseLenient = new RegExp(
            (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) +
                '|' +
                /\d{1,2}/.source
        );
    }

    function mergeConfigs(parentConfig, childConfig) {
        var res = extend({}, parentConfig),
            prop;
        for (prop in childConfig) {
            if (hasOwnProp(childConfig, prop)) {
                if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) {
                    res[prop] = {};
                    extend(res[prop], parentConfig[prop]);
                    extend(res[prop], childConfig[prop]);
                } else if (childConfig[prop] != null) {
                    res[prop] = childConfig[prop];
                } else {
                    delete res[prop];
                }
            }
        }
        for (prop in parentConfig) {
            if (
                hasOwnProp(parentConfig, prop) &&
                !hasOwnProp(childConfig, prop) &&
                isObject(parentConfig[prop])
            ) {
                // make sure changes to properties don't modify parent config
                res[prop] = extend({}, res[prop]);
            }
        }
        return res;
    }

    function Locale(config) {
        if (config != null) {
            this.set(config);
        }
    }

    var keys;

    if (Object.keys) {
        keys = Object.keys;
    } else {
        keys = function (obj) {
            var i,
                res = [];
            for (i in obj) {
                if (hasOwnProp(obj, i)) {
                    res.push(i);
                }
            }
            return res;
        };
    }

    var defaultCalendar = {
        sameDay: '[Today at] LT',
        nextDay: '[Tomorrow at] LT',
        nextWeek: 'dddd [at] LT',
        lastDay: '[Yesterday at] LT',
        lastWeek: '[Last] dddd [at] LT',
        sameElse: 'L',
    };

    function calendar(key, mom, now) {
        var output = this._calendar[key] || this._calendar['sameElse'];
        return isFunction(output) ? output.call(mom, now) : output;
    }

    function zeroFill(number, targetLength, forceSign) {
        var absNumber = '' + Math.abs(number),
            zerosToFill = targetLength - absNumber.length,
            sign = number >= 0;
        return (
            (sign ? (forceSign ? '+' : '') : '-') +
            Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) +
            absNumber
        );
    }

    var formattingTokens =
            /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,
        localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,
        formatFunctions = {},
        formatTokenFunctions = {};

    // token:    'M'
    // padded:   ['MM', 2]
    // ordinal:  'Mo'
    // callback: function () { this.month() + 1 }
    function addFormatToken(token, padded, ordinal, callback) {
        var func = callback;
        if (typeof callback === 'string') {
            func = function () {
                return this[callback]();
            };
        }
        if (token) {
            formatTokenFunctions[token] = func;
        }
        if (padded) {
            formatTokenFunctions[padded[0]] = function () {
                return zeroFill(func.apply(this, arguments), padded[1], padded[2]);
            };
        }
        if (ordinal) {
            formatTokenFunctions[ordinal] = function () {
                return this.localeData().ordinal(
                    func.apply(this, arguments),
                    token
                );
            };
        }
    }

    function removeFormattingTokens(input) {
        if (input.match(/\[[\s\S]/)) {
            return input.replace(/^\[|\]$/g, '');
        }
        return input.replace(/\\/g, '');
    }

    function makeFormatFunction(format) {
        var array = format.match(formattingTokens),
            i,
            length;

        for (i = 0, length = array.length; i < length; i++) {
            if (formatTokenFunctions[array[i]]) {
                array[i] = formatTokenFunctions[array[i]];
            } else {
                array[i] = removeFormattingTokens(array[i]);
            }
        }

        return function (mom) {
            var output = '',
                i;
            for (i = 0; i < length; i++) {
                output += isFunction(array[i])
                    ? array[i].call(mom, format)
                    : array[i];
            }
            return output;
        };
    }

    // format date using native date object
    function formatMoment(m, format) {
        if (!m.isValid()) {
            return m.localeData().invalidDate();
        }

        format = expandFormat(format, m.localeData());
        formatFunctions[format] =
            formatFunctions[format] || makeFormatFunction(format);

        return formatFunctions[format](m);
    }

    function expandFormat(format, locale) {
        var i = 5;

        function replaceLongDateFormatTokens(input) {
            return locale.longDateFormat(input) || input;
        }

        localFormattingTokens.lastIndex = 0;
        while (i >= 0 && localFormattingTokens.test(format)) {
            format = format.replace(
                localFormattingTokens,
                replaceLongDateFormatTokens
            );
            localFormattingTokens.lastIndex = 0;
            i -= 1;
        }

        return format;
    }

    var defaultLongDateFormat = {
        LTS: 'h:mm:ss A',
        LT: 'h:mm A',
        L: 'MM/DD/YYYY',
        LL: 'MMMM D, YYYY',
        LLL: 'MMMM D, YYYY h:mm A',
        LLLL: 'dddd, MMMM D, YYYY h:mm A',
    };

    function longDateFormat(key) {
        var format = this._longDateFormat[key],
            formatUpper = this._longDateFormat[key.toUpperCase()];

        if (format || !formatUpper) {
            return format;
        }

        this._longDateFormat[key] = formatUpper
            .match(formattingTokens)
            .map(function (tok) {
                if (
                    tok === 'MMMM' ||
                    tok === 'MM' ||
                    tok === 'DD' ||
                    tok === 'dddd'
                ) {
                    return tok.slice(1);
                }
                return tok;
            })
            .join('');

        return this._longDateFormat[key];
    }

    var defaultInvalidDate = 'Invalid date';

    function invalidDate() {
        return this._invalidDate;
    }

    var defaultOrdinal = '%d',
        defaultDayOfMonthOrdinalParse = /\d{1,2}/;

    function ordinal(number) {
        return this._ordinal.replace('%d', number);
    }

    var defaultRelativeTime = {
        future: 'in %s',
        past: '%s ago',
        s: 'a few seconds',
        ss: '%d seconds',
        m: 'a minute',
        mm: '%d minutes',
        h: 'an hour',
        hh: '%d hours',
        d: 'a day',
        dd: '%d days',
        w: 'a week',
        ww: '%d weeks',
        M: 'a month',
        MM: '%d months',
        y: 'a year',
        yy: '%d years',
    };

    function relativeTime(number, withoutSuffix, string, isFuture) {
        var output = this._relativeTime[string];
        return isFunction(output)
            ? output(number, withoutSuffix, string, isFuture)
            : output.replace(/%d/i, number);
    }

    function pastFuture(diff, output) {
        var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
        return isFunction(format) ? format(output) : format.replace(/%s/i, output);
    }

    var aliases = {};

    function addUnitAlias(unit, shorthand) {
        var lowerCase = unit.toLowerCase();
        aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit;
    }

    function normalizeUnits(units) {
        return typeof units === 'string'
            ? aliases[units] || aliases[units.toLowerCase()]
            : undefined;
    }

    function normalizeObjectUnits(inputObject) {
        var normalizedInput = {},
            normalizedProp,
            prop;

        for (prop in inputObject) {
            if (hasOwnProp(inputObject, prop)) {
                normalizedProp = normalizeUnits(prop);
                if (normalizedProp) {
                    normalizedInput[normalizedProp] = inputObject[prop];
                }
            }
        }

        return normalizedInput;
    }

    var priorities = {};

    function addUnitPriority(unit, priority) {
        priorities[unit] = priority;
    }

    function getPrioritizedUnits(unitsObj) {
        var units = [],
            u;
        for (u in unitsObj) {
            if (hasOwnProp(unitsObj, u)) {
                units.push({ unit: u, priority: priorities[u] });
            }
        }
        units.sort(function (a, b) {
            return a.priority - b.priority;
        });
        return units;
    }

    function isLeapYear(year) {
        return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
    }

    function absFloor(number) {
        if (number < 0) {
            // -0 -> 0
            return Math.ceil(number) || 0;
        } else {
            return Math.floor(number);
        }
    }

    function toInt(argumentForCoercion) {
        var coercedNumber = +argumentForCoercion,
            value = 0;

        if (coercedNumber !== 0 && isFinite(coercedNumber)) {
            value = absFloor(coercedNumber);
        }

        return value;
    }

    function makeGetSet(unit, keepTime) {
        return function (value) {
            if (value != null) {
                set$1(this, unit, value);
                hooks.updateOffset(this, keepTime);
                return this;
            } else {
                return get(this, unit);
            }
        };
    }

    function get(mom, unit) {
        return mom.isValid()
            ? mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]()
            : NaN;
    }

    function set$1(mom, unit, value) {
        if (mom.isValid() && !isNaN(value)) {
            if (
                unit === 'FullYear' &&
                isLeapYear(mom.year()) &&
                mom.month() === 1 &&
                mom.date() === 29
            ) {
                value = toInt(value);
                mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](
                    value,
                    mom.month(),
                    daysInMonth(value, mom.month())
                );
            } else {
                mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
            }
        }
    }

    // MOMENTS

    function stringGet(units) {
        units = normalizeUnits(units);
        if (isFunction(this[units])) {
            return this[units]();
        }
        return this;
    }

    function stringSet(units, value) {
        if (typeof units === 'object') {
            units = normalizeObjectUnits(units);
            var prioritized = getPrioritizedUnits(units),
                i,
                prioritizedLen = prioritized.length;
            for (i = 0; i < prioritizedLen; i++) {
                this[prioritized[i].unit](units[prioritized[i].unit]);
            }
        } else {
            units = normalizeUnits(units);
            if (isFunction(this[units])) {
                return this[units](value);
            }
        }
        return this;
    }

    var match1 = /\d/, //       0 - 9
        match2 = /\d\d/, //      00 - 99
        match3 = /\d{3}/, //     000 - 999
        match4 = /\d{4}/, //    0000 - 9999
        match6 = /[+-]?\d{6}/, // -999999 - 999999
        match1to2 = /\d\d?/, //       0 - 99
        match3to4 = /\d\d\d\d?/, //     999 - 9999
        match5to6 = /\d\d\d\d\d\d?/, //   99999 - 999999
        match1to3 = /\d{1,3}/, //       0 - 999
        match1to4 = /\d{1,4}/, //       0 - 9999
        match1to6 = /[+-]?\d{1,6}/, // -999999 - 999999
        matchUnsigned = /\d+/, //       0 - inf
        matchSigned = /[+-]?\d+/, //    -inf - inf
        matchOffset = /Z|[+-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
        matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi, // +00 -00 +00:00 -00:00 +0000 -0000 or Z
        matchTimestamp = /[+-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
        // any word (or two) characters or numbers including two/three word month in arabic.
        // includes scottish gaelic two word and hyphenated months
        matchWord =
            /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i,
        regexes;

    regexes = {};

    function addRegexToken(token, regex, strictRegex) {
        regexes[token] = isFunction(regex)
            ? regex
            : function (isStrict, localeData) {
                  return isStrict && strictRegex ? strictRegex : regex;
              };
    }

    function getParseRegexForToken(token, config) {
        if (!hasOwnProp(regexes, token)) {
            return new RegExp(unescapeFormat(token));
        }

        return regexes[token](config._strict, config._locale);
    }

    // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
    function unescapeFormat(s) {
        return regexEscape(
            s
                .replace('\\', '')
                .replace(
                    /\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,
                    function (matched, p1, p2, p3, p4) {
                        return p1 || p2 || p3 || p4;
                    }
                )
        );
    }

    function regexEscape(s) {
        return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
    }

    var tokens = {};

    function addParseToken(token, callback) {
        var i,
            func = callback,
            tokenLen;
        if (typeof token === 'string') {
            token = [token];
        }
        if (isNumber(callback)) {
            func = function (input, array) {
                array[callback] = toInt(input);
            };
        }
        tokenLen = token.length;
        for (i = 0; i < tokenLen; i++) {
            tokens[token[i]] = func;
        }
    }

    function addWeekParseToken(token, callback) {
        addParseToken(token, function (input, array, config, token) {
            config._w = config._w || {};
            callback(input, config._w, config, token);
        });
    }

    function addTimeToArrayFromToken(token, input, config) {
        if (input != null && hasOwnProp(tokens, token)) {
            tokens[token](input, config._a, config, token);
        }
    }

    var YEAR = 0,
        MONTH = 1,
        DATE = 2,
        HOUR = 3,
        MINUTE = 4,
        SECOND = 5,
        MILLISECOND = 6,
        WEEK = 7,
        WEEKDAY = 8;

    function mod(n, x) {
        return ((n % x) + x) % x;
    }

    var indexOf;

    if (Array.prototype.indexOf) {
        indexOf = Array.prototype.indexOf;
    } else {
        indexOf = function (o) {
            // I know
            var i;
            for (i = 0; i < this.length; ++i) {
                if (this[i] === o) {
                    return i;
                }
            }
            return -1;
        };
    }

    function daysInMonth(year, month) {
        if (isNaN(year) || isNaN(month)) {
            return NaN;
        }
        var modMonth = mod(month, 12);
        year += (month - modMonth) / 12;
        return modMonth === 1
            ? isLeapYear(year)
                ? 29
                : 28
            : 31 - ((modMonth % 7) % 2);
    }

    // FORMATTING

    addFormatToken('M', ['MM', 2], 'Mo', function () {
        return this.month() + 1;
    });

    addFormatToken('MMM', 0, 0, function (format) {
        return this.localeData().monthsShort(this, format);
    });

    addFormatToken('MMMM', 0, 0, function (format) {
        return this.localeData().months(this, format);
    });

    // ALIASES

    addUnitAlias('month', 'M');

    // PRIORITY

    addUnitPriority('month', 8);

    // PARSING

    addRegexToken('M', match1to2);
    addRegexToken('MM', match1to2, match2);
    addRegexToken('MMM', function (isStrict, locale) {
        return locale.monthsShortRegex(isStrict);
    });
    addRegexToken('MMMM', function (isStrict, locale) {
        return locale.monthsRegex(isStrict);
    });

    addParseToken(['M', 'MM'], function (input, array) {
        array[MONTH] = toInt(input) - 1;
    });

    addParseToken(['MMM', 'MMMM'], function (input, array, config, token) {
        var month = config._locale.monthsParse(input, token, config._strict);
        // if we didn't find a month name, mark the date as invalid.
        if (month != null) {
            array[MONTH] = month;
        } else {
            getParsingFlags(config).invalidMonth = input;
        }
    });

    // LOCALES

    var defaultLocaleMonths =
            'January_February_March_April_May_June_July_August_September_October_November_December'.split(
                '_'
            ),
        defaultLocaleMonthsShort =
            'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
        MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/,
        defaultMonthsShortRegex = matchWord,
        defaultMonthsRegex = matchWord;

    function localeMonths(m, format) {
        if (!m) {
            return isArray(this._months)
                ? this._months
                : this._months['standalone'];
        }
        return isArray(this._months)
            ? this._months[m.month()]
            : this._months[
                  (this._months.isFormat || MONTHS_IN_FORMAT).test(format)
                      ? 'format'
                      : 'standalone'
              ][m.month()];
    }

    function localeMonthsShort(m, format) {
        if (!m) {
            return isArray(this._monthsShort)
                ? this._monthsShort
                : this._monthsShort['standalone'];
        }
        return isArray(this._monthsShort)
            ? this._monthsShort[m.month()]
            : this._monthsShort[
                  MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'
              ][m.month()];
    }

    function handleStrictParse(monthName, format, strict) {
        var i,
            ii,
            mom,
            llc = monthName.toLocaleLowerCase();
        if (!this._monthsParse) {
            // this is not used
            this._monthsParse = [];
            this._longMonthsParse = [];
            this._shortMonthsParse = [];
            for (i = 0; i < 12; ++i) {
                mom = createUTC([2000, i]);
                this._shortMonthsParse[i] = this.monthsShort(
                    mom,
                    ''
                ).toLocaleLowerCase();
                this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase();
            }
        }

        if (strict) {
            if (format === 'MMM') {
                ii = indexOf.call(this._shortMonthsParse, llc);
                return ii !== -1 ? ii : null;
            } else {
                ii = indexOf.call(this._longMonthsParse, llc);
                return ii !== -1 ? ii : null;
            }
        } else {
            if (format === 'MMM') {
                ii = indexOf.call(this._shortMonthsParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._longMonthsParse, llc);
                return ii !== -1 ? ii : null;
            } else {
                ii = indexOf.call(this._longMonthsParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._shortMonthsParse, llc);
                return ii !== -1 ? ii : null;
            }
        }
    }

    function localeMonthsParse(monthName, format, strict) {
        var i, mom, regex;

        if (this._monthsParseExact) {
            return handleStrictParse.call(this, monthName, format, strict);
        }

        if (!this._monthsParse) {
            this._monthsParse = [];
            this._longMonthsParse = [];
            this._shortMonthsParse = [];
        }

        // TODO: add sorting
        // Sorting makes sure if one month (or abbr) is a prefix of another
        // see sorting in computeMonthsParse
        for (i = 0; i < 12; i++) {
            // make the regex if we don't have it already
            mom = createUTC([2000, i]);
            if (strict && !this._longMonthsParse[i]) {
                this._longMonthsParse[i] = new RegExp(
                    '^' + this.months(mom, '').replace('.', '') + '$',
                    'i'
                );
                this._shortMonthsParse[i] = new RegExp(
                    '^' + this.monthsShort(mom, '').replace('.', '') + '$',
                    'i'
                );
            }
            if (!strict && !this._monthsParse[i]) {
                regex =
                    '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
                this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
            }
            // test the regex
            if (
                strict &&
                format === 'MMMM' &&
                this._longMonthsParse[i].test(monthName)
            ) {
                return i;
            } else if (
                strict &&
                format === 'MMM' &&
                this._shortMonthsParse[i].test(monthName)
            ) {
                return i;
            } else if (!strict && this._monthsParse[i].test(monthName)) {
                return i;
            }
        }
    }

    // MOMENTS

    function setMonth(mom, value) {
        var dayOfMonth;

        if (!mom.isValid()) {
            // No op
            return mom;
        }

        if (typeof value === 'string') {
            if (/^\d+$/.test(value)) {
                value = toInt(value);
            } else {
                value = mom.localeData().monthsParse(value);
                // TODO: Another silent failure?
                if (!isNumber(value)) {
                    return mom;
                }
            }
        }

        dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value));
        mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
        return mom;
    }

    function getSetMonth(value) {
        if (value != null) {
            setMonth(this, value);
            hooks.updateOffset(this, true);
            return this;
        } else {
            return get(this, 'Month');
        }
    }

    function getDaysInMonth() {
        return daysInMonth(this.year(), this.month());
    }

    function monthsShortRegex(isStrict) {
        if (this._monthsParseExact) {
            if (!hasOwnProp(this, '_monthsRegex')) {
                computeMonthsParse.call(this);
            }
            if (isStrict) {
                return this._monthsShortStrictRegex;
            } else {
                return this._monthsShortRegex;
            }
        } else {
            if (!hasOwnProp(this, '_monthsShortRegex')) {
                this._monthsShortRegex = defaultMonthsShortRegex;
            }
            return this._monthsShortStrictRegex && isStrict
                ? this._monthsShortStrictRegex
                : this._monthsShortRegex;
        }
    }

    function monthsRegex(isStrict) {
        if (this._monthsParseExact) {
            if (!hasOwnProp(this, '_monthsRegex')) {
                computeMonthsParse.call(this);
            }
            if (isStrict) {
                return this._monthsStrictRegex;
            } else {
                return this._monthsRegex;
            }
        } else {
            if (!hasOwnProp(this, '_monthsRegex')) {
                this._monthsRegex = defaultMonthsRegex;
            }
            return this._monthsStrictRegex && isStrict
                ? this._monthsStrictRegex
                : this._monthsRegex;
        }
    }

    function computeMonthsParse() {
        function cmpLenRev(a, b) {
            return b.length - a.length;
        }

        var shortPieces = [],
            longPieces = [],
            mixedPieces = [],
            i,
            mom;
        for (i = 0; i < 12; i++) {
            // make the regex if we don't have it already
            mom = createUTC([2000, i]);
            shortPieces.push(this.monthsShort(mom, ''));
            longPieces.push(this.months(mom, ''));
            mixedPieces.push(this.months(mom, ''));
            mixedPieces.push(this.monthsShort(mom, ''));
        }
        // Sorting makes sure if one month (or abbr) is a prefix of another it
        // will match the longer piece.
        shortPieces.sort(cmpLenRev);
        longPieces.sort(cmpLenRev);
        mixedPieces.sort(cmpLenRev);
        for (i = 0; i < 12; i++) {
            shortPieces[i] = regexEscape(shortPieces[i]);
            longPieces[i] = regexEscape(longPieces[i]);
        }
        for (i = 0; i < 24; i++) {
            mixedPieces[i] = regexEscape(mixedPieces[i]);
        }

        this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
        this._monthsShortRegex = this._monthsRegex;
        this._monthsStrictRegex = new RegExp(
            '^(' + longPieces.join('|') + ')',
            'i'
        );
        this._monthsShortStrictRegex = new RegExp(
            '^(' + shortPieces.join('|') + ')',
            'i'
        );
    }

    // FORMATTING

    addFormatToken('Y', 0, 0, function () {
        var y = this.year();
        return y <= 9999 ? zeroFill(y, 4) : '+' + y;
    });

    addFormatToken(0, ['YY', 2], 0, function () {
        return this.year() % 100;
    });

    addFormatToken(0, ['YYYY', 4], 0, 'year');
    addFormatToken(0, ['YYYYY', 5], 0, 'year');
    addFormatToken(0, ['YYYYYY', 6, true], 0, 'year');

    // ALIASES

    addUnitAlias('year', 'y');

    // PRIORITIES

    addUnitPriority('year', 1);

    // PARSING

    addRegexToken('Y', matchSigned);
    addRegexToken('YY', match1to2, match2);
    addRegexToken('YYYY', match1to4, match4);
    addRegexToken('YYYYY', match1to6, match6);
    addRegexToken('YYYYYY', match1to6, match6);

    addParseToken(['YYYYY', 'YYYYYY'], YEAR);
    addParseToken('YYYY', function (input, array) {
        array[YEAR] =
            input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input);
    });
    addParseToken('YY', function (input, array) {
        array[YEAR] = hooks.parseTwoDigitYear(input);
    });
    addParseToken('Y', function (input, array) {
        array[YEAR] = parseInt(input, 10);
    });

    // HELPERS

    function daysInYear(year) {
        return isLeapYear(year) ? 366 : 365;
    }

    // HOOKS

    hooks.parseTwoDigitYear = function (input) {
        return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
    };

    // MOMENTS

    var getSetYear = makeGetSet('FullYear', true);

    function getIsLeapYear() {
        return isLeapYear(this.year());
    }

    function createDate(y, m, d, h, M, s, ms) {
        // can't just apply() to create a date:
        // https://stackoverflow.com/q/181348
        var date;
        // the date constructor remaps years 0-99 to 1900-1999
        if (y < 100 && y >= 0) {
            // preserve leap years using a full 400 year cycle, then reset
            date = new Date(y + 400, m, d, h, M, s, ms);
            if (isFinite(date.getFullYear())) {
                date.setFullYear(y);
            }
        } else {
            date = new Date(y, m, d, h, M, s, ms);
        }

        return date;
    }

    function createUTCDate(y) {
        var date, args;
        // the Date.UTC function remaps years 0-99 to 1900-1999
        if (y < 100 && y >= 0) {
            args = Array.prototype.slice.call(arguments);
            // preserve leap years using a full 400 year cycle, then reset
            args[0] = y + 400;
            date = new Date(Date.UTC.apply(null, args));
            if (isFinite(date.getUTCFullYear())) {
                date.setUTCFullYear(y);
            }
        } else {
            date = new Date(Date.UTC.apply(null, arguments));
        }

        return date;
    }

    // start-of-first-week - start-of-year
    function firstWeekOffset(year, dow, doy) {
        var // first-week day -- which january is always in the first week (4 for iso, 1 for other)
            fwd = 7 + dow - doy,
            // first-week day local weekday -- which local weekday is fwd
            fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7;

        return -fwdlw + fwd - 1;
    }

    // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
    function dayOfYearFromWeeks(year, week, weekday, dow, doy) {
        var localWeekday = (7 + weekday - dow) % 7,
            weekOffset = firstWeekOffset(year, dow, doy),
            dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset,
            resYear,
            resDayOfYear;

        if (dayOfYear <= 0) {
            resYear = year - 1;
            resDayOfYear = daysInYear(resYear) + dayOfYear;
        } else if (dayOfYear > daysInYear(year)) {
            resYear = year + 1;
            resDayOfYear = dayOfYear - daysInYear(year);
        } else {
            resYear = year;
            resDayOfYear = dayOfYear;
        }

        return {
            year: resYear,
            dayOfYear: resDayOfYear,
        };
    }

    function weekOfYear(mom, dow, doy) {
        var weekOffset = firstWeekOffset(mom.year(), dow, doy),
            week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1,
            resWeek,
            resYear;

        if (week < 1) {
            resYear = mom.year() - 1;
            resWeek = week + weeksInYear(resYear, dow, doy);
        } else if (week > weeksInYear(mom.year(), dow, doy)) {
            resWeek = week - weeksInYear(mom.year(), dow, doy);
            resYear = mom.year() + 1;
        } else {
            resYear = mom.year();
            resWeek = week;
        }

        return {
            week: resWeek,
            year: resYear,
        };
    }

    function weeksInYear(year, dow, doy) {
        var weekOffset = firstWeekOffset(year, dow, doy),
            weekOffsetNext = firstWeekOffset(year + 1, dow, doy);
        return (daysInYear(year) - weekOffset + weekOffsetNext) / 7;
    }

    // FORMATTING

    addFormatToken('w', ['ww', 2], 'wo', 'week');
    addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek');

    // ALIASES

    addUnitAlias('week', 'w');
    addUnitAlias('isoWeek', 'W');

    // PRIORITIES

    addUnitPriority('week', 5);
    addUnitPriority('isoWeek', 5);

    // PARSING

    addRegexToken('w', match1to2);
    addRegexToken('ww', match1to2, match2);
    addRegexToken('W', match1to2);
    addRegexToken('WW', match1to2, match2);

    addWeekParseToken(
        ['w', 'ww', 'W', 'WW'],
        function (input, week, config, token) {
            week[token.substr(0, 1)] = toInt(input);
        }
    );

    // HELPERS

    // LOCALES

    function localeWeek(mom) {
        return weekOfYear(mom, this._week.dow, this._week.doy).week;
    }

    var defaultLocaleWeek = {
        dow: 0, // Sunday is the first day of the week.
        doy: 6, // The week that contains Jan 6th is the first week of the year.
    };

    function localeFirstDayOfWeek() {
        return this._week.dow;
    }

    function localeFirstDayOfYear() {
        return this._week.doy;
    }

    // MOMENTS

    function getSetWeek(input) {
        var week = this.localeData().week(this);
        return input == null ? week : this.add((input - week) * 7, 'd');
    }

    function getSetISOWeek(input) {
        var week = weekOfYear(this, 1, 4).week;
        return input == null ? week : this.add((input - week) * 7, 'd');
    }

    // FORMATTING

    addFormatToken('d', 0, 'do', 'day');

    addFormatToken('dd', 0, 0, function (format) {
        return this.localeData().weekdaysMin(this, format);
    });

    addFormatToken('ddd', 0, 0, function (format) {
        return this.localeData().weekdaysShort(this, format);
    });

    addFormatToken('dddd', 0, 0, function (format) {
        return this.localeData().weekdays(this, format);
    });

    addFormatToken('e', 0, 0, 'weekday');
    addFormatToken('E', 0, 0, 'isoWeekday');

    // ALIASES

    addUnitAlias('day', 'd');
    addUnitAlias('weekday', 'e');
    addUnitAlias('isoWeekday', 'E');

    // PRIORITY
    addUnitPriority('day', 11);
    addUnitPriority('weekday', 11);
    addUnitPriority('isoWeekday', 11);

    // PARSING

    addRegexToken('d', match1to2);
    addRegexToken('e', match1to2);
    addRegexToken('E', match1to2);
    addRegexToken('dd', function (isStrict, locale) {
        return locale.weekdaysMinRegex(isStrict);
    });
    addRegexToken('ddd', function (isStrict, locale) {
        return locale.weekdaysShortRegex(isStrict);
    });
    addRegexToken('dddd', function (isStrict, locale) {
        return locale.weekdaysRegex(isStrict);
    });

    addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) {
        var weekday = config._locale.weekdaysParse(input, token, config._strict);
        // if we didn't get a weekday name, mark the date as invalid
        if (weekday != null) {
            week.d = weekday;
        } else {
            getParsingFlags(config).invalidWeekday = input;
        }
    });

    addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) {
        week[token] = toInt(input);
    });

    // HELPERS

    function parseWeekday(input, locale) {
        if (typeof input !== 'string') {
            return input;
        }

        if (!isNaN(input)) {
            return parseInt(input, 10);
        }

        input = locale.weekdaysParse(input);
        if (typeof input === 'number') {
            return input;
        }

        return null;
    }

    function parseIsoWeekday(input, locale) {
        if (typeof input === 'string') {
            return locale.weekdaysParse(input) % 7 || 7;
        }
        return isNaN(input) ? null : input;
    }

    // LOCALES
    function shiftWeekdays(ws, n) {
        return ws.slice(n, 7).concat(ws.slice(0, n));
    }

    var defaultLocaleWeekdays =
            'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
        defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
        defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
        defaultWeekdaysRegex = matchWord,
        defaultWeekdaysShortRegex = matchWord,
        defaultWeekdaysMinRegex = matchWord;

    function localeWeekdays(m, format) {
        var weekdays = isArray(this._weekdays)
            ? this._weekdays
            : this._weekdays[
                  m && m !== true && this._weekdays.isFormat.test(format)
                      ? 'format'
                      : 'standalone'
              ];
        return m === true
            ? shiftWeekdays(weekdays, this._week.dow)
            : m
            ? weekdays[m.day()]
            : weekdays;
    }

    function localeWeekdaysShort(m) {
        return m === true
            ? shiftWeekdays(this._weekdaysShort, this._week.dow)
            : m
            ? this._weekdaysShort[m.day()]
            : this._weekdaysShort;
    }

    function localeWeekdaysMin(m) {
        return m === true
            ? shiftWeekdays(this._weekdaysMin, this._week.dow)
            : m
            ? this._weekdaysMin[m.day()]
            : this._weekdaysMin;
    }

    function handleStrictParse$1(weekdayName, format, strict) {
        var i,
            ii,
            mom,
            llc = weekdayName.toLocaleLowerCase();
        if (!this._weekdaysParse) {
            this._weekdaysParse = [];
            this._shortWeekdaysParse = [];
            this._minWeekdaysParse = [];

            for (i = 0; i < 7; ++i) {
                mom = createUTC([2000, 1]).day(i);
                this._minWeekdaysParse[i] = this.weekdaysMin(
                    mom,
                    ''
                ).toLocaleLowerCase();
                this._shortWeekdaysParse[i] = this.weekdaysShort(
                    mom,
                    ''
                ).toLocaleLowerCase();
                this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase();
            }
        }

        if (strict) {
            if (format === 'dddd') {
                ii = indexOf.call(this._weekdaysParse, llc);
                return ii !== -1 ? ii : null;
            } else if (format === 'ddd') {
                ii = indexOf.call(this._shortWeekdaysParse, llc);
                return ii !== -1 ? ii : null;
            } else {
                ii = indexOf.call(this._minWeekdaysParse, llc);
                return ii !== -1 ? ii : null;
            }
        } else {
            if (format === 'dddd') {
                ii = indexOf.call(this._weekdaysParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._shortWeekdaysParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._minWeekdaysParse, llc);
                return ii !== -1 ? ii : null;
            } else if (format === 'ddd') {
                ii = indexOf.call(this._shortWeekdaysParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._weekdaysParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._minWeekdaysParse, llc);
                return ii !== -1 ? ii : null;
            } else {
                ii = indexOf.call(this._minWeekdaysParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._weekdaysParse, llc);
                if (ii !== -1) {
                    return ii;
                }
                ii = indexOf.call(this._shortWeekdaysParse, llc);
                return ii !== -1 ? ii : null;
            }
        }
    }

    function localeWeekdaysParse(weekdayName, format, strict) {
        var i, mom, regex;

        if (this._weekdaysParseExact) {
            return handleStrictParse$1.call(this, weekdayName, format, strict);
        }

        if (!this._weekdaysParse) {
            this._weekdaysParse = [];
            this._minWeekdaysParse = [];
            this._shortWeekdaysParse = [];
            this._fullWeekdaysParse = [];
        }

        for (i = 0; i < 7; i++) {
            // make the regex if we don't have it already

            mom = createUTC([2000, 1]).day(i);
            if (strict && !this._fullWeekdaysParse[i]) {
                this._fullWeekdaysParse[i] = new RegExp(
                    '^' + this.weekdays(mom, '').replace('.', '\\.?') + '$',
                    'i'
                );
                this._shortWeekdaysParse[i] = new RegExp(
                    '^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$',
                    'i'
                );
                this._minWeekdaysParse[i] = new RegExp(
                    '^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$',
                    'i'
                );
            }
            if (!this._weekdaysParse[i]) {
                regex =
                    '^' +
                    this.weekdays(mom, '') +
                    '|^' +
                    this.weekdaysShort(mom, '') +
                    '|^' +
                    this.weekdaysMin(mom, '');
                this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
            }
            // test the regex
            if (
                strict &&
                format === 'dddd' &&
                this._fullWeekdaysParse[i].test(weekdayName)
            ) {
                return i;
            } else if (
                strict &&
                format === 'ddd' &&
                this._shortWeekdaysParse[i].test(weekdayName)
            ) {
                return i;
            } else if (
                strict &&
                format === 'dd' &&
                this._minWeekdaysParse[i].test(weekdayName)
            ) {
                return i;
            } else if (!strict && this._weekdaysParse[i].test(weekdayName)) {
                return i;
            }
        }
    }

    // MOMENTS

    function getSetDayOfWeek(input) {
        if (!this.isValid()) {
            return input != null ? this : NaN;
        }
        var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
        if (input != null) {
            input = parseWeekday(input, this.localeData());
            return this.add(input - day, 'd');
        } else {
            return day;
        }
    }

    function getSetLocaleDayOfWeek(input) {
        if (!this.isValid()) {
            return input != null ? this : NaN;
        }
        var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
        return input == null ? weekday : this.add(input - weekday, 'd');
    }

    function getSetISODayOfWeek(input) {
        if (!this.isValid()) {
            return input != null ? this : NaN;
        }

        // behaves the same as moment#day except
        // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
        // as a setter, sunday should belong to the previous week.

        if (input != null) {
            var weekday = parseIsoWeekday(input, this.localeData());
            return this.day(this.day() % 7 ? weekday : weekday - 7);
        } else {
            return this.day() || 7;
        }
    }

    function weekdaysRegex(isStrict) {
        if (this._weekdaysParseExact) {
            if (!hasOwnProp(this, '_weekdaysRegex')) {
                computeWeekdaysParse.call(this);
            }
            if (isStrict) {
                return this._weekdaysStrictRegex;
            } else {
                return this._weekdaysRegex;
            }
        } else {
            if (!hasOwnProp(this, '_weekdaysRegex')) {
                this._weekdaysRegex = defaultWeekdaysRegex;
            }
            return this._weekdaysStrictRegex && isStrict
                ? this._weekdaysStrictRegex
                : this._weekdaysRegex;
        }
    }

    function weekdaysShortRegex(isStrict) {
        if (this._weekdaysParseExact) {
            if (!hasOwnProp(this, '_weekdaysRegex')) {
                computeWeekdaysParse.call(this);
            }
            if (isStrict) {
                return this._weekdaysShortStrictRegex;
            } else {
                return this._weekdaysShortRegex;
            }
        } else {
            if (!hasOwnProp(this, '_weekdaysShortRegex')) {
                this._weekdaysShortRegex = defaultWeekdaysShortRegex;
            }
            return this._weekdaysShortStrictRegex && isStrict
                ? this._weekdaysShortStrictRegex
                : this._weekdaysShortRegex;
        }
    }

    function weekdaysMinRegex(isStrict) {
        if (this._weekdaysParseExact) {
            if (!hasOwnProp(this, '_weekdaysRegex')) {
                computeWeekdaysParse.call(this);
            }
            if (isStrict) {
                return this._weekdaysMinStrictRegex;
            } else {
                return this._weekdaysMinRegex;
            }
        } else {
            if (!hasOwnProp(this, '_weekdaysMinRegex')) {
                this._weekdaysMinRegex = defaultWeekdaysMinRegex;
            }
            return this._weekdaysMinStrictRegex && isStrict
                ? this._weekdaysMinStrictRegex
                : this._weekdaysMinRegex;
        }
    }

    function computeWeekdaysParse() {
        function cmpLenRev(a, b) {
            return b.length - a.length;
        }

        var minPieces = [],
            shortPieces = [],
            longPieces = [],
            mixedPieces = [],
            i,
            mom,
            minp,
            shortp,
            longp;
        for (i = 0; i < 7; i++) {
            // make the regex if we don't have it already
            mom = createUTC([2000, 1]).day(i);
            minp = regexEscape(this.weekdaysMin(mom, ''));
            shortp = regexEscape(this.weekdaysShort(mom, ''));
            longp = regexEscape(this.weekdays(mom, ''));
            minPieces.push(minp);
            shortPieces.push(shortp);
            longPieces.push(longp);
            mixedPieces.push(minp);
            mixedPieces.push(shortp);
            mixedPieces.push(longp);
        }
        // Sorting makes sure if one weekday (or abbr) is a prefix of another it
        // will match the longer piece.
        minPieces.sort(cmpLenRev);
        shortPieces.sort(cmpLenRev);
        longPieces.sort(cmpLenRev);
        mixedPieces.sort(cmpLenRev);

        this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
        this._weekdaysShortRegex = this._weekdaysRegex;
        this._weekdaysMinRegex = this._weekdaysRegex;

        this._weekdaysStrictRegex = new RegExp(
            '^(' + longPieces.join('|') + ')',
            'i'
        );
        this._weekdaysShortStrictRegex = new RegExp(
            '^(' + shortPieces.join('|') + ')',
            'i'
        );
        this._weekdaysMinStrictRegex = new RegExp(
            '^(' + minPieces.join('|') + ')',
            'i'
        );
    }

    // FORMATTING

    function hFormat() {
        return this.hours() % 12 || 12;
    }

    function kFormat() {
        return this.hours() || 24;
    }

    addFormatToken('H', ['HH', 2], 0, 'hour');
    addFormatToken('h', ['hh', 2], 0, hFormat);
    addFormatToken('k', ['kk', 2], 0, kFormat);

    addFormatToken('hmm', 0, 0, function () {
        return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2);
    });

    addFormatToken('hmmss', 0, 0, function () {
        return (
            '' +
            hFormat.apply(this) +
            zeroFill(this.minutes(), 2) +
            zeroFill(this.seconds(), 2)
        );
    });

    addFormatToken('Hmm', 0, 0, function () {
        return '' + this.hours() + zeroFill(this.minutes(), 2);
    });

    addFormatToken('Hmmss', 0, 0, function () {
        return (
            '' +
            this.hours() +
            zeroFill(this.minutes(), 2) +
            zeroFill(this.seconds(), 2)
        );
    });

    function meridiem(token, lowercase) {
        addFormatToken(token, 0, 0, function () {
            return this.localeData().meridiem(
                this.hours(),
                this.minutes(),
                lowercase
            );
        });
    }

    meridiem('a', true);
    meridiem('A', false);

    // ALIASES

    addUnitAlias('hour', 'h');

    // PRIORITY
    addUnitPriority('hour', 13);

    // PARSING

    function matchMeridiem(isStrict, locale) {
        return locale._meridiemParse;
    }

    addRegexToken('a', matchMeridiem);
    addRegexToken('A', matchMeridiem);
    addRegexToken('H', match1to2);
    addRegexToken('h', match1to2);
    addRegexToken('k', match1to2);
    addRegexToken('HH', match1to2, match2);
    addRegexToken('hh', match1to2, match2);
    addRegexToken('kk', match1to2, match2);

    addRegexToken('hmm', match3to4);
    addRegexToken('hmmss', match5to6);
    addRegexToken('Hmm', match3to4);
    addRegexToken('Hmmss', match5to6);

    addParseToken(['H', 'HH'], HOUR);
    addParseToken(['k', 'kk'], function (input, array, config) {
        var kInput = toInt(input);
        array[HOUR] = kInput === 24 ? 0 : kInput;
    });
    addParseToken(['a', 'A'], function (input, array, config) {
        config._isPm = config._locale.isPM(input);
        config._meridiem = input;
    });
    addParseToken(['h', 'hh'], function (input, array, config) {
        array[HOUR] = toInt(input);
        getParsingFlags(config).bigHour = true;
    });
    addParseToken('hmm', function (input, array, config) {
        var pos = input.length - 2;
        array[HOUR] = toInt(input.substr(0, pos));
        array[MINUTE] = toInt(input.substr(pos));
        getParsingFlags(config).bigHour = true;
    });
    addParseToken('hmmss', function (input, array, config) {
        var pos1 = input.length - 4,
            pos2 = input.length - 2;
        array[HOUR] = toInt(input.substr(0, pos1));
        array[MINUTE] = toInt(input.substr(pos1, 2));
        array[SECOND] = toInt(input.substr(pos2));
        getParsingFlags(config).bigHour = true;
    });
    addParseToken('Hmm', function (input, array, config) {
        var pos = input.length - 2;
        array[HOUR] = toInt(input.substr(0, pos));
        array[MINUTE] = toInt(input.substr(pos));
    });
    addParseToken('Hmmss', function (input, array, config) {
        var pos1 = input.length - 4,
            pos2 = input.length - 2;
        array[HOUR] = toInt(input.substr(0, pos1));
        array[MINUTE] = toInt(input.substr(pos1, 2));
        array[SECOND] = toInt(input.substr(pos2));
    });

    // LOCALES

    function localeIsPM(input) {
        // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
        // Using charAt should be more compatible.
        return (input + '').toLowerCase().charAt(0) === 'p';
    }

    var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i,
        // Setting the hour should keep the time, because the user explicitly
        // specified which hour they want. So trying to maintain the same hour (in
        // a new timezone) makes sense. Adding/subtracting hours does not follow
        // this rule.
        getSetHour = makeGetSet('Hours', true);

    function localeMeridiem(hours, minutes, isLower) {
        if (hours > 11) {
            return isLower ? 'pm' : 'PM';
        } else {
            return isLower ? 'am' : 'AM';
        }
    }

    var baseConfig = {
        calendar: defaultCalendar,
        longDateFormat: defaultLongDateFormat,
        invalidDate: defaultInvalidDate,
        ordinal: defaultOrdinal,
        dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse,
        relativeTime: defaultRelativeTime,

        months: defaultLocaleMonths,
        monthsShort: defaultLocaleMonthsShort,

        week: defaultLocaleWeek,

        weekdays: defaultLocaleWeekdays,
        weekdaysMin: defaultLocaleWeekdaysMin,
        weekdaysShort: defaultLocaleWeekdaysShort,

        meridiemParse: defaultLocaleMeridiemParse,
    };

    // internal storage for locale config files
    var locales = {},
        localeFamilies = {},
        globalLocale;

    function commonPrefix(arr1, arr2) {
        var i,
            minl = Math.min(arr1.length, arr2.length);
        for (i = 0; i < minl; i += 1) {
            if (arr1[i] !== arr2[i]) {
                return i;
            }
        }
        return minl;
    }

    function normalizeLocale(key) {
        return key ? key.toLowerCase().replace('_', '-') : key;
    }

    // pick the locale from the array
    // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
    // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
    function chooseLocale(names) {
        var i = 0,
            j,
            next,
            locale,
            split;

        while (i < names.length) {
            split = normalizeLocale(names[i]).split('-');
            j = split.length;
            next = normalizeLocale(names[i + 1]);
            next = next ? next.split('-') : null;
            while (j > 0) {
                locale = loadLocale(split.slice(0, j).join('-'));
                if (locale) {
                    return locale;
                }
                if (
                    next &&
                    next.length >= j &&
                    commonPrefix(split, next) >= j - 1
                ) {
                    //the next array item is better than a shallower substring of this one
                    break;
                }
                j--;
            }
            i++;
        }
        return globalLocale;
    }

    function isLocaleNameSane(name) {
        // Prevent names that look like filesystem paths, i.e contain '/' or '\'
        return name.match('^[^/\\\\]*$') != null;
    }

    function loadLocale(name) {
        var oldLocale = null,
            aliasedRequire;
        // TODO: Find a better way to register and load all the locales in Node
        if (
            locales[name] === undefined &&
            typeof module !== 'undefined' &&
            module &&
            module.exports &&
            isLocaleNameSane(name)
        ) {
            try {
                oldLocale = globalLocale._abbr;
                aliasedRequire = require;
                aliasedRequire('./locale/' + name);
                getSetGlobalLocale(oldLocale);
            } catch (e) {
                // mark as not found to avoid repeating expensive file require call causing high CPU
                // when trying to find en-US, en_US, en-us for every format call
                locales[name] = null; // null means not found
            }
        }
        return locales[name];
    }

    // This function will load locale and then set the global locale.  If
    // no arguments are passed in, it will simply return the current global
    // locale key.
    function getSetGlobalLocale(key, values) {
        var data;
        if (key) {
            if (isUndefined(values)) {
                data = getLocale(key);
            } else {
                data = defineLocale(key, values);
            }

            if (data) {
                // moment.duration._locale = moment._locale = data;
                globalLocale = data;
            } else {
                if (typeof console !== 'undefined' && console.warn) {
                    //warn user if arguments are passed but the locale could not be set
                    console.warn(
                        'Locale ' + key + ' not found. Did you forget to load it?'
                    );
                }
            }
        }

        return globalLocale._abbr;
    }

    function defineLocale(name, config) {
        if (config !== null) {
            var locale,
                parentConfig = baseConfig;
            config.abbr = name;
            if (locales[name] != null) {
                deprecateSimple(
                    'defineLocaleOverride',
                    'use moment.updateLocale(localeName, config) to change ' +
                        'an existing locale. moment.defineLocale(localeName, ' +
                        'config) should only be used for creating a new locale ' +
                        'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'
                );
                parentConfig = locales[name]._config;
            } else if (config.parentLocale != null) {
                if (locales[config.parentLocale] != null) {
                    parentConfig = locales[config.parentLocale]._config;
                } else {
                    locale = loadLocale(config.parentLocale);
                    if (locale != null) {
                        parentConfig = locale._config;
                    } else {
                        if (!localeFamilies[config.parentLocale]) {
                            localeFamilies[config.parentLocale] = [];
                        }
                        localeFamilies[config.parentLocale].push({
                            name: name,
                            config: config,
                        });
                        return null;
                    }
                }
            }
            locales[name] = new Locale(mergeConfigs(parentConfig, config));

            if (localeFamilies[name]) {
                localeFamilies[name].forEach(function (x) {
                    defineLocale(x.name, x.config);
                });
            }

            // backwards compat for now: also set the locale
            // make sure we set the locale AFTER all child locales have been
            // created, so we won't end up with the child locale set.
            getSetGlobalLocale(name);

            return locales[name];
        } else {
            // useful for testing
            delete locales[name];
            return null;
        }
    }

    function updateLocale(name, config) {
        if (config != null) {
            var locale,
                tmpLocale,
                parentConfig = baseConfig;

            if (locales[name] != null && locales[name].parentLocale != null) {
                // Update existing child locale in-place to avoid memory-leaks
                locales[name].set(mergeConfigs(locales[name]._config, config));
            } else {
                // MERGE
                tmpLocale = loadLocale(name);
                if (tmpLocale != null) {
                    parentConfig = tmpLocale._config;
                }
                config = mergeConfigs(parentConfig, config);
                if (tmpLocale == null) {
                    // updateLocale is called for creating a new locale
                    // Set abbr so it will have a name (getters return
                    // undefined otherwise).
                    config.abbr = name;
                }
                locale = new Locale(config);
                locale.parentLocale = locales[name];
                locales[name] = locale;
            }

            // backwards compat for now: also set the locale
            getSetGlobalLocale(name);
        } else {
            // pass null for config to unupdate, useful for tests
            if (locales[name] != null) {
                if (locales[name].parentLocale != null) {
                    locales[name] = locales[name].parentLocale;
                    if (name === getSetGlobalLocale()) {
                        getSetGlobalLocale(name);
                    }
                } else if (locales[name] != null) {
                    delete locales[name];
                }
            }
        }
        return locales[name];
    }

    // returns locale data
    function getLocale(key) {
        var locale;

        if (key && key._locale && key._locale._abbr) {
            key = key._locale._abbr;
        }

        if (!key) {
            return globalLocale;
        }

        if (!isArray(key)) {
            //short-circuit everything else
            locale = loadLocale(key);
            if (locale) {
                return locale;
            }
            key = [key];
        }

        return chooseLocale(key);
    }

    function listLocales() {
        return keys(locales);
    }

    function checkOverflow(m) {
        var overflow,
            a = m._a;

        if (a && getParsingFlags(m).overflow === -2) {
            overflow =
                a[MONTH] < 0 || a[MONTH] > 11
                    ? MONTH
                    : a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH])
                    ? DATE
                    : a[HOUR] < 0 ||
                      a[HOUR] > 24 ||
                      (a[HOUR] === 24 &&
                          (a[MINUTE] !== 0 ||
                              a[SECOND] !== 0 ||
                              a[MILLISECOND] !== 0))
                    ? HOUR
                    : a[MINUTE] < 0 || a[MINUTE] > 59
                    ? MINUTE
                    : a[SECOND] < 0 || a[SECOND] > 59
                    ? SECOND
                    : a[MILLISECOND] < 0 || a[MILLISECOND] > 999
                    ? MILLISECOND
                    : -1;

            if (
                getParsingFlags(m)._overflowDayOfYear &&
                (overflow < YEAR || overflow > DATE)
            ) {
                overflow = DATE;
            }
            if (getParsingFlags(m)._overflowWeeks && overflow === -1) {
                overflow = WEEK;
            }
            if (getParsingFlags(m)._overflowWeekday && overflow === -1) {
                overflow = WEEKDAY;
            }

            getParsingFlags(m).overflow = overflow;
        }

        return m;
    }

    // iso 8601 regex
    // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
    var extendedIsoRegex =
            /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
        basicIsoRegex =
            /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
        tzRegex = /Z|[+-]\d\d(?::?\d\d)?/,
        isoDates = [
            ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/],
            ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/],
            ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/],
            ['GGGG-[W]WW', /\d{4}-W\d\d/, false],
            ['YYYY-DDD', /\d{4}-\d{3}/],
            ['YYYY-MM', /\d{4}-\d\d/, false],
            ['YYYYYYMMDD', /[+-]\d{10}/],
            ['YYYYMMDD', /\d{8}/],
            ['GGGG[W]WWE', /\d{4}W\d{3}/],
            ['GGGG[W]WW', /\d{4}W\d{2}/, false],
            ['YYYYDDD', /\d{7}/],
            ['YYYYMM', /\d{6}/, false],
            ['YYYY', /\d{4}/, false],
        ],
        // iso time formats and regexes
        isoTimes = [
            ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/],
            ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/],
            ['HH:mm:ss', /\d\d:\d\d:\d\d/],
            ['HH:mm', /\d\d:\d\d/],
            ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/],
            ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/],
            ['HHmmss', /\d\d\d\d\d\d/],
            ['HHmm', /\d\d\d\d/],
            ['HH', /\d\d/],
        ],
        aspNetJsonRegex = /^\/?Date\((-?\d+)/i,
        // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3
        rfc2822 =
            /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,
        obsOffsets = {
            UT: 0,
            GMT: 0,
            EDT: -4 * 60,
            EST: -5 * 60,
            CDT: -5 * 60,
            CST: -6 * 60,
            MDT: -6 * 60,
            MST: -7 * 60,
            PDT: -7 * 60,
            PST: -8 * 60,
        };

    // date from iso format
    function configFromISO(config) {
        var i,
            l,
            string = config._i,
            match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string),
            allowTime,
            dateFormat,
            timeFormat,
            tzFormat,
            isoDatesLen = isoDates.length,
            isoTimesLen = isoTimes.length;

        if (match) {
            getParsingFlags(config).iso = true;
            for (i = 0, l = isoDatesLen; i < l; i++) {
                if (isoDates[i][1].exec(match[1])) {
                    dateFormat = isoDates[i][0];
                    allowTime = isoDates[i][2] !== false;
                    break;
                }
            }
            if (dateFormat == null) {
                config._isValid = false;
                return;
            }
            if (match[3]) {
                for (i = 0, l = isoTimesLen; i < l; i++) {
                    if (isoTimes[i][1].exec(match[3])) {
                        // match[2] should be 'T' or space
                        timeFormat = (match[2] || ' ') + isoTimes[i][0];
                        break;
                    }
                }
                if (timeFormat == null) {
                    config._isValid = false;
                    return;
                }
            }
            if (!allowTime && timeFormat != null) {
                config._isValid = false;
                return;
            }
            if (match[4]) {
                if (tzRegex.exec(match[4])) {
                    tzFormat = 'Z';
                } else {
                    config._isValid = false;
                    return;
                }
            }
            config._f = dateFormat + (timeFormat || '') + (tzFormat || '');
            configFromStringAndFormat(config);
        } else {
            config._isValid = false;
        }
    }

    function extractFromRFC2822Strings(
        yearStr,
        monthStr,
        dayStr,
        hourStr,
        minuteStr,
        secondStr
    ) {
        var result = [
            untruncateYear(yearStr),
            defaultLocaleMonthsShort.indexOf(monthStr),
            parseInt(dayStr, 10),
            parseInt(hourStr, 10),
            parseInt(minuteStr, 10),
        ];

        if (secondStr) {
            result.push(parseInt(secondStr, 10));
        }

        return result;
    }

    function untruncateYear(yearStr) {
        var year = parseInt(yearStr, 10);
        if (year <= 49) {
            return 2000 + year;
        } else if (year <= 999) {
            return 1900 + year;
        }
        return year;
    }

    function preprocessRFC2822(s) {
        // Remove comments and folding whitespace and replace multiple-spaces with a single space
        return s
            .replace(/\([^()]*\)|[\n\t]/g, ' ')
            .replace(/(\s\s+)/g, ' ')
            .replace(/^\s\s*/, '')
            .replace(/\s\s*$/, '');
    }

    function checkWeekday(weekdayStr, parsedInput, config) {
        if (weekdayStr) {
            // TODO: Replace the vanilla JS Date object with an independent day-of-week check.
            var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr),
                weekdayActual = new Date(
                    parsedInput[0],
                    parsedInput[1],
                    parsedInput[2]
                ).getDay();
            if (weekdayProvided !== weekdayActual) {
                getParsingFlags(config).weekdayMismatch = true;
                config._isValid = false;
                return false;
            }
        }
        return true;
    }

    function calculateOffset(obsOffset, militaryOffset, numOffset) {
        if (obsOffset) {
            return obsOffsets[obsOffset];
        } else if (militaryOffset) {
            // the only allowed military tz is Z
            return 0;
        } else {
            var hm = parseInt(numOffset, 10),
                m = hm % 100,
                h = (hm - m) / 100;
            return h * 60 + m;
        }
    }

    // date and time from ref 2822 format
    function configFromRFC2822(config) {
        var match = rfc2822.exec(preprocessRFC2822(config._i)),
            parsedArray;
        if (match) {
            parsedArray = extractFromRFC2822Strings(
                match[4],
                match[3],
                match[2],
                match[5],
                match[6],
                match[7]
            );
            if (!checkWeekday(match[1], parsedArray, config)) {
                return;
            }

            config._a = parsedArray;
            config._tzm = calculateOffset(match[8], match[9], match[10]);

            config._d = createUTCDate.apply(null, config._a);
            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);

            getParsingFlags(config).rfc2822 = true;
        } else {
            config._isValid = false;
        }
    }

    // date from 1) ASP.NET, 2) ISO, 3) RFC 2822 formats, or 4) optional fallback if parsing isn't strict
    function configFromString(config) {
        var matched = aspNetJsonRegex.exec(config._i);
        if (matched !== null) {
            config._d = new Date(+matched[1]);
            return;
        }

        configFromISO(config);
        if (config._isValid === false) {
            delete config._isValid;
        } else {
            return;
        }

        configFromRFC2822(config);
        if (config._isValid === false) {
            delete config._isValid;
        } else {
            return;
        }

        if (config._strict) {
            config._isValid = false;
        } else {
            // Final attempt, use Input Fallback
            hooks.createFromInputFallback(config);
        }
    }

    hooks.createFromInputFallback = deprecate(
        'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' +
            'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' +
            'discouraged. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.',
        function (config) {
            config._d = new Date(config._i + (config._useUTC ? ' UTC' : ''));
        }
    );

    // Pick the first defined of two or three arguments.
    function defaults(a, b, c) {
        if (a != null) {
            return a;
        }
        if (b != null) {
            return b;
        }
        return c;
    }

    function currentDateArray(config) {
        // hooks is actually the exported moment object
        var nowValue = new Date(hooks.now());
        if (config._useUTC) {
            return [
                nowValue.getUTCFullYear(),
                nowValue.getUTCMonth(),
                nowValue.getUTCDate(),
            ];
        }
        return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()];
    }

    // convert an array to a date.
    // the array should mirror the parameters below
    // note: all values past the year are optional and will default to the lowest possible value.
    // [year, month, day , hour, minute, second, millisecond]
    function configFromArray(config) {
        var i,
            date,
            input = [],
            currentDate,
            expectedWeekday,
            yearToUse;

        if (config._d) {
            return;
        }

        currentDate = currentDateArray(config);

        //compute day of the year from weeks and weekdays
        if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
            dayOfYearFromWeekInfo(config);
        }

        //if the day of the year is set, figure out what it is
        if (config._dayOfYear != null) {
            yearToUse = defaults(config._a[YEAR], currentDate[YEAR]);

            if (
                config._dayOfYear > daysInYear(yearToUse) ||
                config._dayOfYear === 0
            ) {
                getParsingFlags(config)._overflowDayOfYear = true;
            }

            date = createUTCDate(yearToUse, 0, config._dayOfYear);
            config._a[MONTH] = date.getUTCMonth();
            config._a[DATE] = date.getUTCDate();
        }

        // Default to current date.
        // * if no year, month, day of month are given, default to today
        // * if day of month is given, default month and year
        // * if month is given, default only year
        // * if year is given, don't default anything
        for (i = 0; i < 3 && config._a[i] == null; ++i) {
            config._a[i] = input[i] = currentDate[i];
        }

        // Zero out whatever was not defaulted, including time
        for (; i < 7; i++) {
            config._a[i] = input[i] =
                config._a[i] == null ? (i === 2 ? 1 : 0) : config._a[i];
        }

        // Check for 24:00:00.000
        if (
            config._a[HOUR] === 24 &&
            config._a[MINUTE] === 0 &&
            config._a[SECOND] === 0 &&
            config._a[MILLISECOND] === 0
        ) {
            config._nextDay = true;
            config._a[HOUR] = 0;
        }

        config._d = (config._useUTC ? createUTCDate : createDate).apply(
            null,
            input
        );
        expectedWeekday = config._useUTC
            ? config._d.getUTCDay()
            : config._d.getDay();

        // Apply timezone offset from input. The actual utcOffset can be changed
        // with parseZone.
        if (config._tzm != null) {
            config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm);
        }

        if (config._nextDay) {
            config._a[HOUR] = 24;
        }

        // check for mismatching day of week
        if (
            config._w &&
            typeof config._w.d !== 'undefined' &&
            config._w.d !== expectedWeekday
        ) {
            getParsingFlags(config).weekdayMismatch = true;
        }
    }

    function dayOfYearFromWeekInfo(config) {
        var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow, curWeek;

        w = config._w;
        if (w.GG != null || w.W != null || w.E != null) {
            dow = 1;
            doy = 4;

            // TODO: We need to take the current isoWeekYear, but that depends on
            // how we interpret now (local, utc, fixed offset). So create
            // a now version of current config (take local/utc/offset flags, and
            // create now).
            weekYear = defaults(
                w.GG,
                config._a[YEAR],
                weekOfYear(createLocal(), 1, 4).year
            );
            week = defaults(w.W, 1);
            weekday = defaults(w.E, 1);
            if (weekday < 1 || weekday > 7) {
                weekdayOverflow = true;
            }
        } else {
            dow = config._locale._week.dow;
            doy = config._locale._week.doy;

            curWeek = weekOfYear(createLocal(), dow, doy);

            weekYear = defaults(w.gg, config._a[YEAR], curWeek.year);

            // Default to current week.
            week = defaults(w.w, curWeek.week);

            if (w.d != null) {
                // weekday -- low day numbers are considered next week
                weekday = w.d;
                if (weekday < 0 || weekday > 6) {
                    weekdayOverflow = true;
                }
            } else if (w.e != null) {
                // local weekday -- counting starts from beginning of week
                weekday = w.e + dow;
                if (w.e < 0 || w.e > 6) {
                    weekdayOverflow = true;
                }
            } else {
                // default to beginning of week
                weekday = dow;
            }
        }
        if (week < 1 || week > weeksInYear(weekYear, dow, doy)) {
            getParsingFlags(config)._overflowWeeks = true;
        } else if (weekdayOverflow != null) {
            getParsingFlags(config)._overflowWeekday = true;
        } else {
            temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy);
            config._a[YEAR] = temp.year;
            config._dayOfYear = temp.dayOfYear;
        }
    }

    // constant that refers to the ISO standard
    hooks.ISO_8601 = function () {};

    // constant that refers to the RFC 2822 form
    hooks.RFC_2822 = function () {};

    // date from string and format string
    function configFromStringAndFormat(config) {
        // TODO: Move this to another part of the creation flow to prevent circular deps
        if (config._f === hooks.ISO_8601) {
            configFromISO(config);
            return;
        }
        if (config._f === hooks.RFC_2822) {
            configFromRFC2822(config);
            return;
        }
        config._a = [];
        getParsingFlags(config).empty = true;

        // This array is used to make a Date, either with `new Date` or `Date.UTC`
        var string = '' + config._i,
            i,
            parsedInput,
            tokens,
            token,
            skipped,
            stringLength = string.length,
            totalParsedInputLength = 0,
            era,
            tokenLen;

        tokens =
            expandFormat(config._f, config._locale).match(formattingTokens) || [];
        tokenLen = tokens.length;
        for (i = 0; i < tokenLen; i++) {
            token = tokens[i];
            parsedInput = (string.match(getParseRegexForToken(token, config)) ||
                [])[0];
            if (parsedInput) {
                skipped = string.substr(0, string.indexOf(parsedInput));
                if (skipped.length > 0) {
                    getParsingFlags(config).unusedInput.push(skipped);
                }
                string = string.slice(
                    string.indexOf(parsedInput) + parsedInput.length
                );
                totalParsedInputLength += parsedInput.length;
            }
            // don't parse if it's not a known token
            if (formatTokenFunctions[token]) {
                if (parsedInput) {
                    getParsingFlags(config).empty = false;
                } else {
                    getParsingFlags(config).unusedTokens.push(token);
                }
                addTimeToArrayFromToken(token, parsedInput, config);
            } else if (config._strict && !parsedInput) {
                getParsingFlags(config).unusedTokens.push(token);
            }
        }

        // add remaining unparsed input length to the string
        getParsingFlags(config).charsLeftOver =
            stringLength - totalParsedInputLength;
        if (string.length > 0) {
            getParsingFlags(config).unusedInput.push(string);
        }

        // clear _12h flag if hour is <= 12
        if (
            config._a[HOUR] <= 12 &&
            getParsingFlags(config).bigHour === true &&
            config._a[HOUR] > 0
        ) {
            getParsingFlags(config).bigHour = undefined;
        }

        getParsingFlags(config).parsedDateParts = config._a.slice(0);
        getParsingFlags(config).meridiem = config._meridiem;
        // handle meridiem
        config._a[HOUR] = meridiemFixWrap(
            config._locale,
            config._a[HOUR],
            config._meridiem
        );

        // handle era
        era = getParsingFlags(config).era;
        if (era !== null) {
            config._a[YEAR] = config._locale.erasConvertYear(era, config._a[YEAR]);
        }

        configFromArray(config);
        checkOverflow(config);
    }

    function meridiemFixWrap(locale, hour, meridiem) {
        var isPm;

        if (meridiem == null) {
            // nothing to do
            return hour;
        }
        if (locale.meridiemHour != null) {
            return locale.meridiemHour(hour, meridiem);
        } else if (locale.isPM != null) {
            // Fallback
            isPm = locale.isPM(meridiem);
            if (isPm && hour < 12) {
                hour += 12;
            }
            if (!isPm && hour === 12) {
                hour = 0;
            }
            return hour;
        } else {
            // this is not supposed to happen
            return hour;
        }
    }

    // date from string and array of format strings
    function configFromStringAndArray(config) {
        var tempConfig,
            bestMoment,
            scoreToBeat,
            i,
            currentScore,
            validFormatFound,
            bestFormatIsValid = false,
            configfLen = config._f.length;

        if (configfLen === 0) {
            getParsingFlags(config).invalidFormat = true;
            config._d = new Date(NaN);
            return;
        }

        for (i = 0; i < configfLen; i++) {
            currentScore = 0;
            validFormatFound = false;
            tempConfig = copyConfig({}, config);
            if (config._useUTC != null) {
                tempConfig._useUTC = config._useUTC;
            }
            tempConfig._f = config._f[i];
            configFromStringAndFormat(tempConfig);

            if (isValid(tempConfig)) {
                validFormatFound = true;
            }

            // if there is any input that was not parsed add a penalty for that format
            currentScore += getParsingFlags(tempConfig).charsLeftOver;

            //or tokens
            currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10;

            getParsingFlags(tempConfig).score = currentScore;

            if (!bestFormatIsValid) {
                if (
                    scoreToBeat == null ||
                    currentScore < scoreToBeat ||
                    validFormatFound
                ) {
                    scoreToBeat = currentScore;
                    bestMoment = tempConfig;
                    if (validFormatFound) {
                        bestFormatIsValid = true;
                    }
                }
            } else {
                if (currentScore < scoreToBeat) {
                    scoreToBeat = currentScore;
                    bestMoment = tempConfig;
                }
            }
        }

        extend(config, bestMoment || tempConfig);
    }

    function configFromObject(config) {
        if (config._d) {
            return;
        }

        var i = normalizeObjectUnits(config._i),
            dayOrDate = i.day === undefined ? i.date : i.day;
        config._a = map(
            [i.year, i.month, dayOrDate, i.hour, i.minute, i.second, i.millisecond],
            function (obj) {
                return obj && parseInt(obj, 10);
            }
        );

        configFromArray(config);
    }

    function createFromConfig(config) {
        var res = new Moment(checkOverflow(prepareConfig(config)));
        if (res._nextDay) {
            // Adding is smart enough around DST
            res.add(1, 'd');
            res._nextDay = undefined;
        }

        return res;
    }

    function prepareConfig(config) {
        var input = config._i,
            format = config._f;

        config._locale = config._locale || getLocale(config._l);

        if (input === null || (format === undefined && input === '')) {
            return createInvalid({ nullInput: true });
        }

        if (typeof input === 'string') {
            config._i = input = config._locale.preparse(input);
        }

        if (isMoment(input)) {
            return new Moment(checkOverflow(input));
        } else if (isDate(input)) {
            config._d = input;
        } else if (isArray(format)) {
            configFromStringAndArray(config);
        } else if (format) {
            configFromStringAndFormat(config);
        } else {
            configFromInput(config);
        }

        if (!isValid(config)) {
            config._d = null;
        }

        return config;
    }

    function configFromInput(config) {
        var input = config._i;
        if (isUndefined(input)) {
            config._d = new Date(hooks.now());
        } else if (isDate(input)) {
            config._d = new Date(input.valueOf());
        } else if (typeof input === 'string') {
            configFromString(config);
        } else if (isArray(input)) {
            config._a = map(input.slice(0), function (obj) {
                return parseInt(obj, 10);
            });
            configFromArray(config);
        } else if (isObject(input)) {
            configFromObject(config);
        } else if (isNumber(input)) {
            // from milliseconds
            config._d = new Date(input);
        } else {
            hooks.createFromInputFallback(config);
        }
    }

    function createLocalOrUTC(input, format, locale, strict, isUTC) {
        var c = {};

        if (format === true || format === false) {
            strict = format;
            format = undefined;
        }

        if (locale === true || locale === false) {
            strict = locale;
            locale = undefined;
        }

        if (
            (isObject(input) && isObjectEmpty(input)) ||
            (isArray(input) && input.length === 0)
        ) {
            input = undefined;
        }
        // object construction must be done this way.
        // https://github.com/moment/moment/issues/1423
        c._isAMomentObject = true;
        c._useUTC = c._isUTC = isUTC;
        c._l = locale;
        c._i = input;
        c._f = format;
        c._strict = strict;

        return createFromConfig(c);
    }

    function createLocal(input, format, locale, strict) {
        return createLocalOrUTC(input, format, locale, strict, false);
    }

    var prototypeMin = deprecate(
            'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/',
            function () {
                var other = createLocal.apply(null, arguments);
                if (this.isValid() && other.isValid()) {
                    return other < this ? this : other;
                } else {
                    return createInvalid();
                }
            }
        ),
        prototypeMax = deprecate(
            'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/',
            function () {
                var other = createLocal.apply(null, arguments);
                if (this.isValid() && other.isValid()) {
                    return other > this ? this : other;
                } else {
                    return createInvalid();
                }
            }
        );

    // Pick a moment m from moments so that m[fn](other) is true for all
    // other. This relies on the function fn to be transitive.
    //
    // moments should either be an array of moment objects or an array, whose
    // first element is an array of moment objects.
    function pickBy(fn, moments) {
        var res, i;
        if (moments.length === 1 && isArray(moments[0])) {
            moments = moments[0];
        }
        if (!moments.length) {
            return createLocal();
        }
        res = moments[0];
        for (i = 1; i < moments.length; ++i) {
            if (!moments[i].isValid() || moments[i][fn](res)) {
                res = moments[i];
            }
        }
        return res;
    }

    // TODO: Use [].sort instead?
    function min() {
        var args = [].slice.call(arguments, 0);

        return pickBy('isBefore', args);
    }

    function max() {
        var args = [].slice.call(arguments, 0);

        return pickBy('isAfter', args);
    }

    var now = function () {
        return Date.now ? Date.now() : +new Date();
    };

    var ordering = [
        'year',
        'quarter',
        'month',
        'week',
        'day',
        'hour',
        'minute',
        'second',
        'millisecond',
    ];

    function isDurationValid(m) {
        var key,
            unitHasDecimal = false,
            i,
            orderLen = ordering.length;
        for (key in m) {
            if (
                hasOwnProp(m, key) &&
                !(
                    indexOf.call(ordering, key) !== -1 &&
                    (m[key] == null || !isNaN(m[key]))
                )
            ) {
                return false;
            }
        }

        for (i = 0; i < orderLen; ++i) {
            if (m[ordering[i]]) {
                if (unitHasDecimal) {
                    return false; // only allow non-integers for smallest unit
                }
                if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) {
                    unitHasDecimal = true;
                }
            }
        }

        return true;
    }

    function isValid$1() {
        return this._isValid;
    }

    function createInvalid$1() {
        return createDuration(NaN);
    }

    function Duration(duration) {
        var normalizedInput = normalizeObjectUnits(duration),
            years = normalizedInput.year || 0,
            quarters = normalizedInput.quarter || 0,
            months = normalizedInput.month || 0,
            weeks = normalizedInput.week || normalizedInput.isoWeek || 0,
            days = normalizedInput.day || 0,
            hours = normalizedInput.hour || 0,
            minutes = normalizedInput.minute || 0,
            seconds = normalizedInput.second || 0,
            milliseconds = normalizedInput.millisecond || 0;

        this._isValid = isDurationValid(normalizedInput);

        // representation for dateAddRemove
        this._milliseconds =
            +milliseconds +
            seconds * 1e3 + // 1000
            minutes * 6e4 + // 1000 * 60
            hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978
        // Because of dateAddRemove treats 24 hours as different from a
        // day when working around DST, we need to store them separately
        this._days = +days + weeks * 7;
        // It is impossible to translate months into days without knowing
        // which months you are are talking about, so we have to store
        // it separately.
        this._months = +months + quarters * 3 + years * 12;

        this._data = {};

        this._locale = getLocale();

        this._bubble();
    }

    function isDuration(obj) {
        return obj instanceof Duration;
    }

    function absRound(number) {
        if (number < 0) {
            return Math.round(-1 * number) * -1;
        } else {
            return Math.round(number);
        }
    }

    // compare two arrays, return the number of differences
    function compareArrays(array1, array2, dontConvert) {
        var len = Math.min(array1.length, array2.length),
            lengthDiff = Math.abs(array1.length - array2.length),
            diffs = 0,
            i;
        for (i = 0; i < len; i++) {
            if (
                (dontConvert && array1[i] !== array2[i]) ||
                (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))
            ) {
                diffs++;
            }
        }
        return diffs + lengthDiff;
    }

    // FORMATTING

    function offset(token, separator) {
        addFormatToken(token, 0, 0, function () {
            var offset = this.utcOffset(),
                sign = '+';
            if (offset < 0) {
                offset = -offset;
                sign = '-';
            }
            return (
                sign +
                zeroFill(~~(offset / 60), 2) +
                separator +
                zeroFill(~~offset % 60, 2)
            );
        });
    }

    offset('Z', ':');
    offset('ZZ', '');

    // PARSING

    addRegexToken('Z', matchShortOffset);
    addRegexToken('ZZ', matchShortOffset);
    addParseToken(['Z', 'ZZ'], function (input, array, config) {
        config._useUTC = true;
        config._tzm = offsetFromString(matchShortOffset, input);
    });

    // HELPERS

    // timezone chunker
    // '+10:00' > ['10',  '00']
    // '-1530'  > ['-15', '30']
    var chunkOffset = /([\+\-]|\d\d)/gi;

    function offsetFromString(matcher, string) {
        var matches = (string || '').match(matcher),
            chunk,
            parts,
            minutes;

        if (matches === null) {
            return null;
        }

        chunk = matches[matches.length - 1] || [];
        parts = (chunk + '').match(chunkOffset) || ['-', 0, 0];
        minutes = +(parts[1] * 60) + toInt(parts[2]);

        return minutes === 0 ? 0 : parts[0] === '+' ? minutes : -minutes;
    }

    // Return a moment from input, that is local/utc/zone equivalent to model.
    function cloneWithOffset(input, model) {
        var res, diff;
        if (model._isUTC) {
            res = model.clone();
            diff =
                (isMoment(input) || isDate(input)
                    ? input.valueOf()
                    : createLocal(input).valueOf()) - res.valueOf();
            // Use low-level api, because this fn is low-level api.
            res._d.setTime(res._d.valueOf() + diff);
            hooks.updateOffset(res, false);
            return res;
        } else {
            return createLocal(input).local();
        }
    }

    function getDateOffset(m) {
        // On Firefox.24 Date#getTimezoneOffset returns a floating point.
        // https://github.com/moment/moment/pull/1871
        return -Math.round(m._d.getTimezoneOffset());
    }

    // HOOKS

    // This function will be called whenever a moment is mutated.
    // It is intended to keep the offset in sync with the timezone.
    hooks.updateOffset = function () {};

    // MOMENTS

    // keepLocalTime = true means only change the timezone, without
    // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]-->
    // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset
    // +0200, so we adjust the time as needed, to be valid.
    //
    // Keeping the time actually adds/subtracts (one hour)
    // from the actual represented time. That is why we call updateOffset
    // a second time. In case it wants us to change the offset again
    // _changeInProgress == true case, then we have to adjust, because
    // there is no such time in the given timezone.
    function getSetOffset(input, keepLocalTime, keepMinutes) {
        var offset = this._offset || 0,
            localAdjust;
        if (!this.isValid()) {
            return input != null ? this : NaN;
        }
        if (input != null) {
            if (typeof input === 'string') {
                input = offsetFromString(matchShortOffset, input);
                if (input === null) {
                    return this;
                }
            } else if (Math.abs(input) < 16 && !keepMinutes) {
                input = input * 60;
            }
            if (!this._isUTC && keepLocalTime) {
                localAdjust = getDateOffset(this);
            }
            this._offset = input;
            this._isUTC = true;
            if (localAdjust != null) {
                this.add(localAdjust, 'm');
            }
            if (offset !== input) {
                if (!keepLocalTime || this._changeInProgress) {
                    addSubtract(
                        this,
                        createDuration(input - offset, 'm'),
                        1,
                        false
                    );
                } else if (!this._changeInProgress) {
                    this._changeInProgress = true;
                    hooks.updateOffset(this, true);
                    this._changeInProgress = null;
                }
            }
            return this;
        } else {
            return this._isUTC ? offset : getDateOffset(this);
        }
    }

    function getSetZone(input, keepLocalTime) {
        if (input != null) {
            if (typeof input !== 'string') {
                input = -input;
            }

            this.utcOffset(input, keepLocalTime);

            return this;
        } else {
            return -this.utcOffset();
        }
    }

    function setOffsetToUTC(keepLocalTime) {
        return this.utcOffset(0, keepLocalTime);
    }

    function setOffsetToLocal(keepLocalTime) {
        if (this._isUTC) {
            this.utcOffset(0, keepLocalTime);
            this._isUTC = false;

            if (keepLocalTime) {
                this.subtract(getDateOffset(this), 'm');
            }
        }
        return this;
    }

    function setOffsetToParsedOffset() {
        if (this._tzm != null) {
            this.utcOffset(this._tzm, false, true);
        } else if (typeof this._i === 'string') {
            var tZone = offsetFromString(matchOffset, this._i);
            if (tZone != null) {
                this.utcOffset(tZone);
            } else {
                this.utcOffset(0, true);
            }
        }
        return this;
    }

    function hasAlignedHourOffset(input) {
        if (!this.isValid()) {
            return false;
        }
        input = input ? createLocal(input).utcOffset() : 0;

        return (this.utcOffset() - input) % 60 === 0;
    }

    function isDaylightSavingTime() {
        return (
            this.utcOffset() > this.clone().month(0).utcOffset() ||
            this.utcOffset() > this.clone().month(5).utcOffset()
        );
    }

    function isDaylightSavingTimeShifted() {
        if (!isUndefined(this._isDSTShifted)) {
            return this._isDSTShifted;
        }

        var c = {},
            other;

        copyConfig(c, this);
        c = prepareConfig(c);

        if (c._a) {
            other = c._isUTC ? createUTC(c._a) : createLocal(c._a);
            this._isDSTShifted =
                this.isValid() && compareArrays(c._a, other.toArray()) > 0;
        } else {
            this._isDSTShifted = false;
        }

        return this._isDSTShifted;
    }

    function isLocal() {
        return this.isValid() ? !this._isUTC : false;
    }

    function isUtcOffset() {
        return this.isValid() ? this._isUTC : false;
    }

    function isUtc() {
        return this.isValid() ? this._isUTC && this._offset === 0 : false;
    }

    // ASP.NET json date format regex
    var aspNetRegex = /^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/,
        // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
        // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
        // and further modified to allow for strings containing both week and day
        isoRegex =
            /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;

    function createDuration(input, key) {
        var duration = input,
            // matching against regexp is expensive, do it on demand
            match = null,
            sign,
            ret,
            diffRes;

        if (isDuration(input)) {
            duration = {
                ms: input._milliseconds,
                d: input._days,
                M: input._months,
            };
        } else if (isNumber(input) || !isNaN(+input)) {
            duration = {};
            if (key) {
                duration[key] = +input;
            } else {
                duration.milliseconds = +input;
            }
        } else if ((match = aspNetRegex.exec(input))) {
            sign = match[1] === '-' ? -1 : 1;
            duration = {
                y: 0,
                d: toInt(match[DATE]) * sign,
                h: toInt(match[HOUR]) * sign,
                m: toInt(match[MINUTE]) * sign,
                s: toInt(match[SECOND]) * sign,
                ms: toInt(absRound(match[MILLISECOND] * 1000)) * sign, // the millisecond decimal point is included in the match
            };
        } else if ((match = isoRegex.exec(input))) {
            sign = match[1] === '-' ? -1 : 1;
            duration = {
                y: parseIso(match[2], sign),
                M: parseIso(match[3], sign),
                w: parseIso(match[4], sign),
                d: parseIso(match[5], sign),
                h: parseIso(match[6], sign),
                m: parseIso(match[7], sign),
                s: parseIso(match[8], sign),
            };
        } else if (duration == null) {
            // checks for null or undefined
            duration = {};
        } else if (
            typeof duration === 'object' &&
            ('from' in duration || 'to' in duration)
        ) {
            diffRes = momentsDifference(
                createLocal(duration.from),
                createLocal(duration.to)
            );

            duration = {};
            duration.ms = diffRes.milliseconds;
            duration.M = diffRes.months;
        }

        ret = new Duration(duration);

        if (isDuration(input) && hasOwnProp(input, '_locale')) {
            ret._locale = input._locale;
        }

        if (isDuration(input) && hasOwnProp(input, '_isValid')) {
            ret._isValid = input._isValid;
        }

        return ret;
    }

    createDuration.fn = Duration.prototype;
    createDuration.invalid = createInvalid$1;

    function parseIso(inp, sign) {
        // We'd normally use ~~inp for this, but unfortunately it also
        // converts floats to ints.
        // inp may be undefined, so careful calling replace on it.
        var res = inp && parseFloat(inp.replace(',', '.'));
        // apply sign while we're at it
        return (isNaN(res) ? 0 : res) * sign;
    }

    function positiveMomentsDifference(base, other) {
        var res = {};

        res.months =
            other.month() - base.month() + (other.year() - base.year()) * 12;
        if (base.clone().add(res.months, 'M').isAfter(other)) {
            --res.months;
        }

        res.milliseconds = +other - +base.clone().add(res.months, 'M');

        return res;
    }

    function momentsDifference(base, other) {
        var res;
        if (!(base.isValid() && other.isValid())) {
            return { milliseconds: 0, months: 0 };
        }

        other = cloneWithOffset(other, base);
        if (base.isBefore(other)) {
            res = positiveMomentsDifference(base, other);
        } else {
            res = positiveMomentsDifference(other, base);
            res.milliseconds = -res.milliseconds;
            res.months = -res.months;
        }

        return res;
    }

    // TODO: remove 'name' arg after deprecation is removed
    function createAdder(direction, name) {
        return function (val, period) {
            var dur, tmp;
            //invert the arguments, but complain about it
            if (period !== null && !isNaN(+period)) {
                deprecateSimple(
                    name,
                    'moment().' +
                        name +
                        '(period, number) is deprecated. Please use moment().' +
                        name +
                        '(number, period). ' +
                        'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'
                );
                tmp = val;
                val = period;
                period = tmp;
            }

            dur = createDuration(val, period);
            addSubtract(this, dur, direction);
            return this;
        };
    }

    function addSubtract(mom, duration, isAdding, updateOffset) {
        var milliseconds = duration._milliseconds,
            days = absRound(duration._days),
            months = absRound(duration._months);

        if (!mom.isValid()) {
            // No op
            return;
        }

        updateOffset = updateOffset == null ? true : updateOffset;

        if (months) {
            setMonth(mom, get(mom, 'Month') + months * isAdding);
        }
        if (days) {
            set$1(mom, 'Date', get(mom, 'Date') + days * isAdding);
        }
        if (milliseconds) {
            mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding);
        }
        if (updateOffset) {
            hooks.updateOffset(mom, days || months);
        }
    }

    var add = createAdder(1, 'add'),
        subtract = createAdder(-1, 'subtract');

    function isString(input) {
        return typeof input === 'string' || input instanceof String;
    }

    // type MomentInput = Moment | Date | string | number | (number | string)[] | MomentInputObject | void; // null | undefined
    function isMomentInput(input) {
        return (
            isMoment(input) ||
            isDate(input) ||
            isString(input) ||
            isNumber(input) ||
            isNumberOrStringArray(input) ||
            isMomentInputObject(input) ||
            input === null ||
            input === undefined
        );
    }

    function isMomentInputObject(input) {
        var objectTest = isObject(input) && !isObjectEmpty(input),
            propertyTest = false,
            properties = [
                'years',
                'year',
                'y',
                'months',
                'month',
                'M',
                'days',
                'day',
                'd',
                'dates',
                'date',
                'D',
                'hours',
                'hour',
                'h',
                'minutes',
                'minute',
                'm',
                'seconds',
                'second',
                's',
                'milliseconds',
                'millisecond',
                'ms',
            ],
            i,
            property,
            propertyLen = properties.length;

        for (i = 0; i < propertyLen; i += 1) {
            property = properties[i];
            propertyTest = propertyTest || hasOwnProp(input, property);
        }

        return objectTest && propertyTest;
    }

    function isNumberOrStringArray(input) {
        var arrayTest = isArray(input),
            dataTypeTest = false;
        if (arrayTest) {
            dataTypeTest =
                input.filter(function (item) {
                    return !isNumber(item) && isString(input);
                }).length === 0;
        }
        return arrayTest && dataTypeTest;
    }

    function isCalendarSpec(input) {
        var objectTest = isObject(input) && !isObjectEmpty(input),
            propertyTest = false,
            properties = [
                'sameDay',
                'nextDay',
                'lastDay',
                'nextWeek',
                'lastWeek',
                'sameElse',
            ],
            i,
            property;

        for (i = 0; i < properties.length; i += 1) {
            property = properties[i];
            propertyTest = propertyTest || hasOwnProp(input, property);
        }

        return objectTest && propertyTest;
    }

    function getCalendarFormat(myMoment, now) {
        var diff = myMoment.diff(now, 'days', true);
        return diff < -6
            ? 'sameElse'
            : diff < -1
            ? 'lastWeek'
            : diff < 0
            ? 'lastDay'
            : diff < 1
            ? 'sameDay'
            : diff < 2
            ? 'nextDay'
            : diff < 7
            ? 'nextWeek'
            : 'sameElse';
    }

    function calendar$1(time, formats) {
        // Support for single parameter, formats only overload to the calendar function
        if (arguments.length === 1) {
            if (!arguments[0]) {
                time = undefined;
                formats = undefined;
            } else if (isMomentInput(arguments[0])) {
                time = arguments[0];
                formats = undefined;
            } else if (isCalendarSpec(arguments[0])) {
                formats = arguments[0];
                time = undefined;
            }
        }
        // We want to compare the start of today, vs this.
        // Getting start-of-today depends on whether we're local/utc/offset or not.
        var now = time || createLocal(),
            sod = cloneWithOffset(now, this).startOf('day'),
            format = hooks.calendarFormat(this, sod) || 'sameElse',
            output =
                formats &&
                (isFunction(formats[format])
                    ? formats[format].call(this, now)
                    : formats[format]);

        return this.format(
            output || this.localeData().calendar(format, this, createLocal(now))
        );
    }

    function clone() {
        return new Moment(this);
    }

    function isAfter(input, units) {
        var localInput = isMoment(input) ? input : createLocal(input);
        if (!(this.isValid() && localInput.isValid())) {
            return false;
        }
        units = normalizeUnits(units) || 'millisecond';
        if (units === 'millisecond') {
            return this.valueOf() > localInput.valueOf();
        } else {
            return localInput.valueOf() < this.clone().startOf(units).valueOf();
        }
    }

    function isBefore(input, units) {
        var localInput = isMoment(input) ? input : createLocal(input);
        if (!(this.isValid() && localInput.isValid())) {
            return false;
        }
        units = normalizeUnits(units) || 'millisecond';
        if (units === 'millisecond') {
            return this.valueOf() < localInput.valueOf();
        } else {
            return this.clone().endOf(units).valueOf() < localInput.valueOf();
        }
    }

    function isBetween(from, to, units, inclusivity) {
        var localFrom = isMoment(from) ? from : createLocal(from),
            localTo = isMoment(to) ? to : createLocal(to);
        if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) {
            return false;
        }
        inclusivity = inclusivity || '()';
        return (
            (inclusivity[0] === '('
                ? this.isAfter(localFrom, units)
                : !this.isBefore(localFrom, units)) &&
            (inclusivity[1] === ')'
                ? this.isBefore(localTo, units)
                : !this.isAfter(localTo, units))
        );
    }

    function isSame(input, units) {
        var localInput = isMoment(input) ? input : createLocal(input),
            inputMs;
        if (!(this.isValid() && localInput.isValid())) {
            return false;
        }
        units = normalizeUnits(units) || 'millisecond';
        if (units === 'millisecond') {
            return this.valueOf() === localInput.valueOf();
        } else {
            inputMs = localInput.valueOf();
            return (
                this.clone().startOf(units).valueOf() <= inputMs &&
                inputMs <= this.clone().endOf(units).valueOf()
            );
        }
    }

    function isSameOrAfter(input, units) {
        return this.isSame(input, units) || this.isAfter(input, units);
    }

    function isSameOrBefore(input, units) {
        return this.isSame(input, units) || this.isBefore(input, units);
    }

    function diff(input, units, asFloat) {
        var that, zoneDelta, output;

        if (!this.isValid()) {
            return NaN;
        }

        that = cloneWithOffset(input, this);

        if (!that.isValid()) {
            return NaN;
        }

        zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4;

        units = normalizeUnits(units);

        switch (units) {
            case 'year':
                output = monthDiff(this, that) / 12;
                break;
            case 'month':
                output = monthDiff(this, that);
                break;
            case 'quarter':
                output = monthDiff(this, that) / 3;
                break;
            case 'second':
                output = (this - that) / 1e3;
                break; // 1000
            case 'minute':
                output = (this - that) / 6e4;
                break; // 1000 * 60
            case 'hour':
                output = (this - that) / 36e5;
                break; // 1000 * 60 * 60
            case 'day':
                output = (this - that - zoneDelta) / 864e5;
                break; // 1000 * 60 * 60 * 24, negate dst
            case 'week':
                output = (this - that - zoneDelta) / 6048e5;
                break; // 1000 * 60 * 60 * 24 * 7, negate dst
            default:
                output = this - that;
        }

        return asFloat ? output : absFloor(output);
    }

    function monthDiff(a, b) {
        if (a.date() < b.date()) {
            // end-of-month calculations work correct when the start month has more
            // days than the end month.
            return -monthDiff(b, a);
        }
        // difference in months
        var wholeMonthDiff = (b.year() - a.year()) * 12 + (b.month() - a.month()),
            // b is in (anchor - 1 month, anchor + 1 month)
            anchor = a.clone().add(wholeMonthDiff, 'months'),
            anchor2,
            adjust;

        if (b - anchor < 0) {
            anchor2 = a.clone().add(wholeMonthDiff - 1, 'months');
            // linear across the month
            adjust = (b - anchor) / (anchor - anchor2);
        } else {
            anchor2 = a.clone().add(wholeMonthDiff + 1, 'months');
            // linear across the month
            adjust = (b - anchor) / (anchor2 - anchor);
        }

        //check for negative zero, return zero if negative zero
        return -(wholeMonthDiff + adjust) || 0;
    }

    hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ';
    hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]';

    function toString() {
        return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
    }

    function toISOString(keepOffset) {
        if (!this.isValid()) {
            return null;
        }
        var utc = keepOffset !== true,
            m = utc ? this.clone().utc() : this;
        if (m.year() < 0 || m.year() > 9999) {
            return formatMoment(
                m,
                utc
                    ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]'
                    : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ'
            );
        }
        if (isFunction(Date.prototype.toISOString)) {
            // native implementation is ~50x faster, use it when we can
            if (utc) {
                return this.toDate().toISOString();
            } else {
                return new Date(this.valueOf() + this.utcOffset() * 60 * 1000)
                    .toISOString()
                    .replace('Z', formatMoment(m, 'Z'));
            }
        }
        return formatMoment(
            m,
            utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ'
        );
    }

    /**
     * Return a human readable representation of a moment that can
     * also be evaluated to get a new moment which is the same
     *
     * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects
     */
    function inspect() {
        if (!this.isValid()) {
            return 'moment.invalid(/* ' + this._i + ' */)';
        }
        var func = 'moment',
            zone = '',
            prefix,
            year,
            datetime,
            suffix;
        if (!this.isLocal()) {
            func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone';
            zone = 'Z';
        }
        prefix = '[' + func + '("]';
        year = 0 <= this.year() && this.year() <= 9999 ? 'YYYY' : 'YYYYYY';
        datetime = '-MM-DD[T]HH:mm:ss.SSS';
        suffix = zone + '[")]';

        return this.format(prefix + year + datetime + suffix);
    }

    function format(inputString) {
        if (!inputString) {
            inputString = this.isUtc()
                ? hooks.defaultFormatUtc
                : hooks.defaultFormat;
        }
        var output = formatMoment(this, inputString);
        return this.localeData().postformat(output);
    }

    function from(time, withoutSuffix) {
        if (
            this.isValid() &&
            ((isMoment(time) && time.isValid()) || createLocal(time).isValid())
        ) {
            return createDuration({ to: this, from: time })
                .locale(this.locale())
                .humanize(!withoutSuffix);
        } else {
            return this.localeData().invalidDate();
        }
    }

    function fromNow(withoutSuffix) {
        return this.from(createLocal(), withoutSuffix);
    }

    function to(time, withoutSuffix) {
        if (
            this.isValid() &&
            ((isMoment(time) && time.isValid()) || createLocal(time).isValid())
        ) {
            return createDuration({ from: this, to: time })
                .locale(this.locale())
                .humanize(!withoutSuffix);
        } else {
            return this.localeData().invalidDate();
        }
    }

    function toNow(withoutSuffix) {
        return this.to(createLocal(), withoutSuffix);
    }

    // If passed a locale key, it will set the locale for this
    // instance.  Otherwise, it will return the locale configuration
    // variables for this instance.
    function locale(key) {
        var newLocaleData;

        if (key === undefined) {
            return this._locale._abbr;
        } else {
            newLocaleData = getLocale(key);
            if (newLocaleData != null) {
                this._locale = newLocaleData;
            }
            return this;
        }
    }

    var lang = deprecate(
        'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.',
        function (key) {
            if (key === undefined) {
                return this.localeData();
            } else {
                return this.locale(key);
            }
        }
    );

    function localeData() {
        return this._locale;
    }

    var MS_PER_SECOND = 1000,
        MS_PER_MINUTE = 60 * MS_PER_SECOND,
        MS_PER_HOUR = 60 * MS_PER_MINUTE,
        MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR;

    // actual modulo - handles negative numbers (for dates before 1970):
    function mod$1(dividend, divisor) {
        return ((dividend % divisor) + divisor) % divisor;
    }

    function localStartOfDate(y, m, d) {
        // the date constructor remaps years 0-99 to 1900-1999
        if (y < 100 && y >= 0) {
            // preserve leap years using a full 400 year cycle, then reset
            return new Date(y + 400, m, d) - MS_PER_400_YEARS;
        } else {
            return new Date(y, m, d).valueOf();
        }
    }

    function utcStartOfDate(y, m, d) {
        // Date.UTC remaps years 0-99 to 1900-1999
        if (y < 100 && y >= 0) {
            // preserve leap years using a full 400 year cycle, then reset
            return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS;
        } else {
            return Date.UTC(y, m, d);
        }
    }

    function startOf(units) {
        var time, startOfDate;
        units = normalizeUnits(units);
        if (units === undefined || units === 'millisecond' || !this.isValid()) {
            return this;
        }

        startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;

        switch (units) {
            case 'year':
                time = startOfDate(this.year(), 0, 1);
                break;
            case 'quarter':
                time = startOfDate(
                    this.year(),
                    this.month() - (this.month() % 3),
                    1
                );
                break;
            case 'month':
                time = startOfDate(this.year(), this.month(), 1);
                break;
            case 'week':
                time = startOfDate(
                    this.year(),
                    this.month(),
                    this.date() - this.weekday()
                );
                break;
            case 'isoWeek':
                time = startOfDate(
                    this.year(),
                    this.month(),
                    this.date() - (this.isoWeekday() - 1)
                );
                break;
            case 'day':
            case 'date':
                time = startOfDate(this.year(), this.month(), this.date());
                break;
            case 'hour':
                time = this._d.valueOf();
                time -= mod$1(
                    time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),
                    MS_PER_HOUR
                );
                break;
            case 'minute':
                time = this._d.valueOf();
                time -= mod$1(time, MS_PER_MINUTE);
                break;
            case 'second':
                time = this._d.valueOf();
                time -= mod$1(time, MS_PER_SECOND);
                break;
        }

        this._d.setTime(time);
        hooks.updateOffset(this, true);
        return this;
    }

    function endOf(units) {
        var time, startOfDate;
        units = normalizeUnits(units);
        if (units === undefined || units === 'millisecond' || !this.isValid()) {
            return this;
        }

        startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate;

        switch (units) {
            case 'year':
                time = startOfDate(this.year() + 1, 0, 1) - 1;
                break;
            case 'quarter':
                time =
                    startOfDate(
                        this.year(),
                        this.month() - (this.month() % 3) + 3,
                        1
                    ) - 1;
                break;
            case 'month':
                time = startOfDate(this.year(), this.month() + 1, 1) - 1;
                break;
            case 'week':
                time =
                    startOfDate(
                        this.year(),
                        this.month(),
                        this.date() - this.weekday() + 7
                    ) - 1;
                break;
            case 'isoWeek':
                time =
                    startOfDate(
                        this.year(),
                        this.month(),
                        this.date() - (this.isoWeekday() - 1) + 7
                    ) - 1;
                break;
            case 'day':
            case 'date':
                time = startOfDate(this.year(), this.month(), this.date() + 1) - 1;
                break;
            case 'hour':
                time = this._d.valueOf();
                time +=
                    MS_PER_HOUR -
                    mod$1(
                        time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE),
                        MS_PER_HOUR
                    ) -
                    1;
                break;
            case 'minute':
                time = this._d.valueOf();
                time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1;
                break;
            case 'second':
                time = this._d.valueOf();
                time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1;
                break;
        }

        this._d.setTime(time);
        hooks.updateOffset(this, true);
        return this;
    }

    function valueOf() {
        return this._d.valueOf() - (this._offset || 0) * 60000;
    }

    function unix() {
        return Math.floor(this.valueOf() / 1000);
    }

    function toDate() {
        return new Date(this.valueOf());
    }

    function toArray() {
        var m = this;
        return [
            m.year(),
            m.month(),
            m.date(),
            m.hour(),
            m.minute(),
            m.second(),
            m.millisecond(),
        ];
    }

    function toObject() {
        var m = this;
        return {
            years: m.year(),
            months: m.month(),
            date: m.date(),
            hours: m.hours(),
            minutes: m.minutes(),
            seconds: m.seconds(),
            milliseconds: m.milliseconds(),
        };
    }

    function toJSON() {
        // new Date(NaN).toJSON() === null
        return this.isValid() ? this.toISOString() : null;
    }

    function isValid$2() {
        return isValid(this);
    }

    function parsingFlags() {
        return extend({}, getParsingFlags(this));
    }

    function invalidAt() {
        return getParsingFlags(this).overflow;
    }

    function creationData() {
        return {
            input: this._i,
            format: this._f,
            locale: this._locale,
            isUTC: this._isUTC,
            strict: this._strict,
        };
    }

    addFormatToken('N', 0, 0, 'eraAbbr');
    addFormatToken('NN', 0, 0, 'eraAbbr');
    addFormatToken('NNN', 0, 0, 'eraAbbr');
    addFormatToken('NNNN', 0, 0, 'eraName');
    addFormatToken('NNNNN', 0, 0, 'eraNarrow');

    addFormatToken('y', ['y', 1], 'yo', 'eraYear');
    addFormatToken('y', ['yy', 2], 0, 'eraYear');
    addFormatToken('y', ['yyy', 3], 0, 'eraYear');
    addFormatToken('y', ['yyyy', 4], 0, 'eraYear');

    addRegexToken('N', matchEraAbbr);
    addRegexToken('NN', matchEraAbbr);
    addRegexToken('NNN', matchEraAbbr);
    addRegexToken('NNNN', matchEraName);
    addRegexToken('NNNNN', matchEraNarrow);

    addParseToken(
        ['N', 'NN', 'NNN', 'NNNN', 'NNNNN'],
        function (input, array, config, token) {
            var era = config._locale.erasParse(input, token, config._strict);
            if (era) {
                getParsingFlags(config).era = era;
            } else {
                getParsingFlags(config).invalidEra = input;
            }
        }
    );

    addRegexToken('y', matchUnsigned);
    addRegexToken('yy', matchUnsigned);
    addRegexToken('yyy', matchUnsigned);
    addRegexToken('yyyy', matchUnsigned);
    addRegexToken('yo', matchEraYearOrdinal);

    addParseToken(['y', 'yy', 'yyy', 'yyyy'], YEAR);
    addParseToken(['yo'], function (input, array, config, token) {
        var match;
        if (config._locale._eraYearOrdinalRegex) {
            match = input.match(config._locale._eraYearOrdinalRegex);
        }

        if (config._locale.eraYearOrdinalParse) {
            array[YEAR] = config._locale.eraYearOrdinalParse(input, match);
        } else {
            array[YEAR] = parseInt(input, 10);
        }
    });

    function localeEras(m, format) {
        var i,
            l,
            date,
            eras = this._eras || getLocale('en')._eras;
        for (i = 0, l = eras.length; i < l; ++i) {
            switch (typeof eras[i].since) {
                case 'string':
                    // truncate time
                    date = hooks(eras[i].since).startOf('day');
                    eras[i].since = date.valueOf();
                    break;
            }

            switch (typeof eras[i].until) {
                case 'undefined':
                    eras[i].until = +Infinity;
                    break;
                case 'string':
                    // truncate time
                    date = hooks(eras[i].until).startOf('day').valueOf();
                    eras[i].until = date.valueOf();
                    break;
            }
        }
        return eras;
    }

    function localeErasParse(eraName, format, strict) {
        var i,
            l,
            eras = this.eras(),
            name,
            abbr,
            narrow;
        eraName = eraName.toUpperCase();

        for (i = 0, l = eras.length; i < l; ++i) {
            name = eras[i].name.toUpperCase();
            abbr = eras[i].abbr.toUpperCase();
            narrow = eras[i].narrow.toUpperCase();

            if (strict) {
                switch (format) {
                    case 'N':
                    case 'NN':
                    case 'NNN':
                        if (abbr === eraName) {
                            return eras[i];
                        }
                        break;

                    case 'NNNN':
                        if (name === eraName) {
                            return eras[i];
                        }
                        break;

                    case 'NNNNN':
                        if (narrow === eraName) {
                            return eras[i];
                        }
                        break;
                }
            } else if ([name, abbr, narrow].indexOf(eraName) >= 0) {
                return eras[i];
            }
        }
    }

    function localeErasConvertYear(era, year) {
        var dir = era.since <= era.until ? +1 : -1;
        if (year === undefined) {
            return hooks(era.since).year();
        } else {
            return hooks(era.since).year() + (year - era.offset) * dir;
        }
    }

    function getEraName() {
        var i,
            l,
            val,
            eras = this.localeData().eras();
        for (i = 0, l = eras.length; i < l; ++i) {
            // truncate time
            val = this.clone().startOf('day').valueOf();

            if (eras[i].since <= val && val <= eras[i].until) {
                return eras[i].name;
            }
            if (eras[i].until <= val && val <= eras[i].since) {
                return eras[i].name;
            }
        }

        return '';
    }

    function getEraNarrow() {
        var i,
            l,
            val,
            eras = this.localeData().eras();
        for (i = 0, l = eras.length; i < l; ++i) {
            // truncate time
            val = this.clone().startOf('day').valueOf();

            if (eras[i].since <= val && val <= eras[i].until) {
                return eras[i].narrow;
            }
            if (eras[i].until <= val && val <= eras[i].since) {
                return eras[i].narrow;
            }
        }

        return '';
    }

    function getEraAbbr() {
        var i,
            l,
            val,
            eras = this.localeData().eras();
        for (i = 0, l = eras.length; i < l; ++i) {
            // truncate time
            val = this.clone().startOf('day').valueOf();

            if (eras[i].since <= val && val <= eras[i].until) {
                return eras[i].abbr;
            }
            if (eras[i].until <= val && val <= eras[i].since) {
                return eras[i].abbr;
            }
        }

        return '';
    }

    function getEraYear() {
        var i,
            l,
            dir,
            val,
            eras = this.localeData().eras();
        for (i = 0, l = eras.length; i < l; ++i) {
            dir = eras[i].since <= eras[i].until ? +1 : -1;

            // truncate time
            val = this.clone().startOf('day').valueOf();

            if (
                (eras[i].since <= val && val <= eras[i].until) ||
                (eras[i].until <= val && val <= eras[i].since)
            ) {
                return (
                    (this.year() - hooks(eras[i].since).year()) * dir +
                    eras[i].offset
                );
            }
        }

        return this.year();
    }

    function erasNameRegex(isStrict) {
        if (!hasOwnProp(this, '_erasNameRegex')) {
            computeErasParse.call(this);
        }
        return isStrict ? this._erasNameRegex : this._erasRegex;
    }

    function erasAbbrRegex(isStrict) {
        if (!hasOwnProp(this, '_erasAbbrRegex')) {
            computeErasParse.call(this);
        }
        return isStrict ? this._erasAbbrRegex : this._erasRegex;
    }

    function erasNarrowRegex(isStrict) {
        if (!hasOwnProp(this, '_erasNarrowRegex')) {
            computeErasParse.call(this);
        }
        return isStrict ? this._erasNarrowRegex : this._erasRegex;
    }

    function matchEraAbbr(isStrict, locale) {
        return locale.erasAbbrRegex(isStrict);
    }

    function matchEraName(isStrict, locale) {
        return locale.erasNameRegex(isStrict);
    }

    function matchEraNarrow(isStrict, locale) {
        return locale.erasNarrowRegex(isStrict);
    }

    function matchEraYearOrdinal(isStrict, locale) {
        return locale._eraYearOrdinalRegex || matchUnsigned;
    }

    function computeErasParse() {
        var abbrPieces = [],
            namePieces = [],
            narrowPieces = [],
            mixedPieces = [],
            i,
            l,
            eras = this.eras();

        for (i = 0, l = eras.length; i < l; ++i) {
            namePieces.push(regexEscape(eras[i].name));
            abbrPieces.push(regexEscape(eras[i].abbr));
            narrowPieces.push(regexEscape(eras[i].narrow));

            mixedPieces.push(regexEscape(eras[i].name));
            mixedPieces.push(regexEscape(eras[i].abbr));
            mixedPieces.push(regexEscape(eras[i].narrow));
        }

        this._erasRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i');
        this._erasNameRegex = new RegExp('^(' + namePieces.join('|') + ')', 'i');
        this._erasAbbrRegex = new RegExp('^(' + abbrPieces.join('|') + ')', 'i');
        this._erasNarrowRegex = new RegExp(
            '^(' + narrowPieces.join('|') + ')',
            'i'
        );
    }

    // FORMATTING

    addFormatToken(0, ['gg', 2], 0, function () {
        return this.weekYear() % 100;
    });

    addFormatToken(0, ['GG', 2], 0, function () {
        return this.isoWeekYear() % 100;
    });

    function addWeekYearFormatToken(token, getter) {
        addFormatToken(0, [token, token.length], 0, getter);
    }

    addWeekYearFormatToken('gggg', 'weekYear');
    addWeekYearFormatToken('ggggg', 'weekYear');
    addWeekYearFormatToken('GGGG', 'isoWeekYear');
    addWeekYearFormatToken('GGGGG', 'isoWeekYear');

    // ALIASES

    addUnitAlias('weekYear', 'gg');
    addUnitAlias('isoWeekYear', 'GG');

    // PRIORITY

    addUnitPriority('weekYear', 1);
    addUnitPriority('isoWeekYear', 1);

    // PARSING

    addRegexToken('G', matchSigned);
    addRegexToken('g', matchSigned);
    addRegexToken('GG', match1to2, match2);
    addRegexToken('gg', match1to2, match2);
    addRegexToken('GGGG', match1to4, match4);
    addRegexToken('gggg', match1to4, match4);
    addRegexToken('GGGGG', match1to6, match6);
    addRegexToken('ggggg', match1to6, match6);

    addWeekParseToken(
        ['gggg', 'ggggg', 'GGGG', 'GGGGG'],
        function (input, week, config, token) {
            week[token.substr(0, 2)] = toInt(input);
        }
    );

    addWeekParseToken(['gg', 'GG'], function (input, week, config, token) {
        week[token] = hooks.parseTwoDigitYear(input);
    });

    // MOMENTS

    function getSetWeekYear(input) {
        return getSetWeekYearHelper.call(
            this,
            input,
            this.week(),
            this.weekday(),
            this.localeData()._week.dow,
            this.localeData()._week.doy
        );
    }

    function getSetISOWeekYear(input) {
        return getSetWeekYearHelper.call(
            this,
            input,
            this.isoWeek(),
            this.isoWeekday(),
            1,
            4
        );
    }

    function getISOWeeksInYear() {
        return weeksInYear(this.year(), 1, 4);
    }

    function getISOWeeksInISOWeekYear() {
        return weeksInYear(this.isoWeekYear(), 1, 4);
    }

    function getWeeksInYear() {
        var weekInfo = this.localeData()._week;
        return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
    }

    function getWeeksInWeekYear() {
        var weekInfo = this.localeData()._week;
        return weeksInYear(this.weekYear(), weekInfo.dow, weekInfo.doy);
    }

    function getSetWeekYearHelper(input, week, weekday, dow, doy) {
        var weeksTarget;
        if (input == null) {
            return weekOfYear(this, dow, doy).year;
        } else {
            weeksTarget = weeksInYear(input, dow, doy);
            if (week > weeksTarget) {
                week = weeksTarget;
            }
            return setWeekAll.call(this, input, week, weekday, dow, doy);
        }
    }

    function setWeekAll(weekYear, week, weekday, dow, doy) {
        var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy),
            date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear);

        this.year(date.getUTCFullYear());
        this.month(date.getUTCMonth());
        this.date(date.getUTCDate());
        return this;
    }

    // FORMATTING

    addFormatToken('Q', 0, 'Qo', 'quarter');

    // ALIASES

    addUnitAlias('quarter', 'Q');

    // PRIORITY

    addUnitPriority('quarter', 7);

    // PARSING

    addRegexToken('Q', match1);
    addParseToken('Q', function (input, array) {
        array[MONTH] = (toInt(input) - 1) * 3;
    });

    // MOMENTS

    function getSetQuarter(input) {
        return input == null
            ? Math.ceil((this.month() + 1) / 3)
            : this.month((input - 1) * 3 + (this.month() % 3));
    }

    // FORMATTING

    addFormatToken('D', ['DD', 2], 'Do', 'date');

    // ALIASES

    addUnitAlias('date', 'D');

    // PRIORITY
    addUnitPriority('date', 9);

    // PARSING

    addRegexToken('D', match1to2);
    addRegexToken('DD', match1to2, match2);
    addRegexToken('Do', function (isStrict, locale) {
        // TODO: Remove "ordinalParse" fallback in next major release.
        return isStrict
            ? locale._dayOfMonthOrdinalParse || locale._ordinalParse
            : locale._dayOfMonthOrdinalParseLenient;
    });

    addParseToken(['D', 'DD'], DATE);
    addParseToken('Do', function (input, array) {
        array[DATE] = toInt(input.match(match1to2)[0]);
    });

    // MOMENTS

    var getSetDayOfMonth = makeGetSet('Date', true);

    // FORMATTING

    addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear');

    // ALIASES

    addUnitAlias('dayOfYear', 'DDD');

    // PRIORITY
    addUnitPriority('dayOfYear', 4);

    // PARSING

    addRegexToken('DDD', match1to3);
    addRegexToken('DDDD', match3);
    addParseToken(['DDD', 'DDDD'], function (input, array, config) {
        config._dayOfYear = toInt(input);
    });

    // HELPERS

    // MOMENTS

    function getSetDayOfYear(input) {
        var dayOfYear =
            Math.round(
                (this.clone().startOf('day') - this.clone().startOf('year')) / 864e5
            ) + 1;
        return input == null ? dayOfYear : this.add(input - dayOfYear, 'd');
    }

    // FORMATTING

    addFormatToken('m', ['mm', 2], 0, 'minute');

    // ALIASES

    addUnitAlias('minute', 'm');

    // PRIORITY

    addUnitPriority('minute', 14);

    // PARSING

    addRegexToken('m', match1to2);
    addRegexToken('mm', match1to2, match2);
    addParseToken(['m', 'mm'], MINUTE);

    // MOMENTS

    var getSetMinute = makeGetSet('Minutes', false);

    // FORMATTING

    addFormatToken('s', ['ss', 2], 0, 'second');

    // ALIASES

    addUnitAlias('second', 's');

    // PRIORITY

    addUnitPriority('second', 15);

    // PARSING

    addRegexToken('s', match1to2);
    addRegexToken('ss', match1to2, match2);
    addParseToken(['s', 'ss'], SECOND);

    // MOMENTS

    var getSetSecond = makeGetSet('Seconds', false);

    // FORMATTING

    addFormatToken('S', 0, 0, function () {
        return ~~(this.millisecond() / 100);
    });

    addFormatToken(0, ['SS', 2], 0, function () {
        return ~~(this.millisecond() / 10);
    });

    addFormatToken(0, ['SSS', 3], 0, 'millisecond');
    addFormatToken(0, ['SSSS', 4], 0, function () {
        return this.millisecond() * 10;
    });
    addFormatToken(0, ['SSSSS', 5], 0, function () {
        return this.millisecond() * 100;
    });
    addFormatToken(0, ['SSSSSS', 6], 0, function () {
        return this.millisecond() * 1000;
    });
    addFormatToken(0, ['SSSSSSS', 7], 0, function () {
        return this.millisecond() * 10000;
    });
    addFormatToken(0, ['SSSSSSSS', 8], 0, function () {
        return this.millisecond() * 100000;
    });
    addFormatToken(0, ['SSSSSSSSS', 9], 0, function () {
        return this.millisecond() * 1000000;
    });

    // ALIASES

    addUnitAlias('millisecond', 'ms');

    // PRIORITY

    addUnitPriority('millisecond', 16);

    // PARSING

    addRegexToken('S', match1to3, match1);
    addRegexToken('SS', match1to3, match2);
    addRegexToken('SSS', match1to3, match3);

    var token, getSetMillisecond;
    for (token = 'SSSS'; token.length <= 9; token += 'S') {
        addRegexToken(token, matchUnsigned);
    }

    function parseMs(input, array) {
        array[MILLISECOND] = toInt(('0.' + input) * 1000);
    }

    for (token = 'S'; token.length <= 9; token += 'S') {
        addParseToken(token, parseMs);
    }

    getSetMillisecond = makeGetSet('Milliseconds', false);

    // FORMATTING

    addFormatToken('z', 0, 0, 'zoneAbbr');
    addFormatToken('zz', 0, 0, 'zoneName');

    // MOMENTS

    function getZoneAbbr() {
        return this._isUTC ? 'UTC' : '';
    }

    function getZoneName() {
        return this._isUTC ? 'Coordinated Universal Time' : '';
    }

    var proto = Moment.prototype;

    proto.add = add;
    proto.calendar = calendar$1;
    proto.clone = clone;
    proto.diff = diff;
    proto.endOf = endOf;
    proto.format = format;
    proto.from = from;
    proto.fromNow = fromNow;
    proto.to = to;
    proto.toNow = toNow;
    proto.get = stringGet;
    proto.invalidAt = invalidAt;
    proto.isAfter = isAfter;
    proto.isBefore = isBefore;
    proto.isBetween = isBetween;
    proto.isSame = isSame;
    proto.isSameOrAfter = isSameOrAfter;
    proto.isSameOrBefore = isSameOrBefore;
    proto.isValid = isValid$2;
    proto.lang = lang;
    proto.locale = locale;
    proto.localeData = localeData;
    proto.max = prototypeMax;
    proto.min = prototypeMin;
    proto.parsingFlags = parsingFlags;
    proto.set = stringSet;
    proto.startOf = startOf;
    proto.subtract = subtract;
    proto.toArray = toArray;
    proto.toObject = toObject;
    proto.toDate = toDate;
    proto.toISOString = toISOString;
    proto.inspect = inspect;
    if (typeof Symbol !== 'undefined' && Symbol.for != null) {
        proto[Symbol.for('nodejs.util.inspect.custom')] = function () {
            return 'Moment<' + this.format() + '>';
        };
    }
    proto.toJSON = toJSON;
    proto.toString = toString;
    proto.unix = unix;
    proto.valueOf = valueOf;
    proto.creationData = creationData;
    proto.eraName = getEraName;
    proto.eraNarrow = getEraNarrow;
    proto.eraAbbr = getEraAbbr;
    proto.eraYear = getEraYear;
    proto.year = getSetYear;
    proto.isLeapYear = getIsLeapYear;
    proto.weekYear = getSetWeekYear;
    proto.isoWeekYear = getSetISOWeekYear;
    proto.quarter = proto.quarters = getSetQuarter;
    proto.month = getSetMonth;
    proto.daysInMonth = getDaysInMonth;
    proto.week = proto.weeks = getSetWeek;
    proto.isoWeek = proto.isoWeeks = getSetISOWeek;
    proto.weeksInYear = getWeeksInYear;
    proto.weeksInWeekYear = getWeeksInWeekYear;
    proto.isoWeeksInYear = getISOWeeksInYear;
    proto.isoWeeksInISOWeekYear = getISOWeeksInISOWeekYear;
    proto.date = getSetDayOfMonth;
    proto.day = proto.days = getSetDayOfWeek;
    proto.weekday = getSetLocaleDayOfWeek;
    proto.isoWeekday = getSetISODayOfWeek;
    proto.dayOfYear = getSetDayOfYear;
    proto.hour = proto.hours = getSetHour;
    proto.minute = proto.minutes = getSetMinute;
    proto.second = proto.seconds = getSetSecond;
    proto.millisecond = proto.milliseconds = getSetMillisecond;
    proto.utcOffset = getSetOffset;
    proto.utc = setOffsetToUTC;
    proto.local = setOffsetToLocal;
    proto.parseZone = setOffsetToParsedOffset;
    proto.hasAlignedHourOffset = hasAlignedHourOffset;
    proto.isDST = isDaylightSavingTime;
    proto.isLocal = isLocal;
    proto.isUtcOffset = isUtcOffset;
    proto.isUtc = isUtc;
    proto.isUTC = isUtc;
    proto.zoneAbbr = getZoneAbbr;
    proto.zoneName = getZoneName;
    proto.dates = deprecate(
        'dates accessor is deprecated. Use date instead.',
        getSetDayOfMonth
    );
    proto.months = deprecate(
        'months accessor is deprecated. Use month instead',
        getSetMonth
    );
    proto.years = deprecate(
        'years accessor is deprecated. Use year instead',
        getSetYear
    );
    proto.zone = deprecate(
        'moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/',
        getSetZone
    );
    proto.isDSTShifted = deprecate(
        'isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information',
        isDaylightSavingTimeShifted
    );

    function createUnix(input) {
        return createLocal(input * 1000);
    }

    function createInZone() {
        return createLocal.apply(null, arguments).parseZone();
    }

    function preParsePostFormat(string) {
        return string;
    }

    var proto$1 = Locale.prototype;

    proto$1.calendar = calendar;
    proto$1.longDateFormat = longDateFormat;
    proto$1.invalidDate = invalidDate;
    proto$1.ordinal = ordinal;
    proto$1.preparse = preParsePostFormat;
    proto$1.postformat = preParsePostFormat;
    proto$1.relativeTime = relativeTime;
    proto$1.pastFuture = pastFuture;
    proto$1.set = set;
    proto$1.eras = localeEras;
    proto$1.erasParse = localeErasParse;
    proto$1.erasConvertYear = localeErasConvertYear;
    proto$1.erasAbbrRegex = erasAbbrRegex;
    proto$1.erasNameRegex = erasNameRegex;
    proto$1.erasNarrowRegex = erasNarrowRegex;

    proto$1.months = localeMonths;
    proto$1.monthsShort = localeMonthsShort;
    proto$1.monthsParse = localeMonthsParse;
    proto$1.monthsRegex = monthsRegex;
    proto$1.monthsShortRegex = monthsShortRegex;
    proto$1.week = localeWeek;
    proto$1.firstDayOfYear = localeFirstDayOfYear;
    proto$1.firstDayOfWeek = localeFirstDayOfWeek;

    proto$1.weekdays = localeWeekdays;
    proto$1.weekdaysMin = localeWeekdaysMin;
    proto$1.weekdaysShort = localeWeekdaysShort;
    proto$1.weekdaysParse = localeWeekdaysParse;

    proto$1.weekdaysRegex = weekdaysRegex;
    proto$1.weekdaysShortRegex = weekdaysShortRegex;
    proto$1.weekdaysMinRegex = weekdaysMinRegex;

    proto$1.isPM = localeIsPM;
    proto$1.meridiem = localeMeridiem;

    function get$1(format, index, field, setter) {
        var locale = getLocale(),
            utc = createUTC().set(setter, index);
        return locale[field](utc, format);
    }

    function listMonthsImpl(format, index, field) {
        if (isNumber(format)) {
            index = format;
            format = undefined;
        }

        format = format || '';

        if (index != null) {
            return get$1(format, index, field, 'month');
        }

        var i,
            out = [];
        for (i = 0; i < 12; i++) {
            out[i] = get$1(format, i, field, 'month');
        }
        return out;
    }

    // ()
    // (5)
    // (fmt, 5)
    // (fmt)
    // (true)
    // (true, 5)
    // (true, fmt, 5)
    // (true, fmt)
    function listWeekdaysImpl(localeSorted, format, index, field) {
        if (typeof localeSorted === 'boolean') {
            if (isNumber(format)) {
                index = format;
                format = undefined;
            }

            format = format || '';
        } else {
            format = localeSorted;
            index = format;
            localeSorted = false;

            if (isNumber(format)) {
                index = format;
                format = undefined;
            }

            format = format || '';
        }

        var locale = getLocale(),
            shift = localeSorted ? locale._week.dow : 0,
            i,
            out = [];

        if (index != null) {
            return get$1(format, (index + shift) % 7, field, 'day');
        }

        for (i = 0; i < 7; i++) {
            out[i] = get$1(format, (i + shift) % 7, field, 'day');
        }
        return out;
    }

    function listMonths(format, index) {
        return listMonthsImpl(format, index, 'months');
    }

    function listMonthsShort(format, index) {
        return listMonthsImpl(format, index, 'monthsShort');
    }

    function listWeekdays(localeSorted, format, index) {
        return listWeekdaysImpl(localeSorted, format, index, 'weekdays');
    }

    function listWeekdaysShort(localeSorted, format, index) {
        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort');
    }

    function listWeekdaysMin(localeSorted, format, index) {
        return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin');
    }

    getSetGlobalLocale('en', {
        eras: [
            {
                since: '0001-01-01',
                until: +Infinity,
                offset: 1,
                name: 'Anno Domini',
                narrow: 'AD',
                abbr: 'AD',
            },
            {
                since: '0000-12-31',
                until: -Infinity,
                offset: 1,
                name: 'Before Christ',
                narrow: 'BC',
                abbr: 'BC',
            },
        ],
        dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/,
        ordinal: function (number) {
            var b = number % 10,
                output =
                    toInt((number % 100) / 10) === 1
                        ? 'th'
                        : b === 1
                        ? 'st'
                        : b === 2
                        ? 'nd'
                        : b === 3
                        ? 'rd'
                        : 'th';
            return number + output;
        },
    });

    // Side effect imports

    hooks.lang = deprecate(
        'moment.lang is deprecated. Use moment.locale instead.',
        getSetGlobalLocale
    );
    hooks.langData = deprecate(
        'moment.langData is deprecated. Use moment.localeData instead.',
        getLocale
    );

    var mathAbs = Math.abs;

    function abs() {
        var data = this._data;

        this._milliseconds = mathAbs(this._milliseconds);
        this._days = mathAbs(this._days);
        this._months = mathAbs(this._months);

        data.milliseconds = mathAbs(data.milliseconds);
        data.seconds = mathAbs(data.seconds);
        data.minutes = mathAbs(data.minutes);
        data.hours = mathAbs(data.hours);
        data.months = mathAbs(data.months);
        data.years = mathAbs(data.years);

        return this;
    }

    function addSubtract$1(duration, input, value, direction) {
        var other = createDuration(input, value);

        duration._milliseconds += direction * other._milliseconds;
        duration._days += direction * other._days;
        duration._months += direction * other._months;

        return duration._bubble();
    }

    // supports only 2.0-style add(1, 's') or add(duration)
    function add$1(input, value) {
        return addSubtract$1(this, input, value, 1);
    }

    // supports only 2.0-style subtract(1, 's') or subtract(duration)
    function subtract$1(input, value) {
        return addSubtract$1(this, input, value, -1);
    }

    function absCeil(number) {
        if (number < 0) {
            return Math.floor(number);
        } else {
            return Math.ceil(number);
        }
    }

    function bubble() {
        var milliseconds = this._milliseconds,
            days = this._days,
            months = this._months,
            data = this._data,
            seconds,
            minutes,
            hours,
            years,
            monthsFromDays;

        // if we have a mix of positive and negative values, bubble down first
        // check: https://github.com/moment/moment/issues/2166
        if (
            !(
                (milliseconds >= 0 && days >= 0 && months >= 0) ||
                (milliseconds <= 0 && days <= 0 && months <= 0)
            )
        ) {
            milliseconds += absCeil(monthsToDays(months) + days) * 864e5;
            days = 0;
            months = 0;
        }

        // The following code bubbles up values, see the tests for
        // examples of what that means.
        data.milliseconds = milliseconds % 1000;

        seconds = absFloor(milliseconds / 1000);
        data.seconds = seconds % 60;

        minutes = absFloor(seconds / 60);
        data.minutes = minutes % 60;

        hours = absFloor(minutes / 60);
        data.hours = hours % 24;

        days += absFloor(hours / 24);

        // convert days to months
        monthsFromDays = absFloor(daysToMonths(days));
        months += monthsFromDays;
        days -= absCeil(monthsToDays(monthsFromDays));

        // 12 months -> 1 year
        years = absFloor(months / 12);
        months %= 12;

        data.days = days;
        data.months = months;
        data.years = years;

        return this;
    }

    function daysToMonths(days) {
        // 400 years have 146097 days (taking into account leap year rules)
        // 400 years have 12 months === 4800
        return (days * 4800) / 146097;
    }

    function monthsToDays(months) {
        // the reverse of daysToMonths
        return (months * 146097) / 4800;
    }

    function as(units) {
        if (!this.isValid()) {
            return NaN;
        }
        var days,
            months,
            milliseconds = this._milliseconds;

        units = normalizeUnits(units);

        if (units === 'month' || units === 'quarter' || units === 'year') {
            days = this._days + milliseconds / 864e5;
            months = this._months + daysToMonths(days);
            switch (units) {
                case 'month':
                    return months;
                case 'quarter':
                    return months / 3;
                case 'year':
                    return months / 12;
            }
        } else {
            // handle milliseconds separately because of floating point math errors (issue #1867)
            days = this._days + Math.round(monthsToDays(this._months));
            switch (units) {
                case 'week':
                    return days / 7 + milliseconds / 6048e5;
                case 'day':
                    return days + milliseconds / 864e5;
                case 'hour':
                    return days * 24 + milliseconds / 36e5;
                case 'minute':
                    return days * 1440 + milliseconds / 6e4;
                case 'second':
                    return days * 86400 + milliseconds / 1000;
                // Math.floor prevents floating point math errors here
                case 'millisecond':
                    return Math.floor(days * 864e5) + milliseconds;
                default:
                    throw new Error('Unknown unit ' + units);
            }
        }
    }

    // TODO: Use this.as('ms')?
    function valueOf$1() {
        if (!this.isValid()) {
            return NaN;
        }
        return (
            this._milliseconds +
            this._days * 864e5 +
            (this._months % 12) * 2592e6 +
            toInt(this._months / 12) * 31536e6
        );
    }

    function makeAs(alias) {
        return function () {
            return this.as(alias);
        };
    }

    var asMilliseconds = makeAs('ms'),
        asSeconds = makeAs('s'),
        asMinutes = makeAs('m'),
        asHours = makeAs('h'),
        asDays = makeAs('d'),
        asWeeks = makeAs('w'),
        asMonths = makeAs('M'),
        asQuarters = makeAs('Q'),
        asYears = makeAs('y');

    function clone$1() {
        return createDuration(this);
    }

    function get$2(units) {
        units = normalizeUnits(units);
        return this.isValid() ? this[units + 's']() : NaN;
    }

    function makeGetter(name) {
        return function () {
            return this.isValid() ? this._data[name] : NaN;
        };
    }

    var milliseconds = makeGetter('milliseconds'),
        seconds = makeGetter('seconds'),
        minutes = makeGetter('minutes'),
        hours = makeGetter('hours'),
        days = makeGetter('days'),
        months = makeGetter('months'),
        years = makeGetter('years');

    function weeks() {
        return absFloor(this.days() / 7);
    }

    var round = Math.round,
        thresholds = {
            ss: 44, // a few seconds to seconds
            s: 45, // seconds to minute
            m: 45, // minutes to hour
            h: 22, // hours to day
            d: 26, // days to month/week
            w: null, // weeks to month
            M: 11, // months to year
        };

    // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
    function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
        return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
    }

    function relativeTime$1(posNegDuration, withoutSuffix, thresholds, locale) {
        var duration = createDuration(posNegDuration).abs(),
            seconds = round(duration.as('s')),
            minutes = round(duration.as('m')),
            hours = round(duration.as('h')),
            days = round(duration.as('d')),
            months = round(duration.as('M')),
            weeks = round(duration.as('w')),
            years = round(duration.as('y')),
            a =
                (seconds <= thresholds.ss && ['s', seconds]) ||
                (seconds < thresholds.s && ['ss', seconds]) ||
                (minutes <= 1 && ['m']) ||
                (minutes < thresholds.m && ['mm', minutes]) ||
                (hours <= 1 && ['h']) ||
                (hours < thresholds.h && ['hh', hours]) ||
                (days <= 1 && ['d']) ||
                (days < thresholds.d && ['dd', days]);

        if (thresholds.w != null) {
            a =
                a ||
                (weeks <= 1 && ['w']) ||
                (weeks < thresholds.w && ['ww', weeks]);
        }
        a = a ||
            (months <= 1 && ['M']) ||
            (months < thresholds.M && ['MM', months]) ||
            (years <= 1 && ['y']) || ['yy', years];

        a[2] = withoutSuffix;
        a[3] = +posNegDuration > 0;
        a[4] = locale;
        return substituteTimeAgo.apply(null, a);
    }

    // This function allows you to set the rounding function for relative time strings
    function getSetRelativeTimeRounding(roundingFunction) {
        if (roundingFunction === undefined) {
            return round;
        }
        if (typeof roundingFunction === 'function') {
            round = roundingFunction;
            return true;
        }
        return false;
    }

    // This function allows you to set a threshold for relative time strings
    function getSetRelativeTimeThreshold(threshold, limit) {
        if (thresholds[threshold] === undefined) {
            return false;
        }
        if (limit === undefined) {
            return thresholds[threshold];
        }
        thresholds[threshold] = limit;
        if (threshold === 's') {
            thresholds.ss = limit - 1;
        }
        return true;
    }

    function humanize(argWithSuffix, argThresholds) {
        if (!this.isValid()) {
            return this.localeData().invalidDate();
        }

        var withSuffix = false,
            th = thresholds,
            locale,
            output;

        if (typeof argWithSuffix === 'object') {
            argThresholds = argWithSuffix;
            argWithSuffix = false;
        }
        if (typeof argWithSuffix === 'boolean') {
            withSuffix = argWithSuffix;
        }
        if (typeof argThresholds === 'object') {
            th = Object.assign({}, thresholds, argThresholds);
            if (argThresholds.s != null && argThresholds.ss == null) {
                th.ss = argThresholds.s - 1;
            }
        }

        locale = this.localeData();
        output = relativeTime$1(this, !withSuffix, th, locale);

        if (withSuffix) {
            output = locale.pastFuture(+this, output);
        }

        return locale.postformat(output);
    }

    var abs$1 = Math.abs;

    function sign(x) {
        return (x > 0) - (x < 0) || +x;
    }

    function toISOString$1() {
        // for ISO strings we do not use the normal bubbling rules:
        //  * milliseconds bubble up until they become hours
        //  * days do not bubble at all
        //  * months bubble up until they become years
        // This is because there is no context-free conversion between hours and days
        // (think of clock changes)
        // and also not between days and months (28-31 days per month)
        if (!this.isValid()) {
            return this.localeData().invalidDate();
        }

        var seconds = abs$1(this._milliseconds) / 1000,
            days = abs$1(this._days),
            months = abs$1(this._months),
            minutes,
            hours,
            years,
            s,
            total = this.asSeconds(),
            totalSign,
            ymSign,
            daysSign,
            hmsSign;

        if (!total) {
            // this is the same as C#'s (Noda) and python (isodate)...
            // but not other JS (goog.date)
            return 'P0D';
        }

        // 3600 seconds -> 60 minutes -> 1 hour
        minutes = absFloor(seconds / 60);
        hours = absFloor(minutes / 60);
        seconds %= 60;
        minutes %= 60;

        // 12 months -> 1 year
        years = absFloor(months / 12);
        months %= 12;

        // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
        s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : '';

        totalSign = total < 0 ? '-' : '';
        ymSign = sign(this._months) !== sign(total) ? '-' : '';
        daysSign = sign(this._days) !== sign(total) ? '-' : '';
        hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : '';

        return (
            totalSign +
            'P' +
            (years ? ymSign + years + 'Y' : '') +
            (months ? ymSign + months + 'M' : '') +
            (days ? daysSign + days + 'D' : '') +
            (hours || minutes || seconds ? 'T' : '') +
            (hours ? hmsSign + hours + 'H' : '') +
            (minutes ? hmsSign + minutes + 'M' : '') +
            (seconds ? hmsSign + s + 'S' : '')
        );
    }

    var proto$2 = Duration.prototype;

    proto$2.isValid = isValid$1;
    proto$2.abs = abs;
    proto$2.add = add$1;
    proto$2.subtract = subtract$1;
    proto$2.as = as;
    proto$2.asMilliseconds = asMilliseconds;
    proto$2.asSeconds = asSeconds;
    proto$2.asMinutes = asMinutes;
    proto$2.asHours = asHours;
    proto$2.asDays = asDays;
    proto$2.asWeeks = asWeeks;
    proto$2.asMonths = asMonths;
    proto$2.asQuarters = asQuarters;
    proto$2.asYears = asYears;
    proto$2.valueOf = valueOf$1;
    proto$2._bubble = bubble;
    proto$2.clone = clone$1;
    proto$2.get = get$2;
    proto$2.milliseconds = milliseconds;
    proto$2.seconds = seconds;
    proto$2.minutes = minutes;
    proto$2.hours = hours;
    proto$2.days = days;
    proto$2.weeks = weeks;
    proto$2.months = months;
    proto$2.years = years;
    proto$2.humanize = humanize;
    proto$2.toISOString = toISOString$1;
    proto$2.toString = toISOString$1;
    proto$2.toJSON = toISOString$1;
    proto$2.locale = locale;
    proto$2.localeData = localeData;

    proto$2.toIsoString = deprecate(
        'toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)',
        toISOString$1
    );
    proto$2.lang = lang;

    // FORMATTING

    addFormatToken('X', 0, 0, 'unix');
    addFormatToken('x', 0, 0, 'valueOf');

    // PARSING

    addRegexToken('x', matchSigned);
    addRegexToken('X', matchTimestamp);
    addParseToken('X', function (input, array, config) {
        config._d = new Date(parseFloat(input) * 1000);
    });
    addParseToken('x', function (input, array, config) {
        config._d = new Date(toInt(input));
    });

    //! moment.js

    hooks.version = '2.29.4';

    setHookCallback(createLocal);

    hooks.fn = proto;
    hooks.min = min;
    hooks.max = max;
    hooks.now = now;
    hooks.utc = createUTC;
    hooks.unix = createUnix;
    hooks.months = listMonths;
    hooks.isDate = isDate;
    hooks.locale = getSetGlobalLocale;
    hooks.invalid = createInvalid;
    hooks.duration = createDuration;
    hooks.isMoment = isMoment;
    hooks.weekdays = listWeekdays;
    hooks.parseZone = createInZone;
    hooks.localeData = getLocale;
    hooks.isDuration = isDuration;
    hooks.monthsShort = listMonthsShort;
    hooks.weekdaysMin = listWeekdaysMin;
    hooks.defineLocale = defineLocale;
    hooks.updateLocale = updateLocale;
    hooks.locales = listLocales;
    hooks.weekdaysShort = listWeekdaysShort;
    hooks.normalizeUnits = normalizeUnits;
    hooks.relativeTimeRounding = getSetRelativeTimeRounding;
    hooks.relativeTimeThreshold = getSetRelativeTimeThreshold;
    hooks.calendarFormat = getCalendarFormat;
    hooks.prototype = proto;

    // currently HTML5 input type only supports 24-hour formats
    hooks.HTML5_FMT = {
        DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // <input type="datetime-local" />
        DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // <input type="datetime-local" step="1" />
        DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // <input type="datetime-local" step="0.001" />
        DATE: 'YYYY-MM-DD', // <input type="date" />
        TIME: 'HH:mm', // <input type="time" />
        TIME_SECONDS: 'HH:mm:ss', // <input type="time" step="1" />
        TIME_MS: 'HH:mm:ss.SSS', // <input type="time" step="0.001" />
        WEEK: 'GGGG-[W]WW', // <input type="week" />
        MONTH: 'YYYY-MM', // <input type="month" />
    };

    return hooks;

})));
//! moment.js locale configuration
//! locale : Spanish [es]
//! author : Julio Napurí : https://github.com/julionc

;(function (global, factory) {
   typeof exports === 'object' && typeof module !== 'undefined'
       && typeof require === 'function' ? factory(require('../moment')) :
   typeof define === 'function' && define.amd ? define(['../moment'], factory) :
   factory(global.moment)
}(this, (function (moment) { 'use strict';

    //! moment.js locale configuration

    var monthsShortDot =
            'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split(
                '_'
            ),
        monthsShort = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_'),
        monthsParse = [
            /^ene/i,
            /^feb/i,
            /^mar/i,
            /^abr/i,
            /^may/i,
            /^jun/i,
            /^jul/i,
            /^ago/i,
            /^sep/i,
            /^oct/i,
            /^nov/i,
            /^dic/i,
        ],
        monthsRegex =
            /^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i;

    var es = moment.defineLocale('es', {
        months: 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split(
            '_'
        ),
        monthsShort: function (m, format) {
            if (!m) {
                return monthsShortDot;
            } else if (/-MMM-/.test(format)) {
                return monthsShort[m.month()];
            } else {
                return monthsShortDot[m.month()];
            }
        },
        monthsRegex: monthsRegex,
        monthsShortRegex: monthsRegex,
        monthsStrictRegex:
            /^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i,
        monthsShortStrictRegex:
            /^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i,
        monthsParse: monthsParse,
        longMonthsParse: monthsParse,
        shortMonthsParse: monthsParse,
        weekdays: 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'),
        weekdaysShort: 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'),
        weekdaysMin: 'do_lu_ma_mi_ju_vi_sá'.split('_'),
        weekdaysParseExact: true,
        longDateFormat: {
            LT: 'H:mm',
            LTS: 'H:mm:ss',
            L: 'DD/MM/YYYY',
            LL: 'D [de] MMMM [de] YYYY',
            LLL: 'D [de] MMMM [de] YYYY H:mm',
            LLLL: 'dddd, D [de] MMMM [de] YYYY H:mm',
        },
        calendar: {
            sameDay: function () {
                return '[hoy a la' + (this.hours() !== 1 ? 's' : '') + '] LT';
            },
            nextDay: function () {
                return '[mañana a la' + (this.hours() !== 1 ? 's' : '') + '] LT';
            },
            nextWeek: function () {
                return 'dddd [a la' + (this.hours() !== 1 ? 's' : '') + '] LT';
            },
            lastDay: function () {
                return '[ayer a la' + (this.hours() !== 1 ? 's' : '') + '] LT';
            },
            lastWeek: function () {
                return (
                    '[el] dddd [pasado a la' +
                    (this.hours() !== 1 ? 's' : '') +
                    '] LT'
                );
            },
            sameElse: 'L',
        },
        relativeTime: {
            future: 'en %s',
            past: 'hace %s',
            s: 'unos segundos',
            ss: '%d segundos',
            m: 'un minuto',
            mm: '%d minutos',
            h: 'una hora',
            hh: '%d horas',
            d: 'un día',
            dd: '%d días',
            w: 'una semana',
            ww: '%d semanas',
            M: 'un mes',
            MM: '%d meses',
            y: 'un año',
            yy: '%d años',
        },
        dayOfMonthOrdinalParse: /\d{1,2}º/,
        ordinal: '%dº',
        week: {
            dow: 1, // Monday is the first day of the week.
            doy: 4, // The week that contains Jan 4th is the first week of the year.
        },
        invalidDate: 'Fecha inválida',
    });

    return es;

})));
/*! version : 4.17.47
 =========================================================
 bootstrap-datetimejs
 https://github.com/Eonasdan/bootstrap-datetimepicker
 Copyright (c) 2015 Jonathan Peterson
 =========================================================
 */
/*
 The MIT License (MIT)

 Copyright (c) 2015 Jonathan Peterson

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 THE SOFTWARE.
 */
/*global define:false */
/*global exports:false */
/*global require:false */
/*global jQuery:false */
/*global moment:false */

(function (factory) {
    'use strict';
    if (typeof define === 'function' && define.amd) {
        // AMD is used - Register as an anonymous module.
        define(['jquery', 'moment'], factory);
    } else if (typeof exports === 'object') {
        module.exports = factory(require('jquery'), require('moment'));
    } else {
        // Neither AMD nor CommonJS used. Use global variables.
        if (typeof jQuery === 'undefined') {
            throw 'bootstrap-datetimepicker requires jQuery to be loaded first';
        }
        if (typeof moment === 'undefined') {
            throw 'bootstrap-datetimepicker requires Moment.js to be loaded first';
        }
        factory(jQuery, moment);
    }
}(function ($, moment) {
    'use strict';
    if (!moment) {
        throw new Error('bootstrap-datetimepicker requires Moment.js to be loaded first');
    }

    var dateTimePicker = function (element, options) {
        var picker = {},
            date,
            viewDate,
            unset = true,
            input,
            component = false,
            widget = false,
            use24Hours,
            minViewModeNumber = 0,
            actualFormat,
            parseFormats,
            currentViewMode,
            datePickerModes = [
                {
                    clsName: 'days',
                    navFnc: 'M',
                    navStep: 1
                },
                {
                    clsName: 'months',
                    navFnc: 'y',
                    navStep: 1
                },
                {
                    clsName: 'years',
                    navFnc: 'y',
                    navStep: 10
                },
                {
                    clsName: 'decades',
                    navFnc: 'y',
                    navStep: 100
                }
            ],
            viewModes = ['days', 'months', 'years', 'decades'],
            verticalModes = ['top', 'bottom', 'auto'],
            horizontalModes = ['left', 'right', 'auto'],
            toolbarPlacements = ['default', 'top', 'bottom'],
            keyMap = {
                'up': 38,
                38: 'up',
                'down': 40,
                40: 'down',
                'left': 37,
                37: 'left',
                'right': 39,
                39: 'right',
                'tab': 9,
                9: 'tab',
                'escape': 27,
                27: 'escape',
                'enter': 13,
                13: 'enter',
                'pageUp': 33,
                33: 'pageUp',
                'pageDown': 34,
                34: 'pageDown',
                'shift': 16,
                16: 'shift',
                'control': 17,
                17: 'control',
                'space': 32,
                32: 'space',
                't': 84,
                84: 't',
                'delete': 46,
                46: 'delete'
            },
            keyState = {},

            /********************************************************************************
             *
             * Private functions
             *
             ********************************************************************************/

            hasTimeZone = function () {
                return moment.tz !== undefined && options.timeZone !== undefined && options.timeZone !== null && options.timeZone !== '';
            },

            getMoment = function (d) {
                var returnMoment;

                if (d === undefined || d === null) {
                    returnMoment = moment(); //TODO should this use format? and locale?
                } else if (moment.isDate(d) || moment.isMoment(d)) {
                    // If the date that is passed in is already a Date() or moment() object,
                    // pass it directly to moment.
                    returnMoment = moment(d);
                } else if (hasTimeZone()) { // There is a string to parse and a default time zone
                    // parse with the tz function which takes a default time zone if it is not in the format string
                    returnMoment = moment.tz(d, parseFormats, options.useStrict, options.timeZone);
                } else {
                    returnMoment = moment(d, parseFormats, options.useStrict);
                }

                if (hasTimeZone()) {
                    returnMoment.tz(options.timeZone);
                }

                return returnMoment;
            },

            isEnabled = function (granularity) {
                if (typeof granularity !== 'string' || granularity.length > 1) {
                    throw new TypeError('isEnabled expects a single character string parameter');
                }
                switch (granularity) {
                    case 'y':
                        return actualFormat.indexOf('Y') !== -1;
                    case 'M':
                        return actualFormat.indexOf('M') !== -1;
                    case 'd':
                        return actualFormat.toLowerCase().indexOf('d') !== -1;
                    case 'h':
                    case 'H':
                        return actualFormat.toLowerCase().indexOf('h') !== -1;
                    case 'm':
                        return actualFormat.indexOf('m') !== -1;
                    case 's':
                        return actualFormat.indexOf('s') !== -1;
                    default:
                        return false;
                }
            },

            hasTime = function () {
                return (isEnabled('h') || isEnabled('m') || isEnabled('s'));
            },

            hasDate = function () {
                return (isEnabled('y') || isEnabled('M') || isEnabled('d'));
            },

            getDatePickerTemplate = function () {
                var headTemplate = $('<thead>')
                        .append($('<tr>')
                            .append($('<th>').addClass('prev').attr('data-action', 'previous')
                                .append($('<span>').addClass(options.icons.previous))
                                )
                            .append($('<th>').addClass('picker-switch').attr('data-action', 'pickerSwitch').attr('colspan', (options.calendarWeeks ? '6' : '5')))
                            .append($('<th>').addClass('next').attr('data-action', 'next')
                                .append($('<span>').addClass(options.icons.next))
                                )
                            ),
                    contTemplate = $('<tbody>')
                        .append($('<tr>')
                            .append($('<td>').attr('colspan', (options.calendarWeeks ? '8' : '7')))
                            );

                return [
                    $('<div>').addClass('datepicker-days')
                        .append($('<table>').addClass('table-condensed')
                            .append(headTemplate)
                            .append($('<tbody>'))
                            ),
                    $('<div>').addClass('datepicker-months')
                        .append($('<table>').addClass('table-condensed')
                            .append(headTemplate.clone())
                            .append(contTemplate.clone())
                            ),
                    $('<div>').addClass('datepicker-years')
                        .append($('<table>').addClass('table-condensed')
                            .append(headTemplate.clone())
                            .append(contTemplate.clone())
                            ),
                    $('<div>').addClass('datepicker-decades')
                        .append($('<table>').addClass('table-condensed')
                            .append(headTemplate.clone())
                            .append(contTemplate.clone())
                            )
                ];
            },

            getTimePickerMainTemplate = function () {
                var topRow = $('<tr>'),
                    middleRow = $('<tr>'),
                    bottomRow = $('<tr>');

                if (isEnabled('h')) {
                    topRow.append($('<td>')
                        .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementHour }).addClass('btn').attr('data-action', 'incrementHours').append($('<span>').addClass(options.icons.up))));
                    middleRow.append($('<td>')
                        .append($('<span>').addClass('timepicker-hour').attr({ 'data-time-component': 'hours', 'title': options.tooltips.pickHour }).attr('data-action', 'showHours')));
                    bottomRow.append($('<td>')
                        .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementHour }).addClass('btn').attr('data-action', 'decrementHours').append($('<span>').addClass(options.icons.down))));
                }
                if (isEnabled('m')) {
                    if (isEnabled('h')) {
                        topRow.append($('<td>').addClass('separator'));
                        middleRow.append($('<td>').addClass('separator').html(':'));
                        bottomRow.append($('<td>').addClass('separator'));
                    }
                    topRow.append($('<td>')
                        .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementMinute }).addClass('btn').attr('data-action', 'incrementMinutes')
                            .append($('<span>').addClass(options.icons.up))));
                    middleRow.append($('<td>')
                        .append($('<span>').addClass('timepicker-minute').attr({ 'data-time-component': 'minutes', 'title': options.tooltips.pickMinute }).attr('data-action', 'showMinutes')));
                    bottomRow.append($('<td>')
                        .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementMinute }).addClass('btn').attr('data-action', 'decrementMinutes')
                            .append($('<span>').addClass(options.icons.down))));
                }
                if (isEnabled('s')) {
                    if (isEnabled('m')) {
                        topRow.append($('<td>').addClass('separator'));
                        middleRow.append($('<td>').addClass('separator').html(':'));
                        bottomRow.append($('<td>').addClass('separator'));
                    }
                    topRow.append($('<td>')
                        .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.incrementSecond }).addClass('btn').attr('data-action', 'incrementSeconds')
                            .append($('<span>').addClass(options.icons.up))));
                    middleRow.append($('<td>')
                        .append($('<span>').addClass('timepicker-second').attr({ 'data-time-component': 'seconds', 'title': options.tooltips.pickSecond }).attr('data-action', 'showSeconds')));
                    bottomRow.append($('<td>')
                        .append($('<a>').attr({ href: '#', tabindex: '-1', 'title': options.tooltips.decrementSecond }).addClass('btn').attr('data-action', 'decrementSeconds')
                            .append($('<span>').addClass(options.icons.down))));
                }

                if (!use24Hours) {
                    topRow.append($('<td>').addClass('separator'));
                    middleRow.append($('<td>')
                        .append($('<button>').addClass('btn btn-primary').attr({ 'data-action': 'togglePeriod', tabindex: '-1', 'title': options.tooltips.togglePeriod })));
                    bottomRow.append($('<td>').addClass('separator'));
                }

                return $('<div>').addClass('timepicker-picker')
                    .append($('<table>').addClass('table-condensed')
                        .append([topRow, middleRow, bottomRow]));
            },

            getTimePickerTemplate = function () {
                var hoursView = $('<div>').addClass('timepicker-hours')
                        .append($('<table>').addClass('table-condensed')),
                    minutesView = $('<div>').addClass('timepicker-minutes')
                        .append($('<table>').addClass('table-condensed')),
                    secondsView = $('<div>').addClass('timepicker-seconds')
                        .append($('<table>').addClass('table-condensed')),
                    ret = [getTimePickerMainTemplate()];

                if (isEnabled('h')) {
                    ret.push(hoursView);
                }
                if (isEnabled('m')) {
                    ret.push(minutesView);
                }
                if (isEnabled('s')) {
                    ret.push(secondsView);
                }

                return ret;
            },

            getToolbar = function () {
                var row = [];
                if (options.showTodayButton) {
                    row.push($('<td>').append($('<a>').attr({ 'data-action': 'today', 'title': options.tooltips.today }).append($('<span>').addClass(options.icons.today))));
                }
                if (!options.sideBySide && hasDate() && hasTime()) {
                    row.push($('<td>').append($('<a>').attr({ 'data-action': 'togglePicker', 'title': options.tooltips.selectTime }).append($('<span>').addClass(options.icons.time))));
                }
                if (options.showClear) {
                    row.push($('<td>').append($('<a>').attr({ 'data-action': 'clear', 'title': options.tooltips.clear }).append($('<span>').addClass(options.icons.clear))));
                }
                if (options.showClose) {
                    row.push($('<td>').append($('<a>').attr({ 'data-action': 'close', 'title': options.tooltips.close }).append($('<span>').addClass(options.icons.close))));
                }
                return $('<table>').addClass('table-condensed').append($('<tbody>').append($('<tr>').append(row)));
            },

            getTemplate = function () {
                var template = $('<div>').addClass('bootstrap-datetimepicker-widget dropdown-menu'),
                    dateView = $('<div>').addClass('datepicker').append(getDatePickerTemplate()),
                    timeView = $('<div>').addClass('timepicker').append(getTimePickerTemplate()),
                    content = $('<ul>').addClass('list-unstyled'),
                    toolbar = $('<li>').addClass('picker-switch' + (options.collapse ? ' accordion-toggle' : '')).append(getToolbar());

                if (options.inline) {
                    template.removeClass('dropdown-menu');
                }

                if (use24Hours) {
                    template.addClass('usetwentyfour');
                }

                if (isEnabled('s') && !use24Hours) {
                    template.addClass('wider');
                }

                if (options.sideBySide && hasDate() && hasTime()) {
                    template.addClass('timepicker-sbs');
                    if (options.toolbarPlacement === 'top') {
                        template.append(toolbar);
                    }
                    template.append(
                        $('<div>').addClass('row')
                            .append(dateView.addClass('col-md-6'))
                            .append(timeView.addClass('col-md-6'))
                    );
                    if (options.toolbarPlacement === 'bottom') {
                        template.append(toolbar);
                    }
                    return template;
                }

                if (options.toolbarPlacement === 'top') {
                    content.append(toolbar);
                }
                if (hasDate()) {
                    content.append($('<li>').addClass((options.collapse && hasTime() ? 'collapse in' : '')).append(dateView));
                }
                if (options.toolbarPlacement === 'default') {
                    content.append(toolbar);
                }
                if (hasTime()) {
                    content.append($('<li>').addClass((options.collapse && hasDate() ? 'collapse' : '')).append(timeView));
                }
                if (options.toolbarPlacement === 'bottom') {
                    content.append(toolbar);
                }
                return template.append(content);
            },

            dataToOptions = function () {
                var eData,
                    dataOptions = {};

                if (element.is('input') || options.inline) {
                    eData = element.data();
                } else {
                    eData = element.find('input').data();
                }

                if (eData.dateOptions && eData.dateOptions instanceof Object) {
                    dataOptions = $.extend(true, dataOptions, eData.dateOptions);
                }

                $.each(options, function (key) {
                    var attributeName = 'date' + key.charAt(0).toUpperCase() + key.slice(1);
                    if (eData[attributeName] !== undefined) {
                        dataOptions[key] = eData[attributeName];
                    }
                });
                return dataOptions;
            },

            place = function () {
                var position = (component || element).position(),
                    offset = (component || element).offset(),
                    vertical = options.widgetPositioning.vertical,
                    horizontal = options.widgetPositioning.horizontal,
                    parent;

                if (options.widgetParent) {
                    parent = options.widgetParent.append(widget);
                } else if (element.is('input')) {
                    parent = element.after(widget).parent();
                } else if (options.inline) {
                    parent = element.append(widget);
                    return;
                } else {
                    parent = element;
                    element.children().first().after(widget);
                }

                // Top and bottom logic
                if (vertical === 'auto') {
                    if (offset.top + widget.height() * 1.5 >= $(window).height() + $(window).scrollTop() &&
                        widget.height() + element.outerHeight() < offset.top) {
                        vertical = 'top';
                    } else {
                        vertical = 'bottom';
                    }
                }

                // Left and right logic
                if (horizontal === 'auto') {
                    if (parent.width() < offset.left + widget.outerWidth() / 2 &&
                        offset.left + widget.outerWidth() > $(window).width()) {
                        horizontal = 'right';
                    } else {
                        horizontal = 'left';
                    }
                }

                if (vertical === 'top') {
                    widget.addClass('top').removeClass('bottom');
                } else {
                    widget.addClass('bottom').removeClass('top');
                }

                if (horizontal === 'right') {
                    widget.addClass('pull-right');
                } else {
                    widget.removeClass('pull-right');
                }

                // find the first parent element that has a non-static css positioning
                if (parent.css('position') === 'static') {
                    parent = parent.parents().filter(function () {
                        return $(this).css('position') !== 'static';
                    }).first();
                }

                if (parent.length === 0) {
                    throw new Error('datetimepicker component should be placed within a non-static positioned container');
                }

                widget.css({
                    top: vertical === 'top' ? 'auto' : position.top + element.outerHeight(),
                    bottom: vertical === 'top' ? parent.outerHeight() - (parent === element ? 0 : position.top) : 'auto',
                    left: horizontal === 'left' ? (parent === element ? 0 : position.left) : 'auto',
                    right: horizontal === 'left' ? 'auto' : parent.outerWidth() - element.outerWidth() - (parent === element ? 0 : position.left)
                });
            },

            notifyEvent = function (e) {
                if (e.type === 'dp.change' && ((e.date && e.date.isSame(e.oldDate)) || (!e.date && !e.oldDate))) {
                    return;
                }
                element.trigger(e);
            },

            viewUpdate = function (e) {
                if (e === 'y') {
                    e = 'YYYY';
                }
                notifyEvent({
                    type: 'dp.update',
                    change: e,
                    viewDate: viewDate.clone()
                });
            },

            showMode = function (dir) {
                if (!widget) {
                    return;
                }
                if (dir) {
                    currentViewMode = Math.max(minViewModeNumber, Math.min(3, currentViewMode + dir));
                }
                widget.find('.datepicker > div').hide().filter('.datepicker-' + datePickerModes[currentViewMode].clsName).show();
            },

            fillDow = function () {
                var row = $('<tr>'),
                    currentDate = viewDate.clone().startOf('w').startOf('d');

                if (options.calendarWeeks === true) {
                    row.append($('<th>').addClass('cw').text('#'));
                }

                while (currentDate.isBefore(viewDate.clone().endOf('w'))) {
                    row.append($('<th>').addClass('dow').text(currentDate.format('dd')));
                    currentDate.add(1, 'd');
                }
                widget.find('.datepicker-days thead').append(row);
            },

            isInDisabledDates = function (testDate) {
                return options.disabledDates[testDate.format('YYYY-MM-DD')] === true;
            },

            isInEnabledDates = function (testDate) {
                return options.enabledDates[testDate.format('YYYY-MM-DD')] === true;
            },

            isInDisabledHours = function (testDate) {
                return options.disabledHours[testDate.format('H')] === true;
            },

            isInEnabledHours = function (testDate) {
                return options.enabledHours[testDate.format('H')] === true;
            },

            isValid = function (targetMoment, granularity) {
                if (!targetMoment.isValid()) {
                    return false;
                }
                if (options.disabledDates && granularity === 'd' && isInDisabledDates(targetMoment)) {
                    return false;
                }
                if (options.enabledDates && granularity === 'd' && !isInEnabledDates(targetMoment)) {
                    return false;
                }
                if (options.minDate && targetMoment.isBefore(options.minDate, granularity)) {
                    return false;
                }
                if (options.maxDate && targetMoment.isAfter(options.maxDate, granularity)) {
                    return false;
                }
                if (options.daysOfWeekDisabled && granularity === 'd' && options.daysOfWeekDisabled.indexOf(targetMoment.day()) !== -1) {
                    return false;
                }
                if (options.disabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && isInDisabledHours(targetMoment)) {
                    return false;
                }
                if (options.enabledHours && (granularity === 'h' || granularity === 'm' || granularity === 's') && !isInEnabledHours(targetMoment)) {
                    return false;
                }
                if (options.disabledTimeIntervals && (granularity === 'h' || granularity === 'm' || granularity === 's')) {
                    var found = false;
                    $.each(options.disabledTimeIntervals, function () {
                        if (targetMoment.isBetween(this[0], this[1])) {
                            found = true;
                            return false;
                        }
                    });
                    if (found) {
                        return false;
                    }
                }
                return true;
            },

            fillMonths = function () {
                var spans = [],
                    monthsShort = viewDate.clone().startOf('y').startOf('d');
                while (monthsShort.isSame(viewDate, 'y')) {
                    spans.push($('<span>').attr('data-action', 'selectMonth').addClass('month').text(monthsShort.format('MMM')));
                    monthsShort.add(1, 'M');
                }
                widget.find('.datepicker-months td').empty().append(spans);
            },

            updateMonths = function () {
                var monthsView = widget.find('.datepicker-months'),
                    monthsViewHeader = monthsView.find('th'),
                    months = monthsView.find('tbody').find('span');

                monthsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevYear);
                monthsViewHeader.eq(1).attr('title', options.tooltips.selectYear);
                monthsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextYear);

                monthsView.find('.disabled').removeClass('disabled');

                if (!isValid(viewDate.clone().subtract(1, 'y'), 'y')) {
                    monthsViewHeader.eq(0).addClass('disabled');
                }

                monthsViewHeader.eq(1).text(viewDate.year());

                if (!isValid(viewDate.clone().add(1, 'y'), 'y')) {
                    monthsViewHeader.eq(2).addClass('disabled');
                }

                months.removeClass('active');
                if (date.isSame(viewDate, 'y') && !unset) {
                    months.eq(date.month()).addClass('active');
                }

                months.each(function (index) {
                    if (!isValid(viewDate.clone().month(index), 'M')) {
                        $(this).addClass('disabled');
                    }
                });
            },

            updateYears = function () {
                var yearsView = widget.find('.datepicker-years'),
                    yearsViewHeader = yearsView.find('th'),
                    startYear = viewDate.clone().subtract(5, 'y'),
                    endYear = viewDate.clone().add(6, 'y'),
                    html = '';

                yearsViewHeader.eq(0).find('span').attr('title', options.tooltips.prevDecade);
                yearsViewHeader.eq(1).attr('title', options.tooltips.selectDecade);
                yearsViewHeader.eq(2).find('span').attr('title', options.tooltips.nextDecade);

                yearsView.find('.disabled').removeClass('disabled');

                if (options.minDate && options.minDate.isAfter(startYear, 'y')) {
                    yearsViewHeader.eq(0).addClass('disabled');
                }

                yearsViewHeader.eq(1).text(startYear.year() + '-' + endYear.year());

                if (options.maxDate && options.maxDate.isBefore(endYear, 'y')) {
                    yearsViewHeader.eq(2).addClass('disabled');
                }

                while (!startYear.isAfter(endYear, 'y')) {
                    html += '<span data-action="selectYear" class="year' + (startYear.isSame(date, 'y') && !unset ? ' active' : '') + (!isValid(startYear, 'y') ? ' disabled' : '') + '">' + startYear.year() + '</span>';
                    startYear.add(1, 'y');
                }

                yearsView.find('td').html(html);
            },

            updateDecades = function () {
                var decadesView = widget.find('.datepicker-decades'),
                    decadesViewHeader = decadesView.find('th'),
                    startDecade = moment({ y: viewDate.year() - (viewDate.year() % 100) - 1 }),
                    endDecade = startDecade.clone().add(100, 'y'),
                    startedAt = startDecade.clone(),
                    minDateDecade = false,
                    maxDateDecade = false,
                    endDecadeYear,
                    html = '';

                decadesViewHeader.eq(0).find('span').attr('title', options.tooltips.prevCentury);
                decadesViewHeader.eq(2).find('span').attr('title', options.tooltips.nextCentury);

                decadesView.find('.disabled').removeClass('disabled');

                if (startDecade.isSame(moment({ y: 1900 })) || (options.minDate && options.minDate.isAfter(startDecade, 'y'))) {
                    decadesViewHeader.eq(0).addClass('disabled');
                }

                decadesViewHeader.eq(1).text(startDecade.year() + '-' + endDecade.year());

                if (startDecade.isSame(moment({ y: 2000 })) || (options.maxDate && options.maxDate.isBefore(endDecade, 'y'))) {
                    decadesViewHeader.eq(2).addClass('disabled');
                }

                while (!startDecade.isAfter(endDecade, 'y')) {
                    endDecadeYear = startDecade.year() + 12;
                    minDateDecade = options.minDate && options.minDate.isAfter(startDecade, 'y') && options.minDate.year() <= endDecadeYear;
                    maxDateDecade = options.maxDate && options.maxDate.isAfter(startDecade, 'y') && options.maxDate.year() <= endDecadeYear;
                    html += '<span data-action="selectDecade" class="decade' + (date.isAfter(startDecade) && date.year() <= endDecadeYear ? ' active' : '') +
                        (!isValid(startDecade, 'y') && !minDateDecade && !maxDateDecade ? ' disabled' : '') + '" data-selection="' + (startDecade.year() + 6) + '">' + (startDecade.year() + 1) + ' - ' + (startDecade.year() + 12) + '</span>';
                    startDecade.add(12, 'y');
                }
                html += '<span></span><span></span><span></span>'; //push the dangling block over, at least this way it's even

                decadesView.find('td').html(html);
                decadesViewHeader.eq(1).text((startedAt.year() + 1) + '-' + (startDecade.year()));
            },

            fillDate = function () {
                var daysView = widget.find('.datepicker-days'),
                    daysViewHeader = daysView.find('th'),
                    currentDate,
                    html = [],
                    row,
                    clsNames = [],
                    i;

                if (!hasDate()) {
                    return;
                }

                daysViewHeader.eq(0).find('span').attr('title', options.tooltips.prevMonth);
                daysViewHeader.eq(1).attr('title', options.tooltips.selectMonth);
                daysViewHeader.eq(2).find('span').attr('title', options.tooltips.nextMonth);

                daysView.find('.disabled').removeClass('disabled');
                daysViewHeader.eq(1).text(viewDate.format(options.dayViewHeaderFormat));

                if (!isValid(viewDate.clone().subtract(1, 'M'), 'M')) {
                    daysViewHeader.eq(0).addClass('disabled');
                }
                if (!isValid(viewDate.clone().add(1, 'M'), 'M')) {
                    daysViewHeader.eq(2).addClass('disabled');
                }

                currentDate = viewDate.clone().startOf('M').startOf('w').startOf('d');

                for (i = 0; i < 42; i++) { //always display 42 days (should show 6 weeks)
                    if (currentDate.weekday() === 0) {
                        row = $('<tr>');
                        if (options.calendarWeeks) {
                            row.append('<td class="cw">' + currentDate.week() + '</td>');
                        }
                        html.push(row);
                    }
                    clsNames = ['day'];
                    if (currentDate.isBefore(viewDate, 'M')) {
                        clsNames.push('old');
                    }
                    if (currentDate.isAfter(viewDate, 'M')) {
                        clsNames.push('new');
                    }
                    if (currentDate.isSame(date, 'd') && !unset) {
                        clsNames.push('active');
                    }
                    if (!isValid(currentDate, 'd')) {
                        clsNames.push('disabled');
                    }
                    if (currentDate.isSame(getMoment(), 'd')) {
                        clsNames.push('today');
                    }
                    if (currentDate.day() === 0 || currentDate.day() === 6) {
                        clsNames.push('weekend');
                    }
                    notifyEvent({
                        type: 'dp.classify',
                        date: currentDate,
                        classNames: clsNames
                    });
                    row.append('<td data-action="selectDay" data-day="' + currentDate.format('L') + '" class="' + clsNames.join(' ') + '">' + currentDate.date() + '</td>');
                    currentDate.add(1, 'd');
                }

                daysView.find('tbody').empty().append(html);

                updateMonths();

                updateYears();

                updateDecades();
            },

            fillHours = function () {
                var table = widget.find('.timepicker-hours table'),
                    currentHour = viewDate.clone().startOf('d'),
                    html = [],
                    row = $('<tr>');

                if (viewDate.hour() > 11 && !use24Hours) {
                    currentHour.hour(12);
                }
                while (currentHour.isSame(viewDate, 'd') && (use24Hours || (viewDate.hour() < 12 && currentHour.hour() < 12) || viewDate.hour() > 11)) {
                    if (currentHour.hour() % 4 === 0) {
                        row = $('<tr>');
                        html.push(row);
                    }
                    row.append('<td data-action="selectHour" class="hour' + (!isValid(currentHour, 'h') ? ' disabled' : '') + '">' + currentHour.format(use24Hours ? 'HH' : 'hh') + '</td>');
                    currentHour.add(1, 'h');
                }
                table.empty().append(html);
            },

            fillMinutes = function () {
                var table = widget.find('.timepicker-minutes table'),
                    currentMinute = viewDate.clone().startOf('h'),
                    html = [],
                    row = $('<tr>'),
                    step = options.stepping === 1 ? 5 : options.stepping;

                while (viewDate.isSame(currentMinute, 'h')) {
                    if (currentMinute.minute() % (step * 4) === 0) {
                        row = $('<tr>');
                        html.push(row);
                    }
                    row.append('<td data-action="selectMinute" class="minute' + (!isValid(currentMinute, 'm') ? ' disabled' : '') + '">' + currentMinute.format('mm') + '</td>');
                    currentMinute.add(step, 'm');
                }
                table.empty().append(html);
            },

            fillSeconds = function () {
                var table = widget.find('.timepicker-seconds table'),
                    currentSecond = viewDate.clone().startOf('m'),
                    html = [],
                    row = $('<tr>');

                while (viewDate.isSame(currentSecond, 'm')) {
                    if (currentSecond.second() % 20 === 0) {
                        row = $('<tr>');
                        html.push(row);
                    }
                    row.append('<td data-action="selectSecond" class="second' + (!isValid(currentSecond, 's') ? ' disabled' : '') + '">' + currentSecond.format('ss') + '</td>');
                    currentSecond.add(5, 's');
                }

                table.empty().append(html);
            },

            fillTime = function () {
                var toggle, newDate, timeComponents = widget.find('.timepicker span[data-time-component]');

                if (!use24Hours) {
                    toggle = widget.find('.timepicker [data-action=togglePeriod]');
                    newDate = date.clone().add((date.hours() >= 12) ? -12 : 12, 'h');

                    toggle.text(date.format('A'));

                    if (isValid(newDate, 'h')) {
                        toggle.removeClass('disabled');
                    } else {
                        toggle.addClass('disabled');
                    }
                }
                timeComponents.filter('[data-time-component=hours]').text(date.format(use24Hours ? 'HH' : 'hh'));
                timeComponents.filter('[data-time-component=minutes]').text(date.format('mm'));
                timeComponents.filter('[data-time-component=seconds]').text(date.format('ss'));

                fillHours();
                fillMinutes();
                fillSeconds();
            },

            update = function () {
                if (!widget) {
                    return;
                }
                fillDate();
                fillTime();
            },

            setValue = function (targetMoment) {
                var oldDate = unset ? null : date;

                // case of calling setValue(null or false)
                if (!targetMoment) {
                    unset = true;
                    input.val('');
                    element.data('date', '');
                    notifyEvent({
                        type: 'dp.change',
                        date: false,
                        oldDate: oldDate
                    });
                    update();
                    return;
                }

                targetMoment = targetMoment.clone().locale(options.locale);

                if (hasTimeZone()) {
                    targetMoment.tz(options.timeZone);
                }

                if (options.stepping !== 1) {
                    targetMoment.minutes((Math.round(targetMoment.minutes() / options.stepping) * options.stepping)).seconds(0);

                    while (options.minDate && targetMoment.isBefore(options.minDate)) {
                        targetMoment.add(options.stepping, 'minutes');
                    }
                }

                if (isValid(targetMoment)) {
                    date = targetMoment;
                    viewDate = date.clone();
                    input.val(date.format(actualFormat));
                    element.data('date', date.format(actualFormat));
                    unset = false;
                    update();
                    notifyEvent({
                        type: 'dp.change',
                        date: date.clone(),
                        oldDate: oldDate
                    });
                } else {
                    if (!options.keepInvalid) {
                        input.val(unset ? '' : date.format(actualFormat));
                    } else {
                        notifyEvent({
                            type: 'dp.change',
                            date: targetMoment,
                            oldDate: oldDate
                        });
                    }
                    notifyEvent({
                        type: 'dp.error',
                        date: targetMoment,
                        oldDate: oldDate
                    });
                }
            },

            /**
             * Hides the widget. Possibly will emit dp.hide
             */
            hide = function () {
                var transitioning = false;
                if (!widget) {
                    return picker;
                }
                // Ignore event if in the middle of a picker transition
                widget.find('.collapse').each(function () {
                    var collapseData = $(this).data('collapse');
                    if (collapseData && collapseData.transitioning) {
                        transitioning = true;
                        return false;
                    }
                    return true;
                });
                if (transitioning) {
                    return picker;
                }
                if (component && component.hasClass('btn')) {
                    component.toggleClass('active');
                }
                widget.hide();

                $(window).off('resize', place);
                widget.off('click', '[data-action]');
                widget.off('mousedown', false);

                widget.remove();
                widget = false;

                notifyEvent({
                    type: 'dp.hide',
                    date: date.clone()
                });

                input.blur();

                viewDate = date.clone();

                return picker;
            },

            clear = function () {
                setValue(null);
            },

            parseInputDate = function (inputDate) {
                if (options.parseInputDate === undefined) {
                    if (!moment.isMoment(inputDate) || inputDate instanceof Date) {
                        inputDate = getMoment(inputDate);
                    }
                } else {
                    inputDate = options.parseInputDate(inputDate);
                }
                //inputDate.locale(options.locale);
                return inputDate;
            },

            /********************************************************************************
             *
             * Widget UI interaction functions
             *
             ********************************************************************************/
            actions = {
                next: function () {
                    var navFnc = datePickerModes[currentViewMode].navFnc;
                    viewDate.add(datePickerModes[currentViewMode].navStep, navFnc);
                    fillDate();
                    viewUpdate(navFnc);
                },

                previous: function () {
                    var navFnc = datePickerModes[currentViewMode].navFnc;
                    viewDate.subtract(datePickerModes[currentViewMode].navStep, navFnc);
                    fillDate();
                    viewUpdate(navFnc);
                },

                pickerSwitch: function () {
                    showMode(1);
                },

                selectMonth: function (e) {
                    var month = $(e.target).closest('tbody').find('span').index($(e.target));
                    viewDate.month(month);
                    if (currentViewMode === minViewModeNumber) {
                        setValue(date.clone().year(viewDate.year()).month(viewDate.month()));
                        if (!options.inline) {
                            hide();
                        }
                    } else {
                        showMode(-1);
                        fillDate();
                    }
                    viewUpdate('M');
                },

                selectYear: function (e) {
                    var year = parseInt($(e.target).text(), 10) || 0;
                    viewDate.year(year);
                    if (currentViewMode === minViewModeNumber) {
                        setValue(date.clone().year(viewDate.year()));
                        if (!options.inline) {
                            hide();
                        }
                    } else {
                        showMode(-1);
                        fillDate();
                    }
                    viewUpdate('YYYY');
                },

                selectDecade: function (e) {
                    var year = parseInt($(e.target).data('selection'), 10) || 0;
                    viewDate.year(year);
                    if (currentViewMode === minViewModeNumber) {
                        setValue(date.clone().year(viewDate.year()));
                        if (!options.inline) {
                            hide();
                        }
                    } else {
                        showMode(-1);
                        fillDate();
                    }
                    viewUpdate('YYYY');
                },

                selectDay: function (e) {
                    var day = viewDate.clone();
                    if ($(e.target).is('.old')) {
                        day.subtract(1, 'M');
                    }
                    if ($(e.target).is('.new')) {
                        day.add(1, 'M');
                    }
                    setValue(day.date(parseInt($(e.target).text(), 10)));
                    if (!hasTime() && !options.keepOpen && !options.inline) {
                        hide();
                    }
                },

                incrementHours: function () {
                    var newDate = date.clone().add(1, 'h');
                    if (isValid(newDate, 'h')) {
                        setValue(newDate);
                    }
                },

                incrementMinutes: function () {
                    var newDate = date.clone().add(options.stepping, 'm');
                    if (isValid(newDate, 'm')) {
                        setValue(newDate);
                    }
                },

                incrementSeconds: function () {
                    var newDate = date.clone().add(1, 's');
                    if (isValid(newDate, 's')) {
                        setValue(newDate);
                    }
                },

                decrementHours: function () {
                    var newDate = date.clone().subtract(1, 'h');
                    if (isValid(newDate, 'h')) {
                        setValue(newDate);
                    }
                },

                decrementMinutes: function () {
                    var newDate = date.clone().subtract(options.stepping, 'm');
                    if (isValid(newDate, 'm')) {
                        setValue(newDate);
                    }
                },

                decrementSeconds: function () {
                    var newDate = date.clone().subtract(1, 's');
                    if (isValid(newDate, 's')) {
                        setValue(newDate);
                    }
                },

                togglePeriod: function () {
                    setValue(date.clone().add((date.hours() >= 12) ? -12 : 12, 'h'));
                },

                togglePicker: function (e) {
                    var $this = $(e.target),
                        $parent = $this.closest('ul'),
                        expanded = $parent.find('.in'),
                        closed = $parent.find('.collapse:not(.in)'),
                        collapseData;

                    if (expanded && expanded.length) {
                        collapseData = expanded.data('collapse');
                        if (collapseData && collapseData.transitioning) {
                            return;
                        }
                        if (expanded.collapse) { // if collapse plugin is available through bootstrap.js then use it
                            expanded.collapse('hide');
                            closed.collapse('show');
                        } else { // otherwise just toggle in class on the two views
                            expanded.removeClass('in');
                            closed.addClass('in');
                        }
                        if ($this.is('span')) {
                            $this.toggleClass(options.icons.time + ' ' + options.icons.date);
                        } else {
                            $this.find('span').toggleClass(options.icons.time + ' ' + options.icons.date);
                        }

                        // NOTE: uncomment if toggled state will be restored in show()
                        //if (component) {
                        //    component.find('span').toggleClass(options.icons.time + ' ' + options.icons.date);
                        //}
                    }
                },

                showPicker: function () {
                    widget.find('.timepicker > div:not(.timepicker-picker)').hide();
                    widget.find('.timepicker .timepicker-picker').show();
                },

                showHours: function () {
                    widget.find('.timepicker .timepicker-picker').hide();
                    widget.find('.timepicker .timepicker-hours').show();
                },

                showMinutes: function () {
                    widget.find('.timepicker .timepicker-picker').hide();
                    widget.find('.timepicker .timepicker-minutes').show();
                },

                showSeconds: function () {
                    widget.find('.timepicker .timepicker-picker').hide();
                    widget.find('.timepicker .timepicker-seconds').show();
                },

                selectHour: function (e) {
                    var hour = parseInt($(e.target).text(), 10);

                    if (!use24Hours) {
                        if (date.hours() >= 12) {
                            if (hour !== 12) {
                                hour += 12;
                            }
                        } else {
                            if (hour === 12) {
                                hour = 0;
                            }
                        }
                    }
                    setValue(date.clone().hours(hour));
                    actions.showPicker.call(picker);
                },

                selectMinute: function (e) {
                    setValue(date.clone().minutes(parseInt($(e.target).text(), 10)));
                    actions.showPicker.call(picker);
                },

                selectSecond: function (e) {
                    setValue(date.clone().seconds(parseInt($(e.target).text(), 10)));
                    actions.showPicker.call(picker);
                },

                clear: clear,

                today: function () {
                    var todaysDate = getMoment();
                    if (isValid(todaysDate, 'd')) {
                        setValue(todaysDate);
                    }
                },

                close: hide
            },

            doAction = function (e) {
                if ($(e.currentTarget).is('.disabled')) {
                    return false;
                }
                actions[$(e.currentTarget).data('action')].apply(picker, arguments);
                return false;
            },

            /**
             * Shows the widget. Possibly will emit dp.show and dp.change
             */
            show = function () {
                var currentMoment,
                    useCurrentGranularity = {
                        'year': function (m) {
                            return m.month(0).date(1).hours(0).seconds(0).minutes(0);
                        },
                        'month': function (m) {
                            return m.date(1).hours(0).seconds(0).minutes(0);
                        },
                        'day': function (m) {
                            return m.hours(0).seconds(0).minutes(0);
                        },
                        'hour': function (m) {
                            return m.seconds(0).minutes(0);
                        },
                        'minute': function (m) {
                            return m.seconds(0);
                        }
                    };

                if (input.prop('disabled') || (!options.ignoreReadonly && input.prop('readonly')) || widget) {
                    return picker;
                }
                if (input.val() !== undefined && input.val().trim().length !== 0) {
                    setValue(parseInputDate(input.val().trim()));
                } else if (unset && options.useCurrent && (options.inline || (input.is('input') && input.val().trim().length === 0))) {
                    currentMoment = getMoment();
                    if (typeof options.useCurrent === 'string') {
                        currentMoment = useCurrentGranularity[options.useCurrent](currentMoment);
                    }
                    setValue(currentMoment);
                }
                widget = getTemplate();

                fillDow();
                fillMonths();

                widget.find('.timepicker-hours').hide();
                widget.find('.timepicker-minutes').hide();
                widget.find('.timepicker-seconds').hide();

                update();
                showMode();

                $(window).on('resize', place);
                widget.on('click', '[data-action]', doAction); // this handles clicks on the widget
                widget.on('mousedown', false);

                if (component && component.hasClass('btn')) {
                    component.toggleClass('active');
                }
                place();
                widget.show();
                if (options.focusOnShow && !input.is(':focus')) {
                    input.focus();
                }

                notifyEvent({
                    type: 'dp.show'
                });
                return picker;
            },

            /**
             * Shows or hides the widget
             */
            toggle = function () {
                return (widget ? hide() : show());
            },

            keydown = function (e) {
                var handler = null,
                    index,
                    index2,
                    pressedKeys = [],
                    pressedModifiers = {},
                    currentKey = e.which,
                    keyBindKeys,
                    allModifiersPressed,
                    pressed = 'p';

                keyState[currentKey] = pressed;

                for (index in keyState) {
                    if (keyState.hasOwnProperty(index) && keyState[index] === pressed) {
                        pressedKeys.push(index);
                        if (parseInt(index, 10) !== currentKey) {
                            pressedModifiers[index] = true;
                        }
                    }
                }

                for (index in options.keyBinds) {
                    if (options.keyBinds.hasOwnProperty(index) && typeof (options.keyBinds[index]) === 'function') {
                        keyBindKeys = index.split(' ');
                        if (keyBindKeys.length === pressedKeys.length && keyMap[currentKey] === keyBindKeys[keyBindKeys.length - 1]) {
                            allModifiersPressed = true;
                            for (index2 = keyBindKeys.length - 2; index2 >= 0; index2--) {
                                if (!(keyMap[keyBindKeys[index2]] in pressedModifiers)) {
                                    allModifiersPressed = false;
                                    break;
                                }
                            }
                            if (allModifiersPressed) {
                                handler = options.keyBinds[index];
                                break;
                            }
                        }
                    }
                }

                if (handler) {
                    handler.call(picker, widget);
                    e.stopPropagation();
                    e.preventDefault();
                }
            },

            keyup = function (e) {
                keyState[e.which] = 'r';
                e.stopPropagation();
                e.preventDefault();
            },

            change = function (e) {
                var val = $(e.target).val().trim(),
                    parsedDate = val ? parseInputDate(val) : null;
                setValue(parsedDate);
                e.stopImmediatePropagation();
                return false;
            },

            attachDatePickerElementEvents = function () {
                input.on({
                    'change': change,
                    'blur': options.debug ? '' : hide,
                    'keydown': keydown,
                    'keyup': keyup,
                    'focus': options.allowInputToggle ? show : ''
                });

                if (element.is('input')) {
                    input.on({
                        'focus': show
                    });
                } else if (component) {
                    component.on('click', toggle);
                    component.on('mousedown', false);
                }
            },

            detachDatePickerElementEvents = function () {
                input.off({
                    'change': change,
                    'blur': blur,
                    'keydown': keydown,
                    'keyup': keyup,
                    'focus': options.allowInputToggle ? hide : ''
                });

                if (element.is('input')) {
                    input.off({
                        'focus': show
                    });
                } else if (component) {
                    component.off('click', toggle);
                    component.off('mousedown', false);
                }
            },

            indexGivenDates = function (givenDatesArray) {
                // Store given enabledDates and disabledDates as keys.
                // This way we can check their existence in O(1) time instead of looping through whole array.
                // (for example: options.enabledDates['2014-02-27'] === true)
                var givenDatesIndexed = {};
                $.each(givenDatesArray, function () {
                    var dDate = parseInputDate(this);
                    if (dDate.isValid()) {
                        givenDatesIndexed[dDate.format('YYYY-MM-DD')] = true;
                    }
                });
                return (Object.keys(givenDatesIndexed).length) ? givenDatesIndexed : false;
            },

            indexGivenHours = function (givenHoursArray) {
                // Store given enabledHours and disabledHours as keys.
                // This way we can check their existence in O(1) time instead of looping through whole array.
                // (for example: options.enabledHours['2014-02-27'] === true)
                var givenHoursIndexed = {};
                $.each(givenHoursArray, function () {
                    givenHoursIndexed[this] = true;
                });
                return (Object.keys(givenHoursIndexed).length) ? givenHoursIndexed : false;
            },

            initFormatting = function () {
                var format = options.format || 'L LT';

                actualFormat = format.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput) {
                    var newinput = date.localeData().longDateFormat(formatInput) || formatInput;
                    return newinput.replace(/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g, function (formatInput2) { //temp fix for #740
                        return date.localeData().longDateFormat(formatInput2) || formatInput2;
                    });
                });


                parseFormats = options.extraFormats ? options.extraFormats.slice() : [];
                if (parseFormats.indexOf(format) < 0 && parseFormats.indexOf(actualFormat) < 0) {
                    parseFormats.push(actualFormat);
                }

                use24Hours = (actualFormat.toLowerCase().indexOf('a') < 1 && actualFormat.replace(/\[.*?\]/g, '').indexOf('h') < 1);

                if (isEnabled('y')) {
                    minViewModeNumber = 2;
                }
                if (isEnabled('M')) {
                    minViewModeNumber = 1;
                }
                if (isEnabled('d')) {
                    minViewModeNumber = 0;
                }

                currentViewMode = Math.max(minViewModeNumber, currentViewMode);

                if (!unset) {
                    setValue(date);
                }
            };

        /********************************************************************************
         *
         * Public API functions
         * =====================
         *
         * Important: Do not expose direct references to private objects or the options
         * object to the outer world. Always return a clone when returning values or make
         * a clone when setting a private variable.
         *
         ********************************************************************************/
        picker.destroy = function () {
            ///<summary>Destroys the widget and removes all attached event listeners</summary>
            hide();
            detachDatePickerElementEvents();
            element.removeData('DateTimePicker');
            element.removeData('date');
        };

        picker.toggle = toggle;

        picker.show = show;

        picker.hide = hide;

        picker.disable = function () {
            ///<summary>Disables the input element, the component is attached to, by adding a disabled="true" attribute to it.
            ///If the widget was visible before that call it is hidden. Possibly emits dp.hide</summary>
            hide();
            if (component && component.hasClass('btn')) {
                component.addClass('disabled');
            }
            input.prop('disabled', true);
            return picker;
        };

        picker.enable = function () {
            ///<summary>Enables the input element, the component is attached to, by removing disabled attribute from it.</summary>
            if (component && component.hasClass('btn')) {
                component.removeClass('disabled');
            }
            input.prop('disabled', false);
            return picker;
        };

        picker.ignoreReadonly = function (ignoreReadonly) {
            if (arguments.length === 0) {
                return options.ignoreReadonly;
            }
            if (typeof ignoreReadonly !== 'boolean') {
                throw new TypeError('ignoreReadonly () expects a boolean parameter');
            }
            options.ignoreReadonly = ignoreReadonly;
            return picker;
        };

        picker.options = function (newOptions) {
            if (arguments.length === 0) {
                return $.extend(true, {}, options);
            }

            if (!(newOptions instanceof Object)) {
                throw new TypeError('options() options parameter should be an object');
            }
            $.extend(true, options, newOptions);
            $.each(options, function (key, value) {
                if (picker[key] !== undefined) {
                    picker[key](value);
                } else {
                    throw new TypeError('option ' + key + ' is not recognized!');
                }
            });
            return picker;
        };

        picker.date = function (newDate) {
            ///<signature helpKeyword="$.fn.datetimepicker.date">
            ///<summary>Returns the component's model current date, a moment object or null if not set.</summary>
            ///<returns type="Moment">date.clone()</returns>
            ///</signature>
            ///<signature>
            ///<summary>Sets the components model current moment to it. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration.</summary>
            ///<param name="newDate" locid="$.fn.datetimepicker.date_p:newDate">Takes string, Date, moment, null parameter.</param>
            ///</signature>
            if (arguments.length === 0) {
                if (unset) {
                    return null;
                }
                return date.clone();
            }

            if (newDate !== null && typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) {
                throw new TypeError('date() parameter must be one of [null, string, moment or Date]');
            }

            setValue(newDate === null ? null : parseInputDate(newDate));
            return picker;
        };

        picker.format = function (newFormat) {
            ///<summary>test su</summary>
            ///<param name="newFormat">info about para</param>
            ///<returns type="string|boolean">returns foo</returns>
            if (arguments.length === 0) {
                return options.format;
            }

            if ((typeof newFormat !== 'string') && ((typeof newFormat !== 'boolean') || (newFormat !== false))) {
                throw new TypeError('format() expects a string or boolean:false parameter ' + newFormat);
            }

            options.format = newFormat;
            if (actualFormat) {
                initFormatting(); // reinit formatting
            }
            return picker;
        };

        picker.timeZone = function (newZone) {
            if (arguments.length === 0) {
                return options.timeZone;
            }

            if (typeof newZone !== 'string') {
                throw new TypeError('newZone() expects a string parameter');
            }

            options.timeZone = newZone;

            return picker;
        };

        picker.dayViewHeaderFormat = function (newFormat) {
            if (arguments.length === 0) {
                return options.dayViewHeaderFormat;
            }

            if (typeof newFormat !== 'string') {
                throw new TypeError('dayViewHeaderFormat() expects a string parameter');
            }

            options.dayViewHeaderFormat = newFormat;
            return picker;
        };

        picker.extraFormats = function (formats) {
            if (arguments.length === 0) {
                return options.extraFormats;
            }

            if (formats !== false && !(formats instanceof Array)) {
                throw new TypeError('extraFormats() expects an array or false parameter');
            }

            options.extraFormats = formats;
            if (parseFormats) {
                initFormatting(); // reinit formatting
            }
            return picker;
        };

        picker.disabledDates = function (dates) {
            ///<signature helpKeyword="$.fn.datetimepicker.disabledDates">
            ///<summary>Returns an array with the currently set disabled dates on the component.</summary>
            ///<returns type="array">options.disabledDates</returns>
            ///</signature>
            ///<signature>
            ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
            ///options.enabledDates if such exist.</summary>
            ///<param name="dates" locid="$.fn.datetimepicker.disabledDates_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
            ///</signature>
            if (arguments.length === 0) {
                return (options.disabledDates ? $.extend({}, options.disabledDates) : options.disabledDates);
            }

            if (!dates) {
                options.disabledDates = false;
                update();
                return picker;
            }
            if (!(dates instanceof Array)) {
                throw new TypeError('disabledDates() expects an array parameter');
            }
            options.disabledDates = indexGivenDates(dates);
            options.enabledDates = false;
            update();
            return picker;
        };

        picker.enabledDates = function (dates) {
            ///<signature helpKeyword="$.fn.datetimepicker.enabledDates">
            ///<summary>Returns an array with the currently set enabled dates on the component.</summary>
            ///<returns type="array">options.enabledDates</returns>
            ///</signature>
            ///<signature>
            ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledDates if such exist.</summary>
            ///<param name="dates" locid="$.fn.datetimepicker.enabledDates_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
            ///</signature>
            if (arguments.length === 0) {
                return (options.enabledDates ? $.extend({}, options.enabledDates) : options.enabledDates);
            }

            if (!dates) {
                options.enabledDates = false;
                update();
                return picker;
            }
            if (!(dates instanceof Array)) {
                throw new TypeError('enabledDates() expects an array parameter');
            }
            options.enabledDates = indexGivenDates(dates);
            options.disabledDates = false;
            update();
            return picker;
        };

        picker.daysOfWeekDisabled = function (daysOfWeekDisabled) {
            if (arguments.length === 0) {
                return options.daysOfWeekDisabled.splice(0);
            }

            if ((typeof daysOfWeekDisabled === 'boolean') && !daysOfWeekDisabled) {
                options.daysOfWeekDisabled = false;
                update();
                return picker;
            }

            if (!(daysOfWeekDisabled instanceof Array)) {
                throw new TypeError('daysOfWeekDisabled() expects an array parameter');
            }
            options.daysOfWeekDisabled = daysOfWeekDisabled.reduce(function (previousValue, currentValue) {
                currentValue = parseInt(currentValue, 10);
                if (currentValue > 6 || currentValue < 0 || isNaN(currentValue)) {
                    return previousValue;
                }
                if (previousValue.indexOf(currentValue) === -1) {
                    previousValue.push(currentValue);
                }
                return previousValue;
            }, []).sort();
            if (options.useCurrent && !options.keepInvalid) {
                var tries = 0;
                while (!isValid(date, 'd')) {
                    date.add(1, 'd');
                    if (tries === 31) {
                        throw 'Tried 31 times to find a valid date';
                    }
                    tries++;
                }
                setValue(date);
            }
            update();
            return picker;
        };

        picker.maxDate = function (maxDate) {
            if (arguments.length === 0) {
                return options.maxDate ? options.maxDate.clone() : options.maxDate;
            }

            if ((typeof maxDate === 'boolean') && maxDate === false) {
                options.maxDate = false;
                update();
                return picker;
            }

            if (typeof maxDate === 'string') {
                if (maxDate === 'now' || maxDate === 'moment') {
                    maxDate = getMoment();
                }
            }

            var parsedDate = parseInputDate(maxDate);

            if (!parsedDate.isValid()) {
                throw new TypeError('maxDate() Could not parse date parameter: ' + maxDate);
            }
            if (options.minDate && parsedDate.isBefore(options.minDate)) {
                throw new TypeError('maxDate() date parameter is before options.minDate: ' + parsedDate.format(actualFormat));
            }
            options.maxDate = parsedDate;
            if (options.useCurrent && !options.keepInvalid && date.isAfter(maxDate)) {
                setValue(options.maxDate);
            }
            if (viewDate.isAfter(parsedDate)) {
                viewDate = parsedDate.clone().subtract(options.stepping, 'm');
            }
            update();
            return picker;
        };

        picker.minDate = function (minDate) {
            if (arguments.length === 0) {
                return options.minDate ? options.minDate.clone() : options.minDate;
            }

            if ((typeof minDate === 'boolean') && minDate === false) {
                options.minDate = false;
                update();
                return picker;
            }

            if (typeof minDate === 'string') {
                if (minDate === 'now' || minDate === 'moment') {
                    minDate = getMoment();
                }
            }

            var parsedDate = parseInputDate(minDate);

            if (!parsedDate.isValid()) {
                throw new TypeError('minDate() Could not parse date parameter: ' + minDate);
            }
            if (options.maxDate && parsedDate.isAfter(options.maxDate)) {
                throw new TypeError('minDate() date parameter is after options.maxDate: ' + parsedDate.format(actualFormat));
            }
            options.minDate = parsedDate;
            if (options.useCurrent && !options.keepInvalid && date.isBefore(minDate)) {
                setValue(options.minDate);
            }
            if (viewDate.isBefore(parsedDate)) {
                viewDate = parsedDate.clone().add(options.stepping, 'm');
            }
            update();
            return picker;
        };

        picker.defaultDate = function (defaultDate) {
            ///<signature helpKeyword="$.fn.datetimepicker.defaultDate">
            ///<summary>Returns a moment with the options.defaultDate option configuration or false if not set</summary>
            ///<returns type="Moment">date.clone()</returns>
            ///</signature>
            ///<signature>
            ///<summary>Will set the picker's inital date. If a boolean:false value is passed the options.defaultDate parameter is cleared.</summary>
            ///<param name="defaultDate" locid="$.fn.datetimepicker.defaultDate_p:defaultDate">Takes a string, Date, moment, boolean:false</param>
            ///</signature>
            if (arguments.length === 0) {
                return options.defaultDate ? options.defaultDate.clone() : options.defaultDate;
            }
            if (!defaultDate) {
                options.defaultDate = false;
                return picker;
            }

            if (typeof defaultDate === 'string') {
                if (defaultDate === 'now' || defaultDate === 'moment') {
                    defaultDate = getMoment();
                } else {
                    defaultDate = getMoment(defaultDate);
                }
            }

            var parsedDate = parseInputDate(defaultDate);
            if (!parsedDate.isValid()) {
                throw new TypeError('defaultDate() Could not parse date parameter: ' + defaultDate);
            }
            if (!isValid(parsedDate)) {
                throw new TypeError('defaultDate() date passed is invalid according to component setup validations');
            }

            options.defaultDate = parsedDate;

            if ((options.defaultDate && options.inline) || input.val().trim() === '') {
                setValue(options.defaultDate);
            }
            return picker;
        };

        picker.locale = function (locale) {
            if (arguments.length === 0) {
                return options.locale;
            }

            if (!moment.localeData(locale)) {
                throw new TypeError('locale() locale ' + locale + ' is not loaded from moment locales!');
            }

            options.locale = locale;
            date.locale(options.locale);
            viewDate.locale(options.locale);

            if (actualFormat) {
                initFormatting(); // reinit formatting
            }
            if (widget) {
                hide();
                show();
            }
            return picker;
        };

        picker.stepping = function (stepping) {
            if (arguments.length === 0) {
                return options.stepping;
            }

            stepping = parseInt(stepping, 10);
            if (isNaN(stepping) || stepping < 1) {
                stepping = 1;
            }
            options.stepping = stepping;
            return picker;
        };

        picker.useCurrent = function (useCurrent) {
            var useCurrentOptions = ['year', 'month', 'day', 'hour', 'minute'];
            if (arguments.length === 0) {
                return options.useCurrent;
            }

            if ((typeof useCurrent !== 'boolean') && (typeof useCurrent !== 'string')) {
                throw new TypeError('useCurrent() expects a boolean or string parameter');
            }
            if (typeof useCurrent === 'string' && useCurrentOptions.indexOf(useCurrent.toLowerCase()) === -1) {
                throw new TypeError('useCurrent() expects a string parameter of ' + useCurrentOptions.join(', '));
            }
            options.useCurrent = useCurrent;
            return picker;
        };

        picker.collapse = function (collapse) {
            if (arguments.length === 0) {
                return options.collapse;
            }

            if (typeof collapse !== 'boolean') {
                throw new TypeError('collapse() expects a boolean parameter');
            }
            if (options.collapse === collapse) {
                return picker;
            }
            options.collapse = collapse;
            if (widget) {
                hide();
                show();
            }
            return picker;
        };

        picker.icons = function (icons) {
            if (arguments.length === 0) {
                return $.extend({}, options.icons);
            }

            if (!(icons instanceof Object)) {
                throw new TypeError('icons() expects parameter to be an Object');
            }
            $.extend(options.icons, icons);
            if (widget) {
                hide();
                show();
            }
            return picker;
        };

        picker.tooltips = function (tooltips) {
            if (arguments.length === 0) {
                return $.extend({}, options.tooltips);
            }

            if (!(tooltips instanceof Object)) {
                throw new TypeError('tooltips() expects parameter to be an Object');
            }
            $.extend(options.tooltips, tooltips);
            if (widget) {
                hide();
                show();
            }
            return picker;
        };

        picker.useStrict = function (useStrict) {
            if (arguments.length === 0) {
                return options.useStrict;
            }

            if (typeof useStrict !== 'boolean') {
                throw new TypeError('useStrict() expects a boolean parameter');
            }
            options.useStrict = useStrict;
            return picker;
        };

        picker.sideBySide = function (sideBySide) {
            if (arguments.length === 0) {
                return options.sideBySide;
            }

            if (typeof sideBySide !== 'boolean') {
                throw new TypeError('sideBySide() expects a boolean parameter');
            }
            options.sideBySide = sideBySide;
            if (widget) {
                hide();
                show();
            }
            return picker;
        };

        picker.viewMode = function (viewMode) {
            if (arguments.length === 0) {
                return options.viewMode;
            }

            if (typeof viewMode !== 'string') {
                throw new TypeError('viewMode() expects a string parameter');
            }

            if (viewModes.indexOf(viewMode) === -1) {
                throw new TypeError('viewMode() parameter must be one of (' + viewModes.join(', ') + ') value');
            }

            options.viewMode = viewMode;
            currentViewMode = Math.max(viewModes.indexOf(viewMode), minViewModeNumber);

            showMode();
            return picker;
        };

        picker.toolbarPlacement = function (toolbarPlacement) {
            if (arguments.length === 0) {
                return options.toolbarPlacement;
            }

            if (typeof toolbarPlacement !== 'string') {
                throw new TypeError('toolbarPlacement() expects a string parameter');
            }
            if (toolbarPlacements.indexOf(toolbarPlacement) === -1) {
                throw new TypeError('toolbarPlacement() parameter must be one of (' + toolbarPlacements.join(', ') + ') value');
            }
            options.toolbarPlacement = toolbarPlacement;

            if (widget) {
                hide();
                show();
            }
            return picker;
        };

        picker.widgetPositioning = function (widgetPositioning) {
            if (arguments.length === 0) {
                return $.extend({}, options.widgetPositioning);
            }

            if (({}).toString.call(widgetPositioning) !== '[object Object]') {
                throw new TypeError('widgetPositioning() expects an object variable');
            }
            if (widgetPositioning.horizontal) {
                if (typeof widgetPositioning.horizontal !== 'string') {
                    throw new TypeError('widgetPositioning() horizontal variable must be a string');
                }
                widgetPositioning.horizontal = widgetPositioning.horizontal.toLowerCase();
                if (horizontalModes.indexOf(widgetPositioning.horizontal) === -1) {
                    throw new TypeError('widgetPositioning() expects horizontal parameter to be one of (' + horizontalModes.join(', ') + ')');
                }
                options.widgetPositioning.horizontal = widgetPositioning.horizontal;
            }
            if (widgetPositioning.vertical) {
                if (typeof widgetPositioning.vertical !== 'string') {
                    throw new TypeError('widgetPositioning() vertical variable must be a string');
                }
                widgetPositioning.vertical = widgetPositioning.vertical.toLowerCase();
                if (verticalModes.indexOf(widgetPositioning.vertical) === -1) {
                    throw new TypeError('widgetPositioning() expects vertical parameter to be one of (' + verticalModes.join(', ') + ')');
                }
                options.widgetPositioning.vertical = widgetPositioning.vertical;
            }
            update();
            return picker;
        };

        picker.calendarWeeks = function (calendarWeeks) {
            if (arguments.length === 0) {
                return options.calendarWeeks;
            }

            if (typeof calendarWeeks !== 'boolean') {
                throw new TypeError('calendarWeeks() expects parameter to be a boolean value');
            }

            options.calendarWeeks = calendarWeeks;
            update();
            return picker;
        };

        picker.showTodayButton = function (showTodayButton) {
            if (arguments.length === 0) {
                return options.showTodayButton;
            }

            if (typeof showTodayButton !== 'boolean') {
                throw new TypeError('showTodayButton() expects a boolean parameter');
            }

            options.showTodayButton = showTodayButton;
            if (widget) {
                hide();
                show();
            }
            return picker;
        };

        picker.showClear = function (showClear) {
            if (arguments.length === 0) {
                return options.showClear;
            }

            if (typeof showClear !== 'boolean') {
                throw new TypeError('showClear() expects a boolean parameter');
            }

            options.showClear = showClear;
            if (widget) {
                hide();
                show();
            }
            return picker;
        };

        picker.widgetParent = function (widgetParent) {
            if (arguments.length === 0) {
                return options.widgetParent;
            }

            if (typeof widgetParent === 'string') {
                widgetParent = $(widgetParent);
            }

            if (widgetParent !== null && (typeof widgetParent !== 'string' && !(widgetParent instanceof $))) {
                throw new TypeError('widgetParent() expects a string or a jQuery object parameter');
            }

            options.widgetParent = widgetParent;
            if (widget) {
                hide();
                show();
            }
            return picker;
        };

        picker.keepOpen = function (keepOpen) {
            if (arguments.length === 0) {
                return options.keepOpen;
            }

            if (typeof keepOpen !== 'boolean') {
                throw new TypeError('keepOpen() expects a boolean parameter');
            }

            options.keepOpen = keepOpen;
            return picker;
        };

        picker.focusOnShow = function (focusOnShow) {
            if (arguments.length === 0) {
                return options.focusOnShow;
            }

            if (typeof focusOnShow !== 'boolean') {
                throw new TypeError('focusOnShow() expects a boolean parameter');
            }

            options.focusOnShow = focusOnShow;
            return picker;
        };

        picker.inline = function (inline) {
            if (arguments.length === 0) {
                return options.inline;
            }

            if (typeof inline !== 'boolean') {
                throw new TypeError('inline() expects a boolean parameter');
            }

            options.inline = inline;
            return picker;
        };

        picker.clear = function () {
            clear();
            return picker;
        };

        picker.keyBinds = function (keyBinds) {
            if (arguments.length === 0) {
                return options.keyBinds;
            }

            options.keyBinds = keyBinds;
            return picker;
        };

        picker.getMoment = function (d) {
            return getMoment(d);
        };

        picker.debug = function (debug) {
            if (typeof debug !== 'boolean') {
                throw new TypeError('debug() expects a boolean parameter');
            }

            options.debug = debug;
            return picker;
        };

        picker.allowInputToggle = function (allowInputToggle) {
            if (arguments.length === 0) {
                return options.allowInputToggle;
            }

            if (typeof allowInputToggle !== 'boolean') {
                throw new TypeError('allowInputToggle() expects a boolean parameter');
            }

            options.allowInputToggle = allowInputToggle;
            return picker;
        };

        picker.showClose = function (showClose) {
            if (arguments.length === 0) {
                return options.showClose;
            }

            if (typeof showClose !== 'boolean') {
                throw new TypeError('showClose() expects a boolean parameter');
            }

            options.showClose = showClose;
            return picker;
        };

        picker.keepInvalid = function (keepInvalid) {
            if (arguments.length === 0) {
                return options.keepInvalid;
            }

            if (typeof keepInvalid !== 'boolean') {
                throw new TypeError('keepInvalid() expects a boolean parameter');
            }
            options.keepInvalid = keepInvalid;
            return picker;
        };

        picker.datepickerInput = function (datepickerInput) {
            if (arguments.length === 0) {
                return options.datepickerInput;
            }

            if (typeof datepickerInput !== 'string') {
                throw new TypeError('datepickerInput() expects a string parameter');
            }

            options.datepickerInput = datepickerInput;
            return picker;
        };

        picker.parseInputDate = function (parseInputDate) {
            if (arguments.length === 0) {
                return options.parseInputDate;
            }

            if (typeof parseInputDate !== 'function') {
                throw new TypeError('parseInputDate() sholud be as function');
            }

            options.parseInputDate = parseInputDate;

            return picker;
        };

        picker.disabledTimeIntervals = function (disabledTimeIntervals) {
            ///<signature helpKeyword="$.fn.datetimepicker.disabledTimeIntervals">
            ///<summary>Returns an array with the currently set disabled dates on the component.</summary>
            ///<returns type="array">options.disabledTimeIntervals</returns>
            ///</signature>
            ///<signature>
            ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
            ///options.enabledDates if such exist.</summary>
            ///<param name="dates" locid="$.fn.datetimepicker.disabledTimeIntervals_p:dates">Takes an [ string or Date or moment ] of values and allows the user to select only from those days.</param>
            ///</signature>
            if (arguments.length === 0) {
                return (options.disabledTimeIntervals ? $.extend({}, options.disabledTimeIntervals) : options.disabledTimeIntervals);
            }

            if (!disabledTimeIntervals) {
                options.disabledTimeIntervals = false;
                update();
                return picker;
            }
            if (!(disabledTimeIntervals instanceof Array)) {
                throw new TypeError('disabledTimeIntervals() expects an array parameter');
            }
            options.disabledTimeIntervals = disabledTimeIntervals;
            update();
            return picker;
        };

        picker.disabledHours = function (hours) {
            ///<signature helpKeyword="$.fn.datetimepicker.disabledHours">
            ///<summary>Returns an array with the currently set disabled hours on the component.</summary>
            ///<returns type="array">options.disabledHours</returns>
            ///</signature>
            ///<signature>
            ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of
            ///options.enabledHours if such exist.</summary>
            ///<param name="hours" locid="$.fn.datetimepicker.disabledHours_p:hours">Takes an [ int ] of values and disallows the user to select only from those hours.</param>
            ///</signature>
            if (arguments.length === 0) {
                return (options.disabledHours ? $.extend({}, options.disabledHours) : options.disabledHours);
            }

            if (!hours) {
                options.disabledHours = false;
                update();
                return picker;
            }
            if (!(hours instanceof Array)) {
                throw new TypeError('disabledHours() expects an array parameter');
            }
            options.disabledHours = indexGivenHours(hours);
            options.enabledHours = false;
            if (options.useCurrent && !options.keepInvalid) {
                var tries = 0;
                while (!isValid(date, 'h')) {
                    date.add(1, 'h');
                    if (tries === 24) {
                        throw 'Tried 24 times to find a valid date';
                    }
                    tries++;
                }
                setValue(date);
            }
            update();
            return picker;
        };

        picker.enabledHours = function (hours) {
            ///<signature helpKeyword="$.fn.datetimepicker.enabledHours">
            ///<summary>Returns an array with the currently set enabled hours on the component.</summary>
            ///<returns type="array">options.enabledHours</returns>
            ///</signature>
            ///<signature>
            ///<summary>Setting this takes precedence over options.minDate, options.maxDate configuration. Also calling this function removes the configuration of options.disabledHours if such exist.</summary>
            ///<param name="hours" locid="$.fn.datetimepicker.enabledHours_p:hours">Takes an [ int ] of values and allows the user to select only from those hours.</param>
            ///</signature>
            if (arguments.length === 0) {
                return (options.enabledHours ? $.extend({}, options.enabledHours) : options.enabledHours);
            }

            if (!hours) {
                options.enabledHours = false;
                update();
                return picker;
            }
            if (!(hours instanceof Array)) {
                throw new TypeError('enabledHours() expects an array parameter');
            }
            options.enabledHours = indexGivenHours(hours);
            options.disabledHours = false;
            if (options.useCurrent && !options.keepInvalid) {
                var tries = 0;
                while (!isValid(date, 'h')) {
                    date.add(1, 'h');
                    if (tries === 24) {
                        throw 'Tried 24 times to find a valid date';
                    }
                    tries++;
                }
                setValue(date);
            }
            update();
            return picker;
        };
        /**
         * Returns the component's model current viewDate, a moment object or null if not set. Passing a null value unsets the components model current moment. Parsing of the newDate parameter is made using moment library with the options.format and options.useStrict components configuration.
         * @param {Takes string, viewDate, moment, null parameter.} newDate
         * @returns {viewDate.clone()}
         */
        picker.viewDate = function (newDate) {
            if (arguments.length === 0) {
                return viewDate.clone();
            }

            if (!newDate) {
                viewDate = date.clone();
                return picker;
            }

            if (typeof newDate !== 'string' && !moment.isMoment(newDate) && !(newDate instanceof Date)) {
                throw new TypeError('viewDate() parameter must be one of [string, moment or Date]');
            }

            viewDate = parseInputDate(newDate);
            viewUpdate();
            return picker;
        };

        // initializing element and component attributes
        if (element.is('input')) {
            input = element;
        } else {
            input = element.find(options.datepickerInput);
            if (input.length === 0) {
                input = element.find('input');
            } else if (!input.is('input')) {
                throw new Error('CSS class "' + options.datepickerInput + '" cannot be applied to non input element');
            }
        }

        if (element.hasClass('input-group')) {
            // in case there is more then one 'input-group-addon' Issue #48
            if (element.find('.datepickerbutton').length === 0) {
                component = element.find('.input-group-addon');
            } else {
                component = element.find('.datepickerbutton');
            }
        }

        if (!options.inline && !input.is('input')) {
            throw new Error('Could not initialize DateTimePicker without an input element');
        }

        // Set defaults for date here now instead of in var declaration
        date = getMoment();
        viewDate = date.clone();

        $.extend(true, options, dataToOptions());

        picker.options(options);

        initFormatting();

        attachDatePickerElementEvents();

        if (input.prop('disabled')) {
            picker.disable();
        }
        if (input.is('input') && input.val().trim().length !== 0) {
            setValue(parseInputDate(input.val().trim()));
        }
        else if (options.defaultDate && input.attr('placeholder') === undefined) {
            setValue(options.defaultDate);
        }
        if (options.inline) {
            show();
        }
        return picker;
    };

    /********************************************************************************
     *
     * jQuery plugin constructor and defaults object
     *
     ********************************************************************************/

    /**
    * See (http://jquery.com/).
    * @name jQuery
    * @class
    * See the jQuery Library  (http://jquery.com/) for full details.  This just
    * documents the function and classes that are added to jQuery by this plug-in.
    */
    /**
     * See (http://jquery.com/)
     * @name fn
     * @class
     * See the jQuery Library  (http://jquery.com/) for full details.  This just
     * documents the function and classes that are added to jQuery by this plug-in.
     * @memberOf jQuery
     */
    /**
     * Show comments
     * @class datetimepicker
     * @memberOf jQuery.fn
     */
    $.fn.datetimepicker = function (options) {
        options = options || {};

        var args = Array.prototype.slice.call(arguments, 1),
            isInstance = true,
            thisMethods = ['destroy', 'hide', 'show', 'toggle'],
            returnValue;

        if (typeof options === 'object') {
            return this.each(function () {
                var $this = $(this),
                    _options;
                if (!$this.data('DateTimePicker')) {
                    // create a private copy of the defaults object
                    _options = $.extend(true, {}, $.fn.datetimepicker.defaults, options);
                    $this.data('DateTimePicker', dateTimePicker($this, _options));
                }
            });
        } else if (typeof options === 'string') {
            this.each(function () {
                var $this = $(this),
                    instance = $this.data('DateTimePicker');
                if (!instance) {
                    throw new Error('bootstrap-datetimepicker("' + options + '") method was called on an element that is not using DateTimePicker');
                }

                returnValue = instance[options].apply(instance, args);
                isInstance = returnValue === instance;
            });

            if (isInstance || $.inArray(options, thisMethods) > -1) {
                return this;
            }

            return returnValue;
        }

        throw new TypeError('Invalid arguments for DateTimePicker: ' + options);
    };

    $.fn.datetimepicker.defaults = {
        timeZone: '',
        format: false,
        dayViewHeaderFormat: 'MMMM YYYY',
        extraFormats: false,
        stepping: 1,
        minDate: false,
        maxDate: false,
        useCurrent: true,
        collapse: true,
        locale: moment.locale(),
        defaultDate: false,
        disabledDates: false,
        enabledDates: false,
        icons: {
            time: 'glyphicon glyphicon-time',
            date: 'glyphicon glyphicon-calendar',
            up: 'glyphicon glyphicon-chevron-up',
            down: 'glyphicon glyphicon-chevron-down',
            previous: 'glyphicon glyphicon-chevron-left',
            next: 'glyphicon glyphicon-chevron-right',
            today: 'glyphicon glyphicon-screenshot',
            clear: 'glyphicon glyphicon-trash',
            close: 'glyphicon glyphicon-remove'
        },
        tooltips: {
            today: 'Go to today',
            clear: 'Clear selection',
            close: 'Close the picker',
            selectMonth: 'Select Month',
            prevMonth: 'Previous Month',
            nextMonth: 'Next Month',
            selectYear: 'Select Year',
            prevYear: 'Previous Year',
            nextYear: 'Next Year',
            selectDecade: 'Select Decade',
            prevDecade: 'Previous Decade',
            nextDecade: 'Next Decade',
            prevCentury: 'Previous Century',
            nextCentury: 'Next Century',
            pickHour: 'Pick Hour',
            incrementHour: 'Increment Hour',
            decrementHour: 'Decrement Hour',
            pickMinute: 'Pick Minute',
            incrementMinute: 'Increment Minute',
            decrementMinute: 'Decrement Minute',
            pickSecond: 'Pick Second',
            incrementSecond: 'Increment Second',
            decrementSecond: 'Decrement Second',
            togglePeriod: 'Toggle Period',
            selectTime: 'Select Time'
        },
        useStrict: false,
        sideBySide: false,
        daysOfWeekDisabled: false,
        calendarWeeks: false,
        viewMode: 'days',
        toolbarPlacement: 'default',
        showTodayButton: false,
        showClear: false,
        showClose: false,
        widgetPositioning: {
            horizontal: 'auto',
            vertical: 'auto'
        },
        widgetParent: null,
        ignoreReadonly: false,
        keepOpen: false,
        focusOnShow: true,
        inline: false,
        keepInvalid: false,
        datepickerInput: '.datepickerinput',
        keyBinds: {
            up: function (widget) {
                if (!widget) {
                    return;
                }
                var d = this.date() || this.getMoment();
                if (widget.find('.datepicker').is(':visible')) {
                    this.date(d.clone().subtract(7, 'd'));
                } else {
                    this.date(d.clone().add(this.stepping(), 'm'));
                }
            },
            down: function (widget) {
                if (!widget) {
                    this.show();
                    return;
                }
                var d = this.date() || this.getMoment();
                if (widget.find('.datepicker').is(':visible')) {
                    this.date(d.clone().add(7, 'd'));
                } else {
                    this.date(d.clone().subtract(this.stepping(), 'm'));
                }
            },
            'control up': function (widget) {
                if (!widget) {
                    return;
                }
                var d = this.date() || this.getMoment();
                if (widget.find('.datepicker').is(':visible')) {
                    this.date(d.clone().subtract(1, 'y'));
                } else {
                    this.date(d.clone().add(1, 'h'));
                }
            },
            'control down': function (widget) {
                if (!widget) {
                    return;
                }
                var d = this.date() || this.getMoment();
                if (widget.find('.datepicker').is(':visible')) {
                    this.date(d.clone().add(1, 'y'));
                } else {
                    this.date(d.clone().subtract(1, 'h'));
                }
            },
            left: function (widget) {
                if (!widget) {
                    return;
                }
                var d = this.date() || this.getMoment();
                if (widget.find('.datepicker').is(':visible')) {
                    this.date(d.clone().subtract(1, 'd'));
                }
            },
            right: function (widget) {
                if (!widget) {
                    return;
                }
                var d = this.date() || this.getMoment();
                if (widget.find('.datepicker').is(':visible')) {
                    this.date(d.clone().add(1, 'd'));
                }
            },
            pageUp: function (widget) {
                if (!widget) {
                    return;
                }
                var d = this.date() || this.getMoment();
                if (widget.find('.datepicker').is(':visible')) {
                    this.date(d.clone().subtract(1, 'M'));
                }
            },
            pageDown: function (widget) {
                if (!widget) {
                    return;
                }
                var d = this.date() || this.getMoment();
                if (widget.find('.datepicker').is(':visible')) {
                    this.date(d.clone().add(1, 'M'));
                }
            },
            enter: function () {
                this.hide();
            },
            escape: function () {
                this.hide();
            },
            //tab: function (widget) { //this break the flow of the form. disabling for now
            //    var toggle = widget.find('.picker-switch a[data-action="togglePicker"]');
            //    if(toggle.length > 0) toggle.click();
            //},
            'control space': function (widget) {
                if (!widget) {
                    return;
                }
                if (widget.find('.timepicker').is(':visible')) {
                    widget.find('.btn[data-action="togglePeriod"]').click();
                }
            },
            t: function () {
                this.date(this.getMoment());
            },
            'delete': function () {
                this.clear();
            }
        },
        debug: false,
        allowInputToggle: false,
        disabledTimeIntervals: false,
        disabledHours: false,
        enabledHours: false,
        viewDate: false
    };

    return $.fn.datetimepicker;
}));
/*! DataTables 1.10.10
 * ©2008-2015 SpryMedia Ltd - datatables.net/license
 */

/**
 * @summary     DataTables
 * @description Paginate, search and order HTML tables
 * @version     1.10.10
 * @file        jquery.dataTables.js
 * @author      SpryMedia Ltd (www.sprymedia.co.uk)
 * @contact     www.sprymedia.co.uk/contact
 * @copyright   Copyright 2008-2015 SpryMedia Ltd.
 *
 * This source file is free software, available under the following license:
 *   MIT license - http://datatables.net/license
 *
 * This source file is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
 *
 * For details please refer to: http://www.datatables.net
 */

/*jslint evil: true, undef: true, browser: true */
/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/


(function( factory ) {
	"use strict";

	if ( typeof define === 'function' && define.amd ) {
		// AMD
		define( ['jquery'], function ( $ ) {
			return factory( $, window, document );
		} );
	}
	else if ( typeof exports === 'object' ) {
		// CommonJS
		module.exports = function (root, $) {
			if ( ! root ) {
				// CommonJS environments without a window global must pass a
				// root. This will give an error otherwise
				root = window;
			}

			if ( ! $ ) {
				$ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
					require('jquery') :
					require('jquery')( root );
			}

			return factory( $, root, root.document );
		};
	}
	else {
		// Browser
		factory( jQuery, window, document );
	}
}
(function( $, window, document, undefined ) {
	"use strict";

	/**
	 * DataTables is a plug-in for the jQuery Javascript library. It is a highly
	 * flexible tool, based upon the foundations of progressive enhancement,
	 * which will add advanced interaction controls to any HTML table. For a
	 * full list of features please refer to
	 * [DataTables.net](href="http://datatables.net).
	 *
	 * Note that the `DataTable` object is not a global variable but is aliased
	 * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may
	 * be  accessed.
	 *
	 *  @class
	 *  @param {object} [init={}] Configuration object for DataTables. Options
	 *    are defined by {@link DataTable.defaults}
	 *  @requires jQuery 1.7+
	 *
	 *  @example
	 *    // Basic initialisation
	 *    $(document).ready( function {
	 *      $('#example').dataTable();
	 *    } );
	 *
	 *  @example
	 *    // Initialisation with configuration options - in this case, disable
	 *    // pagination and sorting.
	 *    $(document).ready( function {
	 *      $('#example').dataTable( {
	 *        "paginate": false,
	 *        "sort": false
	 *      } );
	 *    } );
	 */
	var DataTable;

	
	/*
	 * It is useful to have variables which are scoped locally so only the
	 * DataTables functions can access them and they don't leak into global space.
	 * At the same time these functions are often useful over multiple files in the
	 * core and API, so we list, or at least document, all variables which are used
	 * by DataTables as private variables here. This also ensures that there is no
	 * clashing of variable names and that they can easily referenced for reuse.
	 */
	
	
	// Defined else where
	//  _selector_run
	//  _selector_opts
	//  _selector_first
	//  _selector_row_indexes
	
	var _ext; // DataTable.ext
	var _Api; // DataTable.Api
	var _api_register; // DataTable.Api.register
	var _api_registerPlural; // DataTable.Api.registerPlural
	
	var _re_dic = {};
	var _re_new_lines = /[\r\n]/g;
	var _re_html = /<.*?>/g;
	var _re_date_start = /^[\w\+\-]/;
	var _re_date_end = /[\w\+\-]$/;
	
	// Escape regular expression special characters
	var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' );
	
	// http://en.wikipedia.org/wiki/Foreign_exchange_market
	// - \u20BD - Russian ruble.
	// - \u20a9 - South Korean Won
	// - \u20BA - Turkish Lira
	// - \u20B9 - Indian Rupee
	// - R - Brazil (R$) and South Africa
	// - fr - Swiss Franc
	// - kr - Swedish krona, Norwegian krone and Danish krone
	// - \u2009 is thin space and \u202F is narrow no-break space, both used in many
	//   standards as thousands separators.
	var _re_formatted_numeric = /[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi;
	
	
	var _empty = function ( d ) {
		return !d || d === true || d === '-' ? true : false;
	};
	
	
	var _intVal = function ( s ) {
		var integer = parseInt( s, 10 );
		return !isNaN(integer) && isFinite(s) ? integer : null;
	};
	
	// Convert from a formatted number with characters other than `.` as the
	// decimal place, to a Javascript number
	var _numToDecimal = function ( num, decimalPoint ) {
		// Cache created regular expressions for speed as this function is called often
		if ( ! _re_dic[ decimalPoint ] ) {
			_re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' );
		}
		return typeof num === 'string' && decimalPoint !== '.' ?
			num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) :
			num;
	};
	
	
	var _isNumber = function ( d, decimalPoint, formatted ) {
		var strType = typeof d === 'string';
	
		// If empty return immediately so there must be a number if it is a
		// formatted string (this stops the string "k", or "kr", etc being detected
		// as a formatted number for currency
		if ( _empty( d ) ) {
			return true;
		}
	
		if ( decimalPoint && strType ) {
			d = _numToDecimal( d, decimalPoint );
		}
	
		if ( formatted && strType ) {
			d = d.replace( _re_formatted_numeric, '' );
		}
	
		return !isNaN( parseFloat(d) ) && isFinite( d );
	};
	
	
	// A string without HTML in it can be considered to be HTML still
	var _isHtml = function ( d ) {
		return _empty( d ) || typeof d === 'string';
	};
	
	
	var _htmlNumeric = function ( d, decimalPoint, formatted ) {
		if ( _empty( d ) ) {
			return true;
		}
	
		var html = _isHtml( d );
		return ! html ?
			null :
			_isNumber( _stripHtml( d ), decimalPoint, formatted ) ?
				true :
				null;
	};
	
	
	var _pluck = function ( a, prop, prop2 ) {
		var out = [];
		var i=0, ien=a.length;
	
		// Could have the test in the loop for slightly smaller code, but speed
		// is essential here
		if ( prop2 !== undefined ) {
			for ( ; i<ien ; i++ ) {
				if ( a[i] && a[i][ prop ] ) {
					out.push( a[i][ prop ][ prop2 ] );
				}
			}
		}
		else {
			for ( ; i<ien ; i++ ) {
				if ( a[i] ) {
					out.push( a[i][ prop ] );
				}
			}
		}
	
		return out;
	};
	
	
	// Basically the same as _pluck, but rather than looping over `a` we use `order`
	// as the indexes to pick from `a`
	var _pluck_order = function ( a, order, prop, prop2 )
	{
		var out = [];
		var i=0, ien=order.length;
	
		// Could have the test in the loop for slightly smaller code, but speed
		// is essential here
		if ( prop2 !== undefined ) {
			for ( ; i<ien ; i++ ) {
				if ( a[ order[i] ][ prop ] ) {
					out.push( a[ order[i] ][ prop ][ prop2 ] );
				}
			}
		}
		else {
			for ( ; i<ien ; i++ ) {
				out.push( a[ order[i] ][ prop ] );
			}
		}
	
		return out;
	};
	
	
	var _range = function ( len, start )
	{
		var out = [];
		var end;
	
		if ( start === undefined ) {
			start = 0;
			end = len;
		}
		else {
			end = start;
			start = len;
		}
	
		for ( var i=start ; i<end ; i++ ) {
			out.push( i );
		}
	
		return out;
	};
	
	
	var _removeEmpty = function ( a )
	{
		var out = [];
	
		for ( var i=0, ien=a.length ; i<ien ; i++ ) {
			if ( a[i] ) { // careful - will remove all falsy values!
				out.push( a[i] );
			}
		}
	
		return out;
	};
	
	
	var _stripHtml = function ( d ) {
		return d.replace( _re_html, '' );
	};
	
	
	/**
	 * Find the unique elements in a source array.
	 *
	 * @param  {array} src Source array
	 * @return {array} Array of unique items
	 * @ignore
	 */
	var _unique = function ( src )
	{
		// A faster unique method is to use object keys to identify used values,
		// but this doesn't work with arrays or objects, which we must also
		// consider. See jsperf.com/compare-array-unique-versions/4 for more
		// information.
		var
			out = [],
			val,
			i, ien=src.length,
			j, k=0;
	
		again: for ( i=0 ; i<ien ; i++ ) {
			val = src[i];
	
			for ( j=0 ; j<k ; j++ ) {
				if ( out[j] === val ) {
					continue again;
				}
			}
	
			out.push( val );
			k++;
		}
	
		return out;
	};
	
	
	
	/**
	 * Create a mapping object that allows camel case parameters to be looked up
	 * for their Hungarian counterparts. The mapping is stored in a private
	 * parameter called `_hungarianMap` which can be accessed on the source object.
	 *  @param {object} o
	 *  @memberof DataTable#oApi
	 */
	function _fnHungarianMap ( o )
	{
		var
			hungarian = 'a aa ai ao as b fn i m o s ',
			match,
			newKey,
			map = {};
	
		$.each( o, function (key, val) {
			match = key.match(/^([^A-Z]+?)([A-Z])/);
	
			if ( match && hungarian.indexOf(match[1]+' ') !== -1 )
			{
				newKey = key.replace( match[0], match[2].toLowerCase() );
				map[ newKey ] = key;
	
				if ( match[1] === 'o' )
				{
					_fnHungarianMap( o[key] );
				}
			}
		} );
	
		o._hungarianMap = map;
	}
	
	
	/**
	 * Convert from camel case parameters to Hungarian, based on a Hungarian map
	 * created by _fnHungarianMap.
	 *  @param {object} src The model object which holds all parameters that can be
	 *    mapped.
	 *  @param {object} user The object to convert from camel case to Hungarian.
	 *  @param {boolean} force When set to `true`, properties which already have a
	 *    Hungarian value in the `user` object will be overwritten. Otherwise they
	 *    won't be.
	 *  @memberof DataTable#oApi
	 */
	function _fnCamelToHungarian ( src, user, force )
	{
		if ( ! src._hungarianMap ) {
			_fnHungarianMap( src );
		}
	
		var hungarianKey;
	
		$.each( user, function (key, val) {
			hungarianKey = src._hungarianMap[ key ];
	
			if ( hungarianKey !== undefined && (force || user[hungarianKey] === undefined) )
			{
				// For objects, we need to buzz down into the object to copy parameters
				if ( hungarianKey.charAt(0) === 'o' )
				{
					// Copy the camelCase options over to the hungarian
					if ( ! user[ hungarianKey ] ) {
						user[ hungarianKey ] = {};
					}
					$.extend( true, user[hungarianKey], user[key] );
	
					_fnCamelToHungarian( src[hungarianKey], user[hungarianKey], force );
				}
				else {
					user[hungarianKey] = user[ key ];
				}
			}
		} );
	}
	
	
	/**
	 * Language compatibility - when certain options are given, and others aren't, we
	 * need to duplicate the values over, in order to provide backwards compatibility
	 * with older language files.
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnLanguageCompat( lang )
	{
		var defaults = DataTable.defaults.oLanguage;
		var zeroRecords = lang.sZeroRecords;
	
		/* Backwards compatibility - if there is no sEmptyTable given, then use the same as
		 * sZeroRecords - assuming that is given.
		 */
		if ( ! lang.sEmptyTable && zeroRecords &&
			defaults.sEmptyTable === "No data available in table" )
		{
			_fnMap( lang, lang, 'sZeroRecords', 'sEmptyTable' );
		}
	
		/* Likewise with loading records */
		if ( ! lang.sLoadingRecords && zeroRecords &&
			defaults.sLoadingRecords === "Loading..." )
		{
			_fnMap( lang, lang, 'sZeroRecords', 'sLoadingRecords' );
		}
	
		// Old parameter name of the thousands separator mapped onto the new
		if ( lang.sInfoThousands ) {
			lang.sThousands = lang.sInfoThousands;
		}
	
		var decimal = lang.sDecimal;
		if ( decimal ) {
			_addNumericSort( decimal );
		}
	}
	
	
	/**
	 * Map one parameter onto another
	 *  @param {object} o Object to map
	 *  @param {*} knew The new parameter name
	 *  @param {*} old The old parameter name
	 */
	var _fnCompatMap = function ( o, knew, old ) {
		if ( o[ knew ] !== undefined ) {
			o[ old ] = o[ knew ];
		}
	};
	
	
	/**
	 * Provide backwards compatibility for the main DT options. Note that the new
	 * options are mapped onto the old parameters, so this is an external interface
	 * change only.
	 *  @param {object} init Object to map
	 */
	function _fnCompatOpts ( init )
	{
		_fnCompatMap( init, 'ordering',      'bSort' );
		_fnCompatMap( init, 'orderMulti',    'bSortMulti' );
		_fnCompatMap( init, 'orderClasses',  'bSortClasses' );
		_fnCompatMap( init, 'orderCellsTop', 'bSortCellsTop' );
		_fnCompatMap( init, 'order',         'aaSorting' );
		_fnCompatMap( init, 'orderFixed',    'aaSortingFixed' );
		_fnCompatMap( init, 'paging',        'bPaginate' );
		_fnCompatMap( init, 'pagingType',    'sPaginationType' );
		_fnCompatMap( init, 'pageLength',    'iDisplayLength' );
		_fnCompatMap( init, 'searching',     'bFilter' );
	
		// Boolean initialisation of x-scrolling
		if ( typeof init.sScrollX === 'boolean' ) {
			init.sScrollX = init.sScrollX ? '100%' : '';
		}
		if ( typeof init.scrollX === 'boolean' ) {
			init.scrollX = init.scrollX ? '100%' : '';
		}
	
		// Column search objects are in an array, so it needs to be converted
		// element by element
		var searchCols = init.aoSearchCols;
	
		if ( searchCols ) {
			for ( var i=0, ien=searchCols.length ; i<ien ; i++ ) {
				if ( searchCols[i] ) {
					_fnCamelToHungarian( DataTable.models.oSearch, searchCols[i] );
				}
			}
		}
	}
	
	
	/**
	 * Provide backwards compatibility for column options. Note that the new options
	 * are mapped onto the old parameters, so this is an external interface change
	 * only.
	 *  @param {object} init Object to map
	 */
	function _fnCompatCols ( init )
	{
		_fnCompatMap( init, 'orderable',     'bSortable' );
		_fnCompatMap( init, 'orderData',     'aDataSort' );
		_fnCompatMap( init, 'orderSequence', 'asSorting' );
		_fnCompatMap( init, 'orderDataType', 'sortDataType' );
	
		// orderData can be given as an integer
		var dataSort = init.aDataSort;
		if ( dataSort && ! $.isArray( dataSort ) ) {
			init.aDataSort = [ dataSort ];
		}
	}
	
	
	/**
	 * Browser feature detection for capabilities, quirks
	 *  @param {object} settings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnBrowserDetect( settings )
	{
		// We don't need to do this every time DataTables is constructed, the values
		// calculated are specific to the browser and OS configuration which we
		// don't expect to change between initialisations
		if ( ! DataTable.__browser ) {
			var browser = {};
			DataTable.__browser = browser;
	
			// Scrolling feature / quirks detection
			var n = $('<div/>')
				.css( {
					position: 'fixed',
					top: 0,
					left: 0,
					height: 1,
					width: 1,
					overflow: 'hidden'
				} )
				.append(
					$('<div/>')
						.css( {
							position: 'absolute',
							top: 1,
							left: 1,
							width: 100,
							overflow: 'scroll'
						} )
						.append(
							$('<div/>')
								.css( {
									width: '100%',
									height: 10
								} )
						)
				)
				.appendTo( 'body' );
	
			var outer = n.children();
			var inner = outer.children();
	
			// Numbers below, in order, are:
			// inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth
			//
			// IE6 XP:                           100 100 100  83
			// IE7 Vista:                        100 100 100  83
			// IE 8+ Windows:                     83  83 100  83
			// Evergreen Windows:                 83  83 100  83
			// Evergreen Mac with scrollbars:     85  85 100  85
			// Evergreen Mac without scrollbars: 100 100 100 100
	
			// Get scrollbar width
			browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth;
	
			// IE6/7 will oversize a width 100% element inside a scrolling element, to
			// include the width of the scrollbar, while other browsers ensure the inner
			// element is contained without forcing scrolling
			browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100;
	
			// In rtl text layout, some browsers (most, but not all) will place the
			// scrollbar on the left, rather than the right.
			browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1;
	
			// IE8- don't provide height and width for getBoundingClientRect
			browser.bBounding = n[0].getBoundingClientRect().width ? true : false;
	
			n.remove();
		}
	
		$.extend( settings.oBrowser, DataTable.__browser );
		settings.oScroll.iBarWidth = DataTable.__browser.barWidth;
	}
	
	
	/**
	 * Array.prototype reduce[Right] method, used for browsers which don't support
	 * JS 1.6. Done this way to reduce code size, since we iterate either way
	 *  @param {object} settings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnReduce ( that, fn, init, start, end, inc )
	{
		var
			i = start,
			value,
			isSet = false;
	
		if ( init !== undefined ) {
			value = init;
			isSet = true;
		}
	
		while ( i !== end ) {
			if ( ! that.hasOwnProperty(i) ) {
				continue;
			}
	
			value = isSet ?
				fn( value, that[i], i, that ) :
				that[i];
	
			isSet = true;
			i += inc;
		}
	
		return value;
	}
	
	/**
	 * Add a column to the list used for the table with default values
	 *  @param {object} oSettings dataTables settings object
	 *  @param {node} nTh The th element for this column
	 *  @memberof DataTable#oApi
	 */
	function _fnAddColumn( oSettings, nTh )
	{
		// Add column to aoColumns array
		var oDefaults = DataTable.defaults.column;
		var iCol = oSettings.aoColumns.length;
		var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, {
			"nTh": nTh ? nTh : document.createElement('th'),
			"sTitle":    oDefaults.sTitle    ? oDefaults.sTitle    : nTh ? nTh.innerHTML : '',
			"aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol],
			"mData": oDefaults.mData ? oDefaults.mData : iCol,
			idx: iCol
		} );
		oSettings.aoColumns.push( oCol );
	
		// Add search object for column specific search. Note that the `searchCols[ iCol ]`
		// passed into extend can be undefined. This allows the user to give a default
		// with only some of the parameters defined, and also not give a default
		var searchCols = oSettings.aoPreSearchCols;
		searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] );
	
		// Use the default column options function to initialise classes etc
		_fnColumnOptions( oSettings, iCol, $(nTh).data() );
	}
	
	
	/**
	 * Apply options for a column
	 *  @param {object} oSettings dataTables settings object
	 *  @param {int} iCol column index to consider
	 *  @param {object} oOptions object with sType, bVisible and bSearchable etc
	 *  @memberof DataTable#oApi
	 */
	function _fnColumnOptions( oSettings, iCol, oOptions )
	{
		var oCol = oSettings.aoColumns[ iCol ];
		var oClasses = oSettings.oClasses;
		var th = $(oCol.nTh);
	
		// Try to get width information from the DOM. We can't get it from CSS
		// as we'd need to parse the CSS stylesheet. `width` option can override
		if ( ! oCol.sWidthOrig ) {
			// Width attribute
			oCol.sWidthOrig = th.attr('width') || null;
	
			// Style attribute
			var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/);
			if ( t ) {
				oCol.sWidthOrig = t[1];
			}
		}
	
		/* User specified column options */
		if ( oOptions !== undefined && oOptions !== null )
		{
			// Backwards compatibility
			_fnCompatCols( oOptions );
	
			// Map camel case parameters to their Hungarian counterparts
			_fnCamelToHungarian( DataTable.defaults.column, oOptions );
	
			/* Backwards compatibility for mDataProp */
			if ( oOptions.mDataProp !== undefined && !oOptions.mData )
			{
				oOptions.mData = oOptions.mDataProp;
			}
	
			if ( oOptions.sType )
			{
				oCol._sManualType = oOptions.sType;
			}
	
			// `class` is a reserved word in Javascript, so we need to provide
			// the ability to use a valid name for the camel case input
			if ( oOptions.className && ! oOptions.sClass )
			{
				oOptions.sClass = oOptions.className;
			}
	
			$.extend( oCol, oOptions );
			_fnMap( oCol, oOptions, "sWidth", "sWidthOrig" );
	
			/* iDataSort to be applied (backwards compatibility), but aDataSort will take
			 * priority if defined
			 */
			if ( oOptions.iDataSort !== undefined )
			{
				oCol.aDataSort = [ oOptions.iDataSort ];
			}
			_fnMap( oCol, oOptions, "aDataSort" );
		}
	
		/* Cache the data get and set functions for speed */
		var mDataSrc = oCol.mData;
		var mData = _fnGetObjectDataFn( mDataSrc );
		var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null;
	
		var attrTest = function( src ) {
			return typeof src === 'string' && src.indexOf('@') !== -1;
		};
		oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && (
			attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter)
		);
	
		oCol.fnGetData = function (rowData, type, meta) {
			var innerData = mData( rowData, type, undefined, meta );
	
			return mRender && type ?
				mRender( innerData, type, rowData, meta ) :
				innerData;
		};
		oCol.fnSetData = function ( rowData, val, meta ) {
			return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta );
		};
	
		// Indicate if DataTables should read DOM data as an object or array
		// Used in _fnGetRowElements
		if ( typeof mDataSrc !== 'number' ) {
			oSettings._rowReadObject = true;
		}
	
		/* Feature sorting overrides column specific when off */
		if ( !oSettings.oFeatures.bSort )
		{
			oCol.bSortable = false;
			th.addClass( oClasses.sSortableNone ); // Have to add class here as order event isn't called
		}
	
		/* Check that the class assignment is correct for sorting */
		var bAsc = $.inArray('asc', oCol.asSorting) !== -1;
		var bDesc = $.inArray('desc', oCol.asSorting) !== -1;
		if ( !oCol.bSortable || (!bAsc && !bDesc) )
		{
			oCol.sSortingClass = oClasses.sSortableNone;
			oCol.sSortingClassJUI = "";
		}
		else if ( bAsc && !bDesc )
		{
			oCol.sSortingClass = oClasses.sSortableAsc;
			oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed;
		}
		else if ( !bAsc && bDesc )
		{
			oCol.sSortingClass = oClasses.sSortableDesc;
			oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed;
		}
		else
		{
			oCol.sSortingClass = oClasses.sSortable;
			oCol.sSortingClassJUI = oClasses.sSortJUI;
		}
	}
	
	
	/**
	 * Adjust the table column widths for new data. Note: you would probably want to
	 * do a redraw after calling this function!
	 *  @param {object} settings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnAdjustColumnSizing ( settings )
	{
		/* Not interested in doing column width calculation if auto-width is disabled */
		if ( settings.oFeatures.bAutoWidth !== false )
		{
			var columns = settings.aoColumns;
	
			_fnCalculateColumnWidths( settings );
			for ( var i=0 , iLen=columns.length ; i<iLen ; i++ )
			{
				columns[i].nTh.style.width = columns[i].sWidth;
			}
		}
	
		var scroll = settings.oScroll;
		if ( scroll.sY !== '' || scroll.sX !== '')
		{
			_fnScrollDraw( settings );
		}
	
		_fnCallbackFire( settings, null, 'column-sizing', [settings] );
	}
	
	
	/**
	 * Covert the index of a visible column to the index in the data array (take account
	 * of hidden columns)
	 *  @param {object} oSettings dataTables settings object
	 *  @param {int} iMatch Visible column index to lookup
	 *  @returns {int} i the data index
	 *  @memberof DataTable#oApi
	 */
	function _fnVisibleToColumnIndex( oSettings, iMatch )
	{
		var aiVis = _fnGetColumns( oSettings, 'bVisible' );
	
		return typeof aiVis[iMatch] === 'number' ?
			aiVis[iMatch] :
			null;
	}
	
	
	/**
	 * Covert the index of an index in the data array and convert it to the visible
	 *   column index (take account of hidden columns)
	 *  @param {int} iMatch Column index to lookup
	 *  @param {object} oSettings dataTables settings object
	 *  @returns {int} i the data index
	 *  @memberof DataTable#oApi
	 */
	function _fnColumnIndexToVisible( oSettings, iMatch )
	{
		var aiVis = _fnGetColumns( oSettings, 'bVisible' );
		var iPos = $.inArray( iMatch, aiVis );
	
		return iPos !== -1 ? iPos : null;
	}
	
	
	/**
	 * Get the number of visible columns
	 *  @param {object} oSettings dataTables settings object
	 *  @returns {int} i the number of visible columns
	 *  @memberof DataTable#oApi
	 */
	function _fnVisbleColumns( oSettings )
	{
		return _fnGetColumns( oSettings, 'bVisible' ).length;
	}
	
	
	/**
	 * Get an array of column indexes that match a given property
	 *  @param {object} oSettings dataTables settings object
	 *  @param {string} sParam Parameter in aoColumns to look for - typically
	 *    bVisible or bSearchable
	 *  @returns {array} Array of indexes with matched properties
	 *  @memberof DataTable#oApi
	 */
	function _fnGetColumns( oSettings, sParam )
	{
		var a = [];
	
		$.map( oSettings.aoColumns, function(val, i) {
			if ( val[sParam] ) {
				a.push( i );
			}
		} );
	
		return a;
	}
	
	
	/**
	 * Calculate the 'type' of a column
	 *  @param {object} settings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnColumnTypes ( settings )
	{
		var columns = settings.aoColumns;
		var data = settings.aoData;
		var types = DataTable.ext.type.detect;
		var i, ien, j, jen, k, ken;
		var col, cell, detectedType, cache;
	
		// For each column, spin over the 
		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
			col = columns[i];
			cache = [];
	
			if ( ! col.sType && col._sManualType ) {
				col.sType = col._sManualType;
			}
			else if ( ! col.sType ) {
				for ( j=0, jen=types.length ; j<jen ; j++ ) {
					for ( k=0, ken=data.length ; k<ken ; k++ ) {
						// Use a cache array so we only need to get the type data
						// from the formatter once (when using multiple detectors)
						if ( cache[k] === undefined ) {
							cache[k] = _fnGetCellData( settings, k, i, 'type' );
						}
	
						detectedType = types[j]( cache[k], settings );
	
						// If null, then this type can't apply to this column, so
						// rather than testing all cells, break out. There is an
						// exception for the last type which is `html`. We need to
						// scan all rows since it is possible to mix string and HTML
						// types
						if ( ! detectedType && j !== types.length-1 ) {
							break;
						}
	
						// Only a single match is needed for html type since it is
						// bottom of the pile and very similar to string
						if ( detectedType === 'html' ) {
							break;
						}
					}
	
					// Type is valid for all data points in the column - use this
					// type
					if ( detectedType ) {
						col.sType = detectedType;
						break;
					}
				}
	
				// Fall back - if no type was detected, always use string
				if ( ! col.sType ) {
					col.sType = 'string';
				}
			}
		}
	}
	
	
	/**
	 * Take the column definitions and static columns arrays and calculate how
	 * they relate to column indexes. The callback function will then apply the
	 * definition found for a column to a suitable configuration object.
	 *  @param {object} oSettings dataTables settings object
	 *  @param {array} aoColDefs The aoColumnDefs array that is to be applied
	 *  @param {array} aoCols The aoColumns array that defines columns individually
	 *  @param {function} fn Callback function - takes two parameters, the calculated
	 *    column index and the definition for that column.
	 *  @memberof DataTable#oApi
	 */
	function _fnApplyColumnDefs( oSettings, aoColDefs, aoCols, fn )
	{
		var i, iLen, j, jLen, k, kLen, def;
		var columns = oSettings.aoColumns;
	
		// Column definitions with aTargets
		if ( aoColDefs )
		{
			/* Loop over the definitions array - loop in reverse so first instance has priority */
			for ( i=aoColDefs.length-1 ; i>=0 ; i-- )
			{
				def = aoColDefs[i];
	
				/* Each definition can target multiple columns, as it is an array */
				var aTargets = def.targets !== undefined ?
					def.targets :
					def.aTargets;
	
				if ( ! $.isArray( aTargets ) )
				{
					aTargets = [ aTargets ];
				}
	
				for ( j=0, jLen=aTargets.length ; j<jLen ; j++ )
				{
					if ( typeof aTargets[j] === 'number' && aTargets[j] >= 0 )
					{
						/* Add columns that we don't yet know about */
						while( columns.length <= aTargets[j] )
						{
							_fnAddColumn( oSettings );
						}
	
						/* Integer, basic index */
						fn( aTargets[j], def );
					}
					else if ( typeof aTargets[j] === 'number' && aTargets[j] < 0 )
					{
						/* Negative integer, right to left column counting */
						fn( columns.length+aTargets[j], def );
					}
					else if ( typeof aTargets[j] === 'string' )
					{
						/* Class name matching on TH element */
						for ( k=0, kLen=columns.length ; k<kLen ; k++ )
						{
							if ( aTargets[j] == "_all" ||
							     $(columns[k].nTh).hasClass( aTargets[j] ) )
							{
								fn( k, def );
							}
						}
					}
				}
			}
		}
	
		// Statically defined columns array
		if ( aoCols )
		{
			for ( i=0, iLen=aoCols.length ; i<iLen ; i++ )
			{
				fn( i, aoCols[i] );
			}
		}
	}
	
	/**
	 * Add a data array to the table, creating DOM node etc. This is the parallel to
	 * _fnGatherData, but for adding rows from a Javascript source, rather than a
	 * DOM source.
	 *  @param {object} oSettings dataTables settings object
	 *  @param {array} aData data array to be added
	 *  @param {node} [nTr] TR element to add to the table - optional. If not given,
	 *    DataTables will create a row automatically
	 *  @param {array} [anTds] Array of TD|TH elements for the row - must be given
	 *    if nTr is.
	 *  @returns {int} >=0 if successful (index of new aoData entry), -1 if failed
	 *  @memberof DataTable#oApi
	 */
	function _fnAddData ( oSettings, aDataIn, nTr, anTds )
	{
		/* Create the object for storing information about this new row */
		var iRow = oSettings.aoData.length;
		var oData = $.extend( true, {}, DataTable.models.oRow, {
			src: nTr ? 'dom' : 'data',
			idx: iRow
		} );
	
		oData._aData = aDataIn;
		oSettings.aoData.push( oData );
	
		/* Create the cells */
		var nTd, sThisType;
		var columns = oSettings.aoColumns;
	
		// Invalidate the column types as the new data needs to be revalidated
		for ( var i=0, iLen=columns.length ; i<iLen ; i++ )
		{
			columns[i].sType = null;
		}
	
		/* Add to the display array */
		oSettings.aiDisplayMaster.push( iRow );
	
		var id = oSettings.rowIdFn( aDataIn );
		if ( id !== undefined ) {
			oSettings.aIds[ id ] = oData;
		}
	
		/* Create the DOM information, or register it if already present */
		if ( nTr || ! oSettings.oFeatures.bDeferRender )
		{
			_fnCreateTr( oSettings, iRow, nTr, anTds );
		}
	
		return iRow;
	}
	
	
	/**
	 * Add one or more TR elements to the table. Generally we'd expect to
	 * use this for reading data from a DOM sourced table, but it could be
	 * used for an TR element. Note that if a TR is given, it is used (i.e.
	 * it is not cloned).
	 *  @param {object} settings dataTables settings object
	 *  @param {array|node|jQuery} trs The TR element(s) to add to the table
	 *  @returns {array} Array of indexes for the added rows
	 *  @memberof DataTable#oApi
	 */
	function _fnAddTr( settings, trs )
	{
		var row;
	
		// Allow an individual node to be passed in
		if ( ! (trs instanceof $) ) {
			trs = $(trs);
		}
	
		return trs.map( function (i, el) {
			row = _fnGetRowElements( settings, el );
			return _fnAddData( settings, row.data, el, row.cells );
		} );
	}
	
	
	/**
	 * Take a TR element and convert it to an index in aoData
	 *  @param {object} oSettings dataTables settings object
	 *  @param {node} n the TR element to find
	 *  @returns {int} index if the node is found, null if not
	 *  @memberof DataTable#oApi
	 */
	function _fnNodeToDataIndex( oSettings, n )
	{
		return (n._DT_RowIndex!==undefined) ? n._DT_RowIndex : null;
	}
	
	
	/**
	 * Take a TD element and convert it into a column data index (not the visible index)
	 *  @param {object} oSettings dataTables settings object
	 *  @param {int} iRow The row number the TD/TH can be found in
	 *  @param {node} n The TD/TH element to find
	 *  @returns {int} index if the node is found, -1 if not
	 *  @memberof DataTable#oApi
	 */
	function _fnNodeToColumnIndex( oSettings, iRow, n )
	{
		return $.inArray( n, oSettings.aoData[ iRow ].anCells );
	}
	
	
	/**
	 * Get the data for a given cell from the internal cache, taking into account data mapping
	 *  @param {object} settings dataTables settings object
	 *  @param {int} rowIdx aoData row id
	 *  @param {int} colIdx Column index
	 *  @param {string} type data get type ('display', 'type' 'filter' 'sort')
	 *  @returns {*} Cell data
	 *  @memberof DataTable#oApi
	 */
	function _fnGetCellData( settings, rowIdx, colIdx, type )
	{
		var draw           = settings.iDraw;
		var col            = settings.aoColumns[colIdx];
		var rowData        = settings.aoData[rowIdx]._aData;
		var defaultContent = col.sDefaultContent;
		var cellData       = col.fnGetData( rowData, type, {
			settings: settings,
			row:      rowIdx,
			col:      colIdx
		} );
	
		if ( cellData === undefined ) {
			if ( settings.iDrawError != draw && defaultContent === null ) {
				_fnLog( settings, 0, "Requested unknown parameter "+
					(typeof col.mData=='function' ? '{function}' : "'"+col.mData+"'")+
					" for row "+rowIdx+", column "+colIdx, 4 );
				settings.iDrawError = draw;
			}
			return defaultContent;
		}
	
		/* When the data source is null, we can use default column data */
		if ( (cellData === rowData || cellData === null) && defaultContent !== null ) {
			cellData = defaultContent;
		}
		else if ( typeof cellData === 'function' ) {
			// If the data source is a function, then we run it and use the return,
			// executing in the scope of the data object (for instances)
			return cellData.call( rowData );
		}
	
		if ( cellData === null && type == 'display' ) {
			return '';
		}
		return cellData;
	}
	
	
	/**
	 * Set the value for a specific cell, into the internal data cache
	 *  @param {object} settings dataTables settings object
	 *  @param {int} rowIdx aoData row id
	 *  @param {int} colIdx Column index
	 *  @param {*} val Value to set
	 *  @memberof DataTable#oApi
	 */
	function _fnSetCellData( settings, rowIdx, colIdx, val )
	{
		var col     = settings.aoColumns[colIdx];
		var rowData = settings.aoData[rowIdx]._aData;
	
		col.fnSetData( rowData, val, {
			settings: settings,
			row:      rowIdx,
			col:      colIdx
		}  );
	}
	
	
	// Private variable that is used to match action syntax in the data property object
	var __reArray = /\[.*?\]$/;
	var __reFn = /\(\)$/;
	
	/**
	 * Split string on periods, taking into account escaped periods
	 * @param  {string} str String to split
	 * @return {array} Split string
	 */
	function _fnSplitObjNotation( str )
	{
		return $.map( str.match(/(\\.|[^\.])+/g) || [''], function ( s ) {
			return s.replace(/\\./g, '.');
		} );
	}
	
	
	/**
	 * Return a function that can be used to get data from a source object, taking
	 * into account the ability to use nested objects as a source
	 *  @param {string|int|function} mSource The data source for the object
	 *  @returns {function} Data get function
	 *  @memberof DataTable#oApi
	 */
	function _fnGetObjectDataFn( mSource )
	{
		if ( $.isPlainObject( mSource ) )
		{
			/* Build an object of get functions, and wrap them in a single call */
			var o = {};
			$.each( mSource, function (key, val) {
				if ( val ) {
					o[key] = _fnGetObjectDataFn( val );
				}
			} );
	
			return function (data, type, row, meta) {
				var t = o[type] || o._;
				return t !== undefined ?
					t(data, type, row, meta) :
					data;
			};
		}
		else if ( mSource === null )
		{
			/* Give an empty string for rendering / sorting etc */
			return function (data) { // type, row and meta also passed, but not used
				return data;
			};
		}
		else if ( typeof mSource === 'function' )
		{
			return function (data, type, row, meta) {
				return mSource( data, type, row, meta );
			};
		}
		else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 ||
			      mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1) )
		{
			/* If there is a . in the source string then the data source is in a
			 * nested object so we loop over the data for each level to get the next
			 * level down. On each loop we test for undefined, and if found immediately
			 * return. This allows entire objects to be missing and sDefaultContent to
			 * be used if defined, rather than throwing an error
			 */
			var fetchData = function (data, type, src) {
				var arrayNotation, funcNotation, out, innerSrc;
	
				if ( src !== "" )
				{
					var a = _fnSplitObjNotation( src );
	
					for ( var i=0, iLen=a.length ; i<iLen ; i++ )
					{
						// Check if we are dealing with special notation
						arrayNotation = a[i].match(__reArray);
						funcNotation = a[i].match(__reFn);
	
						if ( arrayNotation )
						{
							// Array notation
							a[i] = a[i].replace(__reArray, '');
	
							// Condition allows simply [] to be passed in
							if ( a[i] !== "" ) {
								data = data[ a[i] ];
							}
							out = [];
	
							// Get the remainder of the nested object to get
							a.splice( 0, i+1 );
							innerSrc = a.join('.');
	
							// Traverse each entry in the array getting the properties requested
							if ( $.isArray( data ) ) {
								for ( var j=0, jLen=data.length ; j<jLen ; j++ ) {
									out.push( fetchData( data[j], type, innerSrc ) );
								}
							}
	
							// If a string is given in between the array notation indicators, that
							// is used to join the strings together, otherwise an array is returned
							var join = arrayNotation[0].substring(1, arrayNotation[0].length-1);
							data = (join==="") ? out : out.join(join);
	
							// The inner call to fetchData has already traversed through the remainder
							// of the source requested, so we exit from the loop
							break;
						}
						else if ( funcNotation )
						{
							// Function call
							a[i] = a[i].replace(__reFn, '');
							data = data[ a[i] ]();
							continue;
						}
	
						if ( data === null || data[ a[i] ] === undefined )
						{
							return undefined;
						}
						data = data[ a[i] ];
					}
				}
	
				return data;
			};
	
			return function (data, type) { // row and meta also passed, but not used
				return fetchData( data, type, mSource );
			};
		}
		else
		{
			/* Array or flat object mapping */
			return function (data, type) { // row and meta also passed, but not used
				return data[mSource];
			};
		}
	}
	
	
	/**
	 * Return a function that can be used to set data from a source object, taking
	 * into account the ability to use nested objects as a source
	 *  @param {string|int|function} mSource The data source for the object
	 *  @returns {function} Data set function
	 *  @memberof DataTable#oApi
	 */
	function _fnSetObjectDataFn( mSource )
	{
		if ( $.isPlainObject( mSource ) )
		{
			/* Unlike get, only the underscore (global) option is used for for
			 * setting data since we don't know the type here. This is why an object
			 * option is not documented for `mData` (which is read/write), but it is
			 * for `mRender` which is read only.
			 */
			return _fnSetObjectDataFn( mSource._ );
		}
		else if ( mSource === null )
		{
			/* Nothing to do when the data source is null */
			return function () {};
		}
		else if ( typeof mSource === 'function' )
		{
			return function (data, val, meta) {
				mSource( data, 'set', val, meta );
			};
		}
		else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 ||
			      mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1) )
		{
			/* Like the get, we need to get data from a nested object */
			var setData = function (data, val, src) {
				var a = _fnSplitObjNotation( src ), b;
				var aLast = a[a.length-1];
				var arrayNotation, funcNotation, o, innerSrc;
	
				for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ )
				{
					// Check if we are dealing with an array notation request
					arrayNotation = a[i].match(__reArray);
					funcNotation = a[i].match(__reFn);
	
					if ( arrayNotation )
					{
						a[i] = a[i].replace(__reArray, '');
						data[ a[i] ] = [];
	
						// Get the remainder of the nested object to set so we can recurse
						b = a.slice();
						b.splice( 0, i+1 );
						innerSrc = b.join('.');
	
						// Traverse each entry in the array setting the properties requested
						if ( $.isArray( val ) )
						{
							for ( var j=0, jLen=val.length ; j<jLen ; j++ )
							{
								o = {};
								setData( o, val[j], innerSrc );
								data[ a[i] ].push( o );
							}
						}
						else
						{
							// We've been asked to save data to an array, but it
							// isn't array data to be saved. Best that can be done
							// is to just save the value.
							data[ a[i] ] = val;
						}
	
						// The inner call to setData has already traversed through the remainder
						// of the source and has set the data, thus we can exit here
						return;
					}
					else if ( funcNotation )
					{
						// Function call
						a[i] = a[i].replace(__reFn, '');
						data = data[ a[i] ]( val );
					}
	
					// If the nested object doesn't currently exist - since we are
					// trying to set the value - create it
					if ( data[ a[i] ] === null || data[ a[i] ] === undefined )
					{
						data[ a[i] ] = {};
					}
					data = data[ a[i] ];
				}
	
				// Last item in the input - i.e, the actual set
				if ( aLast.match(__reFn ) )
				{
					// Function call
					data = data[ aLast.replace(__reFn, '') ]( val );
				}
				else
				{
					// If array notation is used, we just want to strip it and use the property name
					// and assign the value. If it isn't used, then we get the result we want anyway
					data[ aLast.replace(__reArray, '') ] = val;
				}
			};
	
			return function (data, val) { // meta is also passed in, but not used
				return setData( data, val, mSource );
			};
		}
		else
		{
			/* Array or flat object mapping */
			return function (data, val) { // meta is also passed in, but not used
				data[mSource] = val;
			};
		}
	}
	
	
	/**
	 * Return an array with the full table data
	 *  @param {object} oSettings dataTables settings object
	 *  @returns array {array} aData Master data array
	 *  @memberof DataTable#oApi
	 */
	function _fnGetDataMaster ( settings )
	{
		return _pluck( settings.aoData, '_aData' );
	}
	
	
	/**
	 * Nuke the table
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnClearTable( settings )
	{
		settings.aoData.length = 0;
		settings.aiDisplayMaster.length = 0;
		settings.aiDisplay.length = 0;
		settings.aIds = {};
	}
	
	
	 /**
	 * Take an array of integers (index array) and remove a target integer (value - not
	 * the key!)
	 *  @param {array} a Index array to target
	 *  @param {int} iTarget value to find
	 *  @memberof DataTable#oApi
	 */
	function _fnDeleteIndex( a, iTarget, splice )
	{
		var iTargetIndex = -1;
	
		for ( var i=0, iLen=a.length ; i<iLen ; i++ )
		{
			if ( a[i] == iTarget )
			{
				iTargetIndex = i;
			}
			else if ( a[i] > iTarget )
			{
				a[i]--;
			}
		}
	
		if ( iTargetIndex != -1 && splice === undefined )
		{
			a.splice( iTargetIndex, 1 );
		}
	}
	
	
	/**
	 * Mark cached data as invalid such that a re-read of the data will occur when
	 * the cached data is next requested. Also update from the data source object.
	 *
	 * @param {object} settings DataTables settings object
	 * @param {int}    rowIdx   Row index to invalidate
	 * @param {string} [src]    Source to invalidate from: undefined, 'auto', 'dom'
	 *     or 'data'
	 * @param {int}    [colIdx] Column index to invalidate. If undefined the whole
	 *     row will be invalidated
	 * @memberof DataTable#oApi
	 *
	 * @todo For the modularisation of v1.11 this will need to become a callback, so
	 *   the sort and filter methods can subscribe to it. That will required
	 *   initialisation options for sorting, which is why it is not already baked in
	 */
	function _fnInvalidate( settings, rowIdx, src, colIdx )
	{
		var row = settings.aoData[ rowIdx ];
		var i, ien;
		var cellWrite = function ( cell, col ) {
			// This is very frustrating, but in IE if you just write directly
			// to innerHTML, and elements that are overwritten are GC'ed,
			// even if there is a reference to them elsewhere
			while ( cell.childNodes.length ) {
				cell.removeChild( cell.firstChild );
			}
	
			cell.innerHTML = _fnGetCellData( settings, rowIdx, col, 'display' );
		};
	
		// Are we reading last data from DOM or the data object?
		if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) {
			// Read the data from the DOM
			row._aData = _fnGetRowElements(
					settings, row, colIdx, colIdx === undefined ? undefined : row._aData
				)
				.data;
		}
		else {
			// Reading from data object, update the DOM
			var cells = row.anCells;
	
			if ( cells ) {
				if ( colIdx !== undefined ) {
					cellWrite( cells[colIdx], colIdx );
				}
				else {
					for ( i=0, ien=cells.length ; i<ien ; i++ ) {
						cellWrite( cells[i], i );
					}
				}
			}
		}
	
		// For both row and cell invalidation, the cached data for sorting and
		// filtering is nulled out
		row._aSortData = null;
		row._aFilterData = null;
	
		// Invalidate the type for a specific column (if given) or all columns since
		// the data might have changed
		var cols = settings.aoColumns;
		if ( colIdx !== undefined ) {
			cols[ colIdx ].sType = null;
		}
		else {
			for ( i=0, ien=cols.length ; i<ien ; i++ ) {
				cols[i].sType = null;
			}
	
			// Update DataTables special `DT_*` attributes for the row
			_fnRowAttributes( settings, row );
		}
	}
	
	
	/**
	 * Build a data source object from an HTML row, reading the contents of the
	 * cells that are in the row.
	 *
	 * @param {object} settings DataTables settings object
	 * @param {node|object} TR element from which to read data or existing row
	 *   object from which to re-read the data from the cells
	 * @param {int} [colIdx] Optional column index
	 * @param {array|object} [d] Data source object. If `colIdx` is given then this
	 *   parameter should also be given and will be used to write the data into.
	 *   Only the column in question will be written
	 * @returns {object} Object with two parameters: `data` the data read, in
	 *   document order, and `cells` and array of nodes (they can be useful to the
	 *   caller, so rather than needing a second traversal to get them, just return
	 *   them from here).
	 * @memberof DataTable#oApi
	 */
	function _fnGetRowElements( settings, row, colIdx, d )
	{
		var
			tds = [],
			td = row.firstChild,
			name, col, o, i=0, contents,
			columns = settings.aoColumns,
			objectRead = settings._rowReadObject;
	
		// Allow the data object to be passed in, or construct
		d = d !== undefined ?
			d :
			objectRead ?
				{} :
				[];
	
		var attr = function ( str, td  ) {
			if ( typeof str === 'string' ) {
				var idx = str.indexOf('@');
	
				if ( idx !== -1 ) {
					var attr = str.substring( idx+1 );
					var setter = _fnSetObjectDataFn( str );
					setter( d, td.getAttribute( attr ) );
				}
			}
		};
	
		// Read data from a cell and store into the data object
		var cellProcess = function ( cell ) {
			if ( colIdx === undefined || colIdx === i ) {
				col = columns[i];
				contents = $.trim(cell.innerHTML);
	
				if ( col && col._bAttrSrc ) {
					var setter = _fnSetObjectDataFn( col.mData._ );
					setter( d, contents );
	
					attr( col.mData.sort, cell );
					attr( col.mData.type, cell );
					attr( col.mData.filter, cell );
				}
				else {
					// Depending on the `data` option for the columns the data can
					// be read to either an object or an array.
					if ( objectRead ) {
						if ( ! col._setter ) {
							// Cache the setter function
							col._setter = _fnSetObjectDataFn( col.mData );
						}
						col._setter( d, contents );
					}
					else {
						d[i] = contents;
					}
				}
			}
	
			i++;
		};
	
		if ( td ) {
			// `tr` element was passed in
			while ( td ) {
				name = td.nodeName.toUpperCase();
	
				if ( name == "TD" || name == "TH" ) {
					cellProcess( td );
					tds.push( td );
				}
	
				td = td.nextSibling;
			}
		}
		else {
			// Existing row object passed in
			tds = row.anCells;
	
			for ( var j=0, jen=tds.length ; j<jen ; j++ ) {
				cellProcess( tds[j] );
			}
		}
	
		// Read the ID from the DOM if present
		var rowNode = row.firstChild ? row : row.nTr;
	
		if ( rowNode ) {
			var id = rowNode.getAttribute( 'id' );
	
			if ( id ) {
				_fnSetObjectDataFn( settings.rowId )( d, id );
			}
		}
	
		return {
			data: d,
			cells: tds
		};
	}
	/**
	 * Create a new TR element (and it's TD children) for a row
	 *  @param {object} oSettings dataTables settings object
	 *  @param {int} iRow Row to consider
	 *  @param {node} [nTrIn] TR element to add to the table - optional. If not given,
	 *    DataTables will create a row automatically
	 *  @param {array} [anTds] Array of TD|TH elements for the row - must be given
	 *    if nTr is.
	 *  @memberof DataTable#oApi
	 */
	function _fnCreateTr ( oSettings, iRow, nTrIn, anTds )
	{
		var
			row = oSettings.aoData[iRow],
			rowData = row._aData,
			cells = [],
			nTr, nTd, oCol,
			i, iLen;
	
		if ( row.nTr === null )
		{
			nTr = nTrIn || document.createElement('tr');
	
			row.nTr = nTr;
			row.anCells = cells;
	
			/* Use a private property on the node to allow reserve mapping from the node
			 * to the aoData array for fast look up
			 */
			nTr._DT_RowIndex = iRow;
	
			/* Special parameters can be given by the data source to be used on the row */
			_fnRowAttributes( oSettings, row );
	
			/* Process each column */
			for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
			{
				oCol = oSettings.aoColumns[i];
	
				nTd = nTrIn ? anTds[i] : document.createElement( oCol.sCellType );
				nTd._DT_CellIndex = {
					row: iRow,
					column: i
				};
				
				cells.push( nTd );
	
				// Need to create the HTML if new, or if a rendering function is defined
				if ( !nTrIn || oCol.mRender || oCol.mData !== i )
				{
					nTd.innerHTML = _fnGetCellData( oSettings, iRow, i, 'display' );
				}
	
				/* Add user defined class */
				if ( oCol.sClass )
				{
					nTd.className += ' '+oCol.sClass;
				}
	
				// Visibility - add or remove as required
				if ( oCol.bVisible && ! nTrIn )
				{
					nTr.appendChild( nTd );
				}
				else if ( ! oCol.bVisible && nTrIn )
				{
					nTd.parentNode.removeChild( nTd );
				}
	
				if ( oCol.fnCreatedCell )
				{
					oCol.fnCreatedCell.call( oSettings.oInstance,
						nTd, _fnGetCellData( oSettings, iRow, i ), rowData, iRow, i
					);
				}
			}
	
			_fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [nTr, rowData, iRow] );
		}
	
		// Remove once webkit bug 131819 and Chromium bug 365619 have been resolved
		// and deployed
		row.nTr.setAttribute( 'role', 'row' );
	}
	
	
	/**
	 * Add attributes to a row based on the special `DT_*` parameters in a data
	 * source object.
	 *  @param {object} settings DataTables settings object
	 *  @param {object} DataTables row object for the row to be modified
	 *  @memberof DataTable#oApi
	 */
	function _fnRowAttributes( settings, row )
	{
		var tr = row.nTr;
		var data = row._aData;
	
		if ( tr ) {
			var id = settings.rowIdFn( data );
	
			if ( id ) {
				tr.id = id;
			}
	
			if ( data.DT_RowClass ) {
				// Remove any classes added by DT_RowClass before
				var a = data.DT_RowClass.split(' ');
				row.__rowc = row.__rowc ?
					_unique( row.__rowc.concat( a ) ) :
					a;
	
				$(tr)
					.removeClass( row.__rowc.join(' ') )
					.addClass( data.DT_RowClass );
			}
	
			if ( data.DT_RowAttr ) {
				$(tr).attr( data.DT_RowAttr );
			}
	
			if ( data.DT_RowData ) {
				$(tr).data( data.DT_RowData );
			}
		}
	}
	
	
	/**
	 * Create the HTML header for the table
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnBuildHead( oSettings )
	{
		var i, ien, cell, row, column;
		var thead = oSettings.nTHead;
		var tfoot = oSettings.nTFoot;
		var createHeader = $('th, td', thead).length === 0;
		var classes = oSettings.oClasses;
		var columns = oSettings.aoColumns;
	
		if ( createHeader ) {
			row = $('<tr/>').appendTo( thead );
		}
	
		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
			column = columns[i];
			cell = $( column.nTh ).addClass( column.sClass );
	
			if ( createHeader ) {
				cell.appendTo( row );
			}
	
			// 1.11 move into sorting
			if ( oSettings.oFeatures.bSort ) {
				cell.addClass( column.sSortingClass );
	
				if ( column.bSortable !== false ) {
					cell
						.attr( 'tabindex', oSettings.iTabIndex )
						.attr( 'aria-controls', oSettings.sTableId );
	
					_fnSortAttachListener( oSettings, column.nTh, i );
				}
			}
	
			if ( column.sTitle != cell[0].innerHTML ) {
				cell.html( column.sTitle );
			}
	
			_fnRenderer( oSettings, 'header' )(
				oSettings, cell, column, classes
			);
		}
	
		if ( createHeader ) {
			_fnDetectHeader( oSettings.aoHeader, thead );
		}
		
		/* ARIA role for the rows */
	 	$(thead).find('>tr').attr('role', 'row');
	
		/* Deal with the footer - add classes if required */
		$(thead).find('>tr>th, >tr>td').addClass( classes.sHeaderTH );
		$(tfoot).find('>tr>th, >tr>td').addClass( classes.sFooterTH );
	
		// Cache the footer cells. Note that we only take the cells from the first
		// row in the footer. If there is more than one row the user wants to
		// interact with, they need to use the table().foot() method. Note also this
		// allows cells to be used for multiple columns using colspan
		if ( tfoot !== null ) {
			var cells = oSettings.aoFooter[0];
	
			for ( i=0, ien=cells.length ; i<ien ; i++ ) {
				column = columns[i];
				column.nTf = cells[i].cell;
	
				if ( column.sClass ) {
					$(column.nTf).addClass( column.sClass );
				}
			}
		}
	}
	
	
	/**
	 * Draw the header (or footer) element based on the column visibility states. The
	 * methodology here is to use the layout array from _fnDetectHeader, modified for
	 * the instantaneous column visibility, to construct the new layout. The grid is
	 * traversed over cell at a time in a rows x columns grid fashion, although each
	 * cell insert can cover multiple elements in the grid - which is tracks using the
	 * aApplied array. Cell inserts in the grid will only occur where there isn't
	 * already a cell in that position.
	 *  @param {object} oSettings dataTables settings object
	 *  @param array {objects} aoSource Layout array from _fnDetectHeader
	 *  @param {boolean} [bIncludeHidden=false] If true then include the hidden columns in the calc,
	 *  @memberof DataTable#oApi
	 */
	function _fnDrawHead( oSettings, aoSource, bIncludeHidden )
	{
		var i, iLen, j, jLen, k, kLen, n, nLocalTr;
		var aoLocal = [];
		var aApplied = [];
		var iColumns = oSettings.aoColumns.length;
		var iRowspan, iColspan;
	
		if ( ! aoSource )
		{
			return;
		}
	
		if (  bIncludeHidden === undefined )
		{
			bIncludeHidden = false;
		}
	
		/* Make a copy of the master layout array, but without the visible columns in it */
		for ( i=0, iLen=aoSource.length ; i<iLen ; i++ )
		{
			aoLocal[i] = aoSource[i].slice();
			aoLocal[i].nTr = aoSource[i].nTr;
	
			/* Remove any columns which are currently hidden */
			for ( j=iColumns-1 ; j>=0 ; j-- )
			{
				if ( !oSettings.aoColumns[j].bVisible && !bIncludeHidden )
				{
					aoLocal[i].splice( j, 1 );
				}
			}
	
			/* Prep the applied array - it needs an element for each row */
			aApplied.push( [] );
		}
	
		for ( i=0, iLen=aoLocal.length ; i<iLen ; i++ )
		{
			nLocalTr = aoLocal[i].nTr;
	
			/* All cells are going to be replaced, so empty out the row */
			if ( nLocalTr )
			{
				while( (n = nLocalTr.firstChild) )
				{
					nLocalTr.removeChild( n );
				}
			}
	
			for ( j=0, jLen=aoLocal[i].length ; j<jLen ; j++ )
			{
				iRowspan = 1;
				iColspan = 1;
	
				/* Check to see if there is already a cell (row/colspan) covering our target
				 * insert point. If there is, then there is nothing to do.
				 */
				if ( aApplied[i][j] === undefined )
				{
					nLocalTr.appendChild( aoLocal[i][j].cell );
					aApplied[i][j] = 1;
	
					/* Expand the cell to cover as many rows as needed */
					while ( aoLocal[i+iRowspan] !== undefined &&
					        aoLocal[i][j].cell == aoLocal[i+iRowspan][j].cell )
					{
						aApplied[i+iRowspan][j] = 1;
						iRowspan++;
					}
	
					/* Expand the cell to cover as many columns as needed */
					while ( aoLocal[i][j+iColspan] !== undefined &&
					        aoLocal[i][j].cell == aoLocal[i][j+iColspan].cell )
					{
						/* Must update the applied array over the rows for the columns */
						for ( k=0 ; k<iRowspan ; k++ )
						{
							aApplied[i+k][j+iColspan] = 1;
						}
						iColspan++;
					}
	
					/* Do the actual expansion in the DOM */
					$(aoLocal[i][j].cell)
						.attr('rowspan', iRowspan)
						.attr('colspan', iColspan);
				}
			}
		}
	}
	
	
	/**
	 * Insert the required TR nodes into the table for display
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnDraw( oSettings )
	{
		/* Provide a pre-callback function which can be used to cancel the draw is false is returned */
		var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] );
		if ( $.inArray( false, aPreDraw ) !== -1 )
		{
			_fnProcessingDisplay( oSettings, false );
			return;
		}
	
		var i, iLen, n;
		var anRows = [];
		var iRowCount = 0;
		var asStripeClasses = oSettings.asStripeClasses;
		var iStripes = asStripeClasses.length;
		var iOpenRows = oSettings.aoOpenRows.length;
		var oLang = oSettings.oLanguage;
		var iInitDisplayStart = oSettings.iInitDisplayStart;
		var bServerSide = _fnDataSource( oSettings ) == 'ssp';
		var aiDisplay = oSettings.aiDisplay;
	
		oSettings.bDrawing = true;
	
		/* Check and see if we have an initial draw position from state saving */
		if ( iInitDisplayStart !== undefined && iInitDisplayStart !== -1 )
		{
			oSettings._iDisplayStart = bServerSide ?
				iInitDisplayStart :
				iInitDisplayStart >= oSettings.fnRecordsDisplay() ?
					0 :
					iInitDisplayStart;
	
			oSettings.iInitDisplayStart = -1;
		}
	
		var iDisplayStart = oSettings._iDisplayStart;
		var iDisplayEnd = oSettings.fnDisplayEnd();
	
		/* Server-side processing draw intercept */
		if ( oSettings.bDeferLoading )
		{
			oSettings.bDeferLoading = false;
			oSettings.iDraw++;
			_fnProcessingDisplay( oSettings, false );
		}
		else if ( !bServerSide )
		{
			oSettings.iDraw++;
		}
		else if ( !oSettings.bDestroying && !_fnAjaxUpdate( oSettings ) )
		{
			return;
		}
	
		if ( aiDisplay.length !== 0 )
		{
			var iStart = bServerSide ? 0 : iDisplayStart;
			var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd;
	
			for ( var j=iStart ; j<iEnd ; j++ )
			{
				var iDataIndex = aiDisplay[j];
				var aoData = oSettings.aoData[ iDataIndex ];
				if ( aoData.nTr === null )
				{
					_fnCreateTr( oSettings, iDataIndex );
				}
	
				var nRow = aoData.nTr;
	
				/* Remove the old striping classes and then add the new one */
				if ( iStripes !== 0 )
				{
					var sStripe = asStripeClasses[ iRowCount % iStripes ];
					if ( aoData._sRowStripe != sStripe )
					{
						$(nRow).removeClass( aoData._sRowStripe ).addClass( sStripe );
						aoData._sRowStripe = sStripe;
					}
				}
	
				// Row callback functions - might want to manipulate the row
				// iRowCount and j are not currently documented. Are they at all
				// useful?
				_fnCallbackFire( oSettings, 'aoRowCallback', null,
					[nRow, aoData._aData, iRowCount, j] );
	
				anRows.push( nRow );
				iRowCount++;
			}
		}
		else
		{
			/* Table is empty - create a row with an empty message in it */
			var sZero = oLang.sZeroRecords;
			if ( oSettings.iDraw == 1 &&  _fnDataSource( oSettings ) == 'ajax' )
			{
				sZero = oLang.sLoadingRecords;
			}
			else if ( oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0 )
			{
				sZero = oLang.sEmptyTable;
			}
	
			anRows[ 0 ] = $( '<tr/>', { 'class': iStripes ? asStripeClasses[0] : '' } )
				.append( $('<td />', {
					'valign':  'top',
					'colSpan': _fnVisbleColumns( oSettings ),
					'class':   oSettings.oClasses.sRowEmpty
				} ).html( sZero ) )[0];
		}
	
		/* Header and footer callbacks */
		_fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0],
			_fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
	
		_fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0],
			_fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
	
		var body = $(oSettings.nTBody);
	
		body.children().detach();
		body.append( $(anRows) );
	
		/* Call all required callback functions for the end of a draw */
		_fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings] );
	
		/* Draw is complete, sorting and filtering must be as well */
		oSettings.bSorted = false;
		oSettings.bFiltered = false;
		oSettings.bDrawing = false;
	}
	
	
	/**
	 * Redraw the table - taking account of the various features which are enabled
	 *  @param {object} oSettings dataTables settings object
	 *  @param {boolean} [holdPosition] Keep the current paging position. By default
	 *    the paging is reset to the first page
	 *  @memberof DataTable#oApi
	 */
	function _fnReDraw( settings, holdPosition )
	{
		var
			features = settings.oFeatures,
			sort     = features.bSort,
			filter   = features.bFilter;
	
		if ( sort ) {
			_fnSort( settings );
		}
	
		if ( filter ) {
			_fnFilterComplete( settings, settings.oPreviousSearch );
		}
		else {
			// No filtering, so we want to just use the display master
			settings.aiDisplay = settings.aiDisplayMaster.slice();
		}
	
		if ( holdPosition !== true ) {
			settings._iDisplayStart = 0;
		}
	
		// Let any modules know about the draw hold position state (used by
		// scrolling internally)
		settings._drawHold = holdPosition;
	
		_fnDraw( settings );
	
		settings._drawHold = false;
	}
	
	
	/**
	 * Add the options to the page HTML for the table
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnAddOptionsHtml ( oSettings )
	{
		var classes = oSettings.oClasses;
		var table = $(oSettings.nTable);
		var holding = $('<div/>').insertBefore( table ); // Holding element for speed
		var features = oSettings.oFeatures;
	
		// All DataTables are wrapped in a div
		var insert = $('<div/>', {
			id:      oSettings.sTableId+'_wrapper',
			'class': classes.sWrapper + (oSettings.nTFoot ? '' : ' '+classes.sNoFooter)
		} );
	
		oSettings.nHolding = holding[0];
		oSettings.nTableWrapper = insert[0];
		oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling;
	
		/* Loop over the user set positioning and place the elements as needed */
		var aDom = oSettings.sDom.split('');
		var featureNode, cOption, nNewNode, cNext, sAttr, j;
		for ( var i=0 ; i<aDom.length ; i++ )
		{
			featureNode = null;
			cOption = aDom[i];
	
			if ( cOption == '<' )
			{
				/* New container div */
				nNewNode = $('<div/>')[0];
	
				/* Check to see if we should append an id and/or a class name to the container */
				cNext = aDom[i+1];
				if ( cNext == "'" || cNext == '"' )
				{
					sAttr = "";
					j = 2;
					while ( aDom[i+j] != cNext )
					{
						sAttr += aDom[i+j];
						j++;
					}
	
					/* Replace jQuery UI constants @todo depreciated */
					if ( sAttr == "H" )
					{
						sAttr = classes.sJUIHeader;
					}
					else if ( sAttr == "F" )
					{
						sAttr = classes.sJUIFooter;
					}
	
					/* The attribute can be in the format of "#id.class", "#id" or "class" This logic
					 * breaks the string into parts and applies them as needed
					 */
					if ( sAttr.indexOf('.') != -1 )
					{
						var aSplit = sAttr.split('.');
						nNewNode.id = aSplit[0].substr(1, aSplit[0].length-1);
						nNewNode.className = aSplit[1];
					}
					else if ( sAttr.charAt(0) == "#" )
					{
						nNewNode.id = sAttr.substr(1, sAttr.length-1);
					}
					else
					{
						nNewNode.className = sAttr;
					}
	
					i += j; /* Move along the position array */
				}
	
				insert.append( nNewNode );
				insert = $(nNewNode);
			}
			else if ( cOption == '>' )
			{
				/* End container div */
				insert = insert.parent();
			}
			// @todo Move options into their own plugins?
			else if ( cOption == 'l' && features.bPaginate && features.bLengthChange )
			{
				/* Length */
				featureNode = _fnFeatureHtmlLength( oSettings );
			}
			else if ( cOption == 'f' && features.bFilter )
			{
				/* Filter */
				featureNode = _fnFeatureHtmlFilter( oSettings );
			}
			else if ( cOption == 'r' && features.bProcessing )
			{
				/* pRocessing */
				featureNode = _fnFeatureHtmlProcessing( oSettings );
			}
			else if ( cOption == 't' )
			{
				/* Table */
				featureNode = _fnFeatureHtmlTable( oSettings );
			}
			else if ( cOption ==  'i' && features.bInfo )
			{
				/* Info */
				featureNode = _fnFeatureHtmlInfo( oSettings );
			}
			else if ( cOption == 'p' && features.bPaginate )
			{
				/* Pagination */
				featureNode = _fnFeatureHtmlPaginate( oSettings );
			}
			else if ( DataTable.ext.feature.length !== 0 )
			{
				/* Plug-in features */
				var aoFeatures = DataTable.ext.feature;
				for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ )
				{
					if ( cOption == aoFeatures[k].cFeature )
					{
						featureNode = aoFeatures[k].fnInit( oSettings );
						break;
					}
				}
			}
	
			/* Add to the 2D features array */
			if ( featureNode )
			{
				var aanFeatures = oSettings.aanFeatures;
	
				if ( ! aanFeatures[cOption] )
				{
					aanFeatures[cOption] = [];
				}
	
				aanFeatures[cOption].push( featureNode );
				insert.append( featureNode );
			}
		}
	
		/* Built our DOM structure - replace the holding div with what we want */
		holding.replaceWith( insert );
		oSettings.nHolding = null;
	}
	
	
	/**
	 * Use the DOM source to create up an array of header cells. The idea here is to
	 * create a layout grid (array) of rows x columns, which contains a reference
	 * to the cell that that point in the grid (regardless of col/rowspan), such that
	 * any column / row could be removed and the new grid constructed
	 *  @param array {object} aLayout Array to store the calculated layout in
	 *  @param {node} nThead The header/footer element for the table
	 *  @memberof DataTable#oApi
	 */
	function _fnDetectHeader ( aLayout, nThead )
	{
		var nTrs = $(nThead).children('tr');
		var nTr, nCell;
		var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan;
		var bUnique;
		var fnShiftCol = function ( a, i, j ) {
			var k = a[i];
	                while ( k[j] ) {
				j++;
			}
			return j;
		};
	
		aLayout.splice( 0, aLayout.length );
	
		/* We know how many rows there are in the layout - so prep it */
		for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
		{
			aLayout.push( [] );
		}
	
		/* Calculate a layout array */
		for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
		{
			nTr = nTrs[i];
			iColumn = 0;
	
			/* For every cell in the row... */
			nCell = nTr.firstChild;
			while ( nCell ) {
				if ( nCell.nodeName.toUpperCase() == "TD" ||
				     nCell.nodeName.toUpperCase() == "TH" )
				{
					/* Get the col and rowspan attributes from the DOM and sanitise them */
					iColspan = nCell.getAttribute('colspan') * 1;
					iRowspan = nCell.getAttribute('rowspan') * 1;
					iColspan = (!iColspan || iColspan===0 || iColspan===1) ? 1 : iColspan;
					iRowspan = (!iRowspan || iRowspan===0 || iRowspan===1) ? 1 : iRowspan;
	
					/* There might be colspan cells already in this row, so shift our target
					 * accordingly
					 */
					iColShifted = fnShiftCol( aLayout, i, iColumn );
	
					/* Cache calculation for unique columns */
					bUnique = iColspan === 1 ? true : false;
	
					/* If there is col / rowspan, copy the information into the layout grid */
					for ( l=0 ; l<iColspan ; l++ )
					{
						for ( k=0 ; k<iRowspan ; k++ )
						{
							aLayout[i+k][iColShifted+l] = {
								"cell": nCell,
								"unique": bUnique
							};
							aLayout[i+k].nTr = nTr;
						}
					}
				}
				nCell = nCell.nextSibling;
			}
		}
	}
	
	
	/**
	 * Get an array of unique th elements, one for each column
	 *  @param {object} oSettings dataTables settings object
	 *  @param {node} nHeader automatically detect the layout from this node - optional
	 *  @param {array} aLayout thead/tfoot layout from _fnDetectHeader - optional
	 *  @returns array {node} aReturn list of unique th's
	 *  @memberof DataTable#oApi
	 */
	function _fnGetUniqueThs ( oSettings, nHeader, aLayout )
	{
		var aReturn = [];
		if ( !aLayout )
		{
			aLayout = oSettings.aoHeader;
			if ( nHeader )
			{
				aLayout = [];
				_fnDetectHeader( aLayout, nHeader );
			}
		}
	
		for ( var i=0, iLen=aLayout.length ; i<iLen ; i++ )
		{
			for ( var j=0, jLen=aLayout[i].length ; j<jLen ; j++ )
			{
				if ( aLayout[i][j].unique &&
					 (!aReturn[j] || !oSettings.bSortCellsTop) )
				{
					aReturn[j] = aLayout[i][j].cell;
				}
			}
		}
	
		return aReturn;
	}
	
	/**
	 * Create an Ajax call based on the table's settings, taking into account that
	 * parameters can have multiple forms, and backwards compatibility.
	 *
	 * @param {object} oSettings dataTables settings object
	 * @param {array} data Data to send to the server, required by
	 *     DataTables - may be augmented by developer callbacks
	 * @param {function} fn Callback function to run when data is obtained
	 */
	function _fnBuildAjax( oSettings, data, fn )
	{
		// Compatibility with 1.9-, allow fnServerData and event to manipulate
		_fnCallbackFire( oSettings, 'aoServerParams', 'serverParams', [data] );
	
		// Convert to object based for 1.10+ if using the old array scheme which can
		// come from server-side processing or serverParams
		if ( data && $.isArray(data) ) {
			var tmp = {};
			var rbracket = /(.*?)\[\]$/;
	
			$.each( data, function (key, val) {
				var match = val.name.match(rbracket);
	
				if ( match ) {
					// Support for arrays
					var name = match[0];
	
					if ( ! tmp[ name ] ) {
						tmp[ name ] = [];
					}
					tmp[ name ].push( val.value );
				}
				else {
					tmp[val.name] = val.value;
				}
			} );
			data = tmp;
		}
	
		var ajaxData;
		var ajax = oSettings.ajax;
		var instance = oSettings.oInstance;
		var callback = function ( json ) {
			_fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR] );
			fn( json );
		};
	
		if ( $.isPlainObject( ajax ) && ajax.data )
		{
			ajaxData = ajax.data;
	
			var newData = $.isFunction( ajaxData ) ?
				ajaxData( data, oSettings ) :  // fn can manipulate data or return
				ajaxData;                      // an object object or array to merge
	
			// If the function returned something, use that alone
			data = $.isFunction( ajaxData ) && newData ?
				newData :
				$.extend( true, data, newData );
	
			// Remove the data property as we've resolved it already and don't want
			// jQuery to do it again (it is restored at the end of the function)
			delete ajax.data;
		}
	
		var baseAjax = {
			"data": data,
			"success": function (json) {
				var error = json.error || json.sError;
				if ( error ) {
					_fnLog( oSettings, 0, error );
				}
	
				oSettings.json = json;
				callback( json );
			},
			"dataType": "json",
			"cache": false,
			"type": oSettings.sServerMethod,
			"error": function (xhr, error, thrown) {
				var ret = _fnCallbackFire( oSettings, null, 'xhr', [oSettings, null, oSettings.jqXHR] );
	
				if ( $.inArray( true, ret ) === -1 ) {
					if ( error == "parsererror" ) {
						_fnLog( oSettings, 0, 'Invalid JSON response', 1 );
					}
					else if ( xhr.readyState === 4 ) {
						_fnLog( oSettings, 0, 'Ajax error', 7 );
					}
				}
	
				_fnProcessingDisplay( oSettings, false );
			}
		};
	
		// Store the data submitted for the API
		oSettings.oAjaxData = data;
	
		// Allow plug-ins and external processes to modify the data
		_fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data] );
	
		if ( oSettings.fnServerData )
		{
			// DataTables 1.9- compatibility
			oSettings.fnServerData.call( instance,
				oSettings.sAjaxSource,
				$.map( data, function (val, key) { // Need to convert back to 1.9 trad format
					return { name: key, value: val };
				} ),
				callback,
				oSettings
			);
		}
		else if ( oSettings.sAjaxSource || typeof ajax === 'string' )
		{
			// DataTables 1.9- compatibility
			oSettings.jqXHR = $.ajax( $.extend( baseAjax, {
				url: ajax || oSettings.sAjaxSource
			} ) );
		}
		else if ( $.isFunction( ajax ) )
		{
			// Is a function - let the caller define what needs to be done
			oSettings.jqXHR = ajax.call( instance, data, callback, oSettings );
		}
		else
		{
			// Object to extend the base settings
			oSettings.jqXHR = $.ajax( $.extend( baseAjax, ajax ) );
	
			// Restore for next time around
			ajax.data = ajaxData;
		}
	}
	
	
	/**
	 * Update the table using an Ajax call
	 *  @param {object} settings dataTables settings object
	 *  @returns {boolean} Block the table drawing or not
	 *  @memberof DataTable#oApi
	 */
	function _fnAjaxUpdate( settings )
	{
		if ( settings.bAjaxDataGet ) {
			settings.iDraw++;
			_fnProcessingDisplay( settings, true );
	
			_fnBuildAjax(
				settings,
				_fnAjaxParameters( settings ),
				function(json) {
					_fnAjaxUpdateDraw( settings, json );
				}
			);
	
			return false;
		}
		return true;
	}
	
	
	/**
	 * Build up the parameters in an object needed for a server-side processing
	 * request. Note that this is basically done twice, is different ways - a modern
	 * method which is used by default in DataTables 1.10 which uses objects and
	 * arrays, or the 1.9- method with is name / value pairs. 1.9 method is used if
	 * the sAjaxSource option is used in the initialisation, or the legacyAjax
	 * option is set.
	 *  @param {object} oSettings dataTables settings object
	 *  @returns {bool} block the table drawing or not
	 *  @memberof DataTable#oApi
	 */
	function _fnAjaxParameters( settings )
	{
		var
			columns = settings.aoColumns,
			columnCount = columns.length,
			features = settings.oFeatures,
			preSearch = settings.oPreviousSearch,
			preColSearch = settings.aoPreSearchCols,
			i, data = [], dataProp, column, columnSearch,
			sort = _fnSortFlatten( settings ),
			displayStart = settings._iDisplayStart,
			displayLength = features.bPaginate !== false ?
				settings._iDisplayLength :
				-1;
	
		var param = function ( name, value ) {
			data.push( { 'name': name, 'value': value } );
		};
	
		// DataTables 1.9- compatible method
		param( 'sEcho',          settings.iDraw );
		param( 'iColumns',       columnCount );
		param( 'sColumns',       _pluck( columns, 'sName' ).join(',') );
		param( 'iDisplayStart',  displayStart );
		param( 'iDisplayLength', displayLength );
	
		// DataTables 1.10+ method
		var d = {
			draw:    settings.iDraw,
			columns: [],
			order:   [],
			start:   displayStart,
			length:  displayLength,
			search:  {
				value: preSearch.sSearch,
				regex: preSearch.bRegex
			}
		};
	
		for ( i=0 ; i<columnCount ; i++ ) {
			column = columns[i];
			columnSearch = preColSearch[i];
			dataProp = typeof column.mData=="function" ? 'function' : column.mData ;
	
			d.columns.push( {
				data:       dataProp,
				name:       column.sName,
				searchable: column.bSearchable,
				orderable:  column.bSortable,
				search:     {
					value: columnSearch.sSearch,
					regex: columnSearch.bRegex
				}
			} );
	
			param( "mDataProp_"+i, dataProp );
	
			if ( features.bFilter ) {
				param( 'sSearch_'+i,     columnSearch.sSearch );
				param( 'bRegex_'+i,      columnSearch.bRegex );
				param( 'bSearchable_'+i, column.bSearchable );
			}
	
			if ( features.bSort ) {
				param( 'bSortable_'+i, column.bSortable );
			}
		}
	
		if ( features.bFilter ) {
			param( 'sSearch', preSearch.sSearch );
			param( 'bRegex', preSearch.bRegex );
		}
	
		if ( features.bSort ) {
			$.each( sort, function ( i, val ) {
				d.order.push( { column: val.col, dir: val.dir } );
	
				param( 'iSortCol_'+i, val.col );
				param( 'sSortDir_'+i, val.dir );
			} );
	
			param( 'iSortingCols', sort.length );
		}
	
		// If the legacy.ajax parameter is null, then we automatically decide which
		// form to use, based on sAjaxSource
		var legacy = DataTable.ext.legacy.ajax;
		if ( legacy === null ) {
			return settings.sAjaxSource ? data : d;
		}
	
		// Otherwise, if legacy has been specified then we use that to decide on the
		// form
		return legacy ? data : d;
	}
	
	
	/**
	 * Data the data from the server (nuking the old) and redraw the table
	 *  @param {object} oSettings dataTables settings object
	 *  @param {object} json json data return from the server.
	 *  @param {string} json.sEcho Tracking flag for DataTables to match requests
	 *  @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering
	 *  @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering
	 *  @param {array} json.aaData The data to display on this page
	 *  @param {string} [json.sColumns] Column ordering (sName, comma separated)
	 *  @memberof DataTable#oApi
	 */
	function _fnAjaxUpdateDraw ( settings, json )
	{
		// v1.10 uses camelCase variables, while 1.9 uses Hungarian notation.
		// Support both
		var compat = function ( old, modern ) {
			return json[old] !== undefined ? json[old] : json[modern];
		};
	
		var data = _fnAjaxDataSrc( settings, json );
		var draw            = compat( 'sEcho',                'draw' );
		var recordsTotal    = compat( 'iTotalRecords',        'recordsTotal' );
		var recordsFiltered = compat( 'iTotalDisplayRecords', 'recordsFiltered' );
	
		if ( draw ) {
			// Protect against out of sequence returns
			if ( draw*1 < settings.iDraw ) {
				return;
			}
			settings.iDraw = draw * 1;
		}
	
		_fnClearTable( settings );
		settings._iRecordsTotal   = parseInt(recordsTotal, 10);
		settings._iRecordsDisplay = parseInt(recordsFiltered, 10);
	
		for ( var i=0, ien=data.length ; i<ien ; i++ ) {
			_fnAddData( settings, data[i] );
		}
		settings.aiDisplay = settings.aiDisplayMaster.slice();
	
		settings.bAjaxDataGet = false;
		_fnDraw( settings );
	
		if ( ! settings._bInitComplete ) {
			_fnInitComplete( settings, json );
		}
	
		settings.bAjaxDataGet = true;
		_fnProcessingDisplay( settings, false );
	}
	
	
	/**
	 * Get the data from the JSON data source to use for drawing a table. Using
	 * `_fnGetObjectDataFn` allows the data to be sourced from a property of the
	 * source object, or from a processing function.
	 *  @param {object} oSettings dataTables settings object
	 *  @param  {object} json Data source object / array from the server
	 *  @return {array} Array of data to use
	 */
	function _fnAjaxDataSrc ( oSettings, json )
	{
		var dataSrc = $.isPlainObject( oSettings.ajax ) && oSettings.ajax.dataSrc !== undefined ?
			oSettings.ajax.dataSrc :
			oSettings.sAjaxDataProp; // Compatibility with 1.9-.
	
		// Compatibility with 1.9-. In order to read from aaData, check if the
		// default has been changed, if not, check for aaData
		if ( dataSrc === 'data' ) {
			return json.aaData || json[dataSrc];
		}
	
		return dataSrc !== "" ?
			_fnGetObjectDataFn( dataSrc )( json ) :
			json;
	}
	
	/**
	 * Generate the node required for filtering text
	 *  @returns {node} Filter control element
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnFeatureHtmlFilter ( settings )
	{
		var classes = settings.oClasses;
		var tableId = settings.sTableId;
		var language = settings.oLanguage;
		var previousSearch = settings.oPreviousSearch;
		var features = settings.aanFeatures;
		var input = '<input type="search" class="'+classes.sFilterInput+'"/>';
	
		var str = language.sSearch;
		str = str.match(/_INPUT_/) ?
			str.replace('_INPUT_', input) :
			str+input;
	
		var filter = $('<div/>', {
				'id': ! features.f ? tableId+'_filter' : null,
				'class': classes.sFilter
			} )
			.append( $('<label/>' ).append( str ) );
	
		var searchFn = function() {
			/* Update all other filter input elements for the new display */
			var n = features.f;
			var val = !this.value ? "" : this.value; // mental IE8 fix :-(
	
			/* Now do the filter */
			if ( val != previousSearch.sSearch ) {
				_fnFilterComplete( settings, {
					"sSearch": val,
					"bRegex": previousSearch.bRegex,
					"bSmart": previousSearch.bSmart ,
					"bCaseInsensitive": previousSearch.bCaseInsensitive
				} );
	
				// Need to redraw, without resorting
				settings._iDisplayStart = 0;
				_fnDraw( settings );
			}
		};
	
		var searchDelay = settings.searchDelay !== null ?
			settings.searchDelay :
			_fnDataSource( settings ) === 'ssp' ?
				400 :
				0;
	
		var jqFilter = $('input', filter)
			.val( previousSearch.sSearch )
			.attr( 'placeholder', language.sSearchPlaceholder )
			.bind(
				'keyup.DT search.DT input.DT paste.DT cut.DT',
				searchDelay ?
					_fnThrottle( searchFn, searchDelay ) :
					searchFn
			)
			.bind( 'keypress.DT', function(e) {
				/* Prevent form submission */
				if ( e.keyCode == 13 ) {
					return false;
				}
			} )
			.attr('aria-controls', tableId);
	
		// Update the input elements whenever the table is filtered
		$(settings.nTable).on( 'search.dt.DT', function ( ev, s ) {
			if ( settings === s ) {
				// IE9 throws an 'unknown error' if document.activeElement is used
				// inside an iframe or frame...
				try {
					if ( jqFilter[0] !== document.activeElement ) {
						jqFilter.val( previousSearch.sSearch );
					}
				}
				catch ( e ) {}
			}
		} );
	
		return filter[0];
	}
	
	
	/**
	 * Filter the table using both the global filter and column based filtering
	 *  @param {object} oSettings dataTables settings object
	 *  @param {object} oSearch search information
	 *  @param {int} [iForce] force a research of the master array (1) or not (undefined or 0)
	 *  @memberof DataTable#oApi
	 */
	function _fnFilterComplete ( oSettings, oInput, iForce )
	{
		var oPrevSearch = oSettings.oPreviousSearch;
		var aoPrevSearch = oSettings.aoPreSearchCols;
		var fnSaveFilter = function ( oFilter ) {
			/* Save the filtering values */
			oPrevSearch.sSearch = oFilter.sSearch;
			oPrevSearch.bRegex = oFilter.bRegex;
			oPrevSearch.bSmart = oFilter.bSmart;
			oPrevSearch.bCaseInsensitive = oFilter.bCaseInsensitive;
		};
		var fnRegex = function ( o ) {
			// Backwards compatibility with the bEscapeRegex option
			return o.bEscapeRegex !== undefined ? !o.bEscapeRegex : o.bRegex;
		};
	
		// Resolve any column types that are unknown due to addition or invalidation
		// @todo As per sort - can this be moved into an event handler?
		_fnColumnTypes( oSettings );
	
		/* In server-side processing all filtering is done by the server, so no point hanging around here */
		if ( _fnDataSource( oSettings ) != 'ssp' )
		{
			/* Global filter */
			_fnFilter( oSettings, oInput.sSearch, iForce, fnRegex(oInput), oInput.bSmart, oInput.bCaseInsensitive );
			fnSaveFilter( oInput );
	
			/* Now do the individual column filter */
			for ( var i=0 ; i<aoPrevSearch.length ; i++ )
			{
				_fnFilterColumn( oSettings, aoPrevSearch[i].sSearch, i, fnRegex(aoPrevSearch[i]),
					aoPrevSearch[i].bSmart, aoPrevSearch[i].bCaseInsensitive );
			}
	
			/* Custom filtering */
			_fnFilterCustom( oSettings );
		}
		else
		{
			fnSaveFilter( oInput );
		}
	
		/* Tell the draw function we have been filtering */
		oSettings.bFiltered = true;
		_fnCallbackFire( oSettings, null, 'search', [oSettings] );
	}
	
	
	/**
	 * Apply custom filtering functions
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnFilterCustom( settings )
	{
		var filters = DataTable.ext.search;
		var displayRows = settings.aiDisplay;
		var row, rowIdx;
	
		for ( var i=0, ien=filters.length ; i<ien ; i++ ) {
			var rows = [];
	
			// Loop over each row and see if it should be included
			for ( var j=0, jen=displayRows.length ; j<jen ; j++ ) {
				rowIdx = displayRows[ j ];
				row = settings.aoData[ rowIdx ];
	
				if ( filters[i]( settings, row._aFilterData, rowIdx, row._aData, j ) ) {
					rows.push( rowIdx );
				}
			}
	
			// So the array reference doesn't break set the results into the
			// existing array
			displayRows.length = 0;
			$.merge( displayRows, rows );
		}
	}
	
	
	/**
	 * Filter the table on a per-column basis
	 *  @param {object} oSettings dataTables settings object
	 *  @param {string} sInput string to filter on
	 *  @param {int} iColumn column to filter
	 *  @param {bool} bRegex treat search string as a regular expression or not
	 *  @param {bool} bSmart use smart filtering or not
	 *  @param {bool} bCaseInsensitive Do case insenstive matching or not
	 *  @memberof DataTable#oApi
	 */
	function _fnFilterColumn ( settings, searchStr, colIdx, regex, smart, caseInsensitive )
	{
		if ( searchStr === '' ) {
			return;
		}
	
		var data;
		var display = settings.aiDisplay;
		var rpSearch = _fnFilterCreateSearch( searchStr, regex, smart, caseInsensitive );
	
		for ( var i=display.length-1 ; i>=0 ; i-- ) {
			data = settings.aoData[ display[i] ]._aFilterData[ colIdx ];
	
			if ( ! rpSearch.test( data ) ) {
				display.splice( i, 1 );
			}
		}
	}
	
	
	/**
	 * Filter the data table based on user input and draw the table
	 *  @param {object} settings dataTables settings object
	 *  @param {string} input string to filter on
	 *  @param {int} force optional - force a research of the master array (1) or not (undefined or 0)
	 *  @param {bool} regex treat as a regular expression or not
	 *  @param {bool} smart perform smart filtering or not
	 *  @param {bool} caseInsensitive Do case insenstive matching or not
	 *  @memberof DataTable#oApi
	 */
	function _fnFilter( settings, input, force, regex, smart, caseInsensitive )
	{
		var rpSearch = _fnFilterCreateSearch( input, regex, smart, caseInsensitive );
		var prevSearch = settings.oPreviousSearch.sSearch;
		var displayMaster = settings.aiDisplayMaster;
		var display, invalidated, i;
	
		// Need to take account of custom filtering functions - always filter
		if ( DataTable.ext.search.length !== 0 ) {
			force = true;
		}
	
		// Check if any of the rows were invalidated
		invalidated = _fnFilterData( settings );
	
		// If the input is blank - we just want the full data set
		if ( input.length <= 0 ) {
			settings.aiDisplay = displayMaster.slice();
		}
		else {
			// New search - start from the master array
			if ( invalidated ||
				 force ||
				 prevSearch.length > input.length ||
				 input.indexOf(prevSearch) !== 0 ||
				 settings.bSorted // On resort, the display master needs to be
				                  // re-filtered since indexes will have changed
			) {
				settings.aiDisplay = displayMaster.slice();
			}
	
			// Search the display array
			display = settings.aiDisplay;
	
			for ( i=display.length-1 ; i>=0 ; i-- ) {
				if ( ! rpSearch.test( settings.aoData[ display[i] ]._sFilterRow ) ) {
					display.splice( i, 1 );
				}
			}
		}
	}
	
	
	/**
	 * Build a regular expression object suitable for searching a table
	 *  @param {string} sSearch string to search for
	 *  @param {bool} bRegex treat as a regular expression or not
	 *  @param {bool} bSmart perform smart filtering or not
	 *  @param {bool} bCaseInsensitive Do case insensitive matching or not
	 *  @returns {RegExp} constructed object
	 *  @memberof DataTable#oApi
	 */
	function _fnFilterCreateSearch( search, regex, smart, caseInsensitive )
	{
		search = regex ?
			search :
			_fnEscapeRegex( search );
		
		if ( smart ) {
			/* For smart filtering we want to allow the search to work regardless of
			 * word order. We also want double quoted text to be preserved, so word
			 * order is important - a la google. So this is what we want to
			 * generate:
			 * 
			 * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$
			 */
			var a = $.map( search.match( /"[^"]+"|[^ ]+/g ) || [''], function ( word ) {
				if ( word.charAt(0) === '"' ) {
					var m = word.match( /^"(.*)"$/ );
					word = m ? m[1] : word;
				}
	
				return word.replace('"', '');
			} );
	
			search = '^(?=.*?'+a.join( ')(?=.*?' )+').*$';
		}
	
		return new RegExp( search, caseInsensitive ? 'i' : '' );
	}
	
	
	/**
	 * Escape a string such that it can be used in a regular expression
	 *  @param {string} sVal string to escape
	 *  @returns {string} escaped string
	 *  @memberof DataTable#oApi
	 */
	function _fnEscapeRegex ( sVal )
	{
		return sVal.replace( _re_escape_regex, '\\$1' );
	}
	
	
	
	var __filter_div = $('<div>')[0];
	var __filter_div_textContent = __filter_div.textContent !== undefined;
	
	// Update the filtering data for each row if needed (by invalidation or first run)
	function _fnFilterData ( settings )
	{
		var columns = settings.aoColumns;
		var column;
		var i, j, ien, jen, filterData, cellData, row;
		var fomatters = DataTable.ext.type.search;
		var wasInvalidated = false;
	
		for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
			row = settings.aoData[i];
	
			if ( ! row._aFilterData ) {
				filterData = [];
	
				for ( j=0, jen=columns.length ; j<jen ; j++ ) {
					column = columns[j];
	
					if ( column.bSearchable ) {
						cellData = _fnGetCellData( settings, i, j, 'filter' );
	
						if ( fomatters[ column.sType ] ) {
							cellData = fomatters[ column.sType ]( cellData );
						}
	
						// Search in DataTables 1.10 is string based. In 1.11 this
						// should be altered to also allow strict type checking.
						if ( cellData === null ) {
							cellData = '';
						}
	
						if ( typeof cellData !== 'string' && cellData.toString ) {
							cellData = cellData.toString();
						}
					}
					else {
						cellData = '';
					}
	
					// If it looks like there is an HTML entity in the string,
					// attempt to decode it so sorting works as expected. Note that
					// we could use a single line of jQuery to do this, but the DOM
					// method used here is much faster http://jsperf.com/html-decode
					if ( cellData.indexOf && cellData.indexOf('&') !== -1 ) {
						__filter_div.innerHTML = cellData;
						cellData = __filter_div_textContent ?
							__filter_div.textContent :
							__filter_div.innerText;
					}
	
					if ( cellData.replace ) {
						cellData = cellData.replace(/[\r\n]/g, '');
					}
	
					filterData.push( cellData );
				}
	
				row._aFilterData = filterData;
				row._sFilterRow = filterData.join('  ');
				wasInvalidated = true;
			}
		}
	
		return wasInvalidated;
	}
	
	
	/**
	 * Convert from the internal Hungarian notation to camelCase for external
	 * interaction
	 *  @param {object} obj Object to convert
	 *  @returns {object} Inverted object
	 *  @memberof DataTable#oApi
	 */
	function _fnSearchToCamel ( obj )
	{
		return {
			search:          obj.sSearch,
			smart:           obj.bSmart,
			regex:           obj.bRegex,
			caseInsensitive: obj.bCaseInsensitive
		};
	}
	
	
	
	/**
	 * Convert from camelCase notation to the internal Hungarian. We could use the
	 * Hungarian convert function here, but this is cleaner
	 *  @param {object} obj Object to convert
	 *  @returns {object} Inverted object
	 *  @memberof DataTable#oApi
	 */
	function _fnSearchToHung ( obj )
	{
		return {
			sSearch:          obj.search,
			bSmart:           obj.smart,
			bRegex:           obj.regex,
			bCaseInsensitive: obj.caseInsensitive
		};
	}
	
	/**
	 * Generate the node required for the info display
	 *  @param {object} oSettings dataTables settings object
	 *  @returns {node} Information element
	 *  @memberof DataTable#oApi
	 */
	function _fnFeatureHtmlInfo ( settings )
	{
		var
			tid = settings.sTableId,
			nodes = settings.aanFeatures.i,
			n = $('<div/>', {
				'class': settings.oClasses.sInfo,
				'id': ! nodes ? tid+'_info' : null
			} );
	
		if ( ! nodes ) {
			// Update display on each draw
			settings.aoDrawCallback.push( {
				"fn": _fnUpdateInfo,
				"sName": "information"
			} );
	
			n
				.attr( 'role', 'status' )
				.attr( 'aria-live', 'polite' );
	
			// Table is described by our info div
			$(settings.nTable).attr( 'aria-describedby', tid+'_info' );
		}
	
		return n[0];
	}
	
	
	/**
	 * Update the information elements in the display
	 *  @param {object} settings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnUpdateInfo ( settings )
	{
		/* Show information about the table */
		var nodes = settings.aanFeatures.i;
		if ( nodes.length === 0 ) {
			return;
		}
	
		var
			lang  = settings.oLanguage,
			start = settings._iDisplayStart+1,
			end   = settings.fnDisplayEnd(),
			max   = settings.fnRecordsTotal(),
			total = settings.fnRecordsDisplay(),
			out   = total ?
				lang.sInfo :
				lang.sInfoEmpty;
	
		if ( total !== max ) {
			/* Record set after filtering */
			out += ' ' + lang.sInfoFiltered;
		}
	
		// Convert the macros
		out += lang.sInfoPostFix;
		out = _fnInfoMacros( settings, out );
	
		var callback = lang.fnInfoCallback;
		if ( callback !== null ) {
			out = callback.call( settings.oInstance,
				settings, start, end, max, total, out
			);
		}
	
		$(nodes).html( out );
	}
	
	
	function _fnInfoMacros ( settings, str )
	{
		// When infinite scrolling, we are always starting at 1. _iDisplayStart is used only
		// internally
		var
			formatter  = settings.fnFormatNumber,
			start      = settings._iDisplayStart+1,
			len        = settings._iDisplayLength,
			vis        = settings.fnRecordsDisplay(),
			all        = len === -1;
	
		return str.
			replace(/_START_/g, formatter.call( settings, start ) ).
			replace(/_END_/g,   formatter.call( settings, settings.fnDisplayEnd() ) ).
			replace(/_MAX_/g,   formatter.call( settings, settings.fnRecordsTotal() ) ).
			replace(/_TOTAL_/g, formatter.call( settings, vis ) ).
			replace(/_PAGE_/g,  formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ).
			replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) );
	}
	
	
	
	/**
	 * Draw the table for the first time, adding all required features
	 *  @param {object} settings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnInitialise ( settings )
	{
		var i, iLen, iAjaxStart=settings.iInitDisplayStart;
		var columns = settings.aoColumns, column;
		var features = settings.oFeatures;
		var deferLoading = settings.bDeferLoading; // value modified by the draw
	
		/* Ensure that the table data is fully initialised */
		if ( ! settings.bInitialised ) {
			setTimeout( function(){ _fnInitialise( settings ); }, 200 );
			return;
		}
	
		/* Show the display HTML options */
		_fnAddOptionsHtml( settings );
	
		/* Build and draw the header / footer for the table */
		_fnBuildHead( settings );
		_fnDrawHead( settings, settings.aoHeader );
		_fnDrawHead( settings, settings.aoFooter );
	
		/* Okay to show that something is going on now */
		_fnProcessingDisplay( settings, true );
	
		/* Calculate sizes for columns */
		if ( features.bAutoWidth ) {
			_fnCalculateColumnWidths( settings );
		}
	
		for ( i=0, iLen=columns.length ; i<iLen ; i++ ) {
			column = columns[i];
	
			if ( column.sWidth ) {
				column.nTh.style.width = _fnStringToCss( column.sWidth );
			}
		}
	
		_fnCallbackFire( settings, null, 'preInit', [settings] );
	
		// If there is default sorting required - let's do it. The sort function
		// will do the drawing for us. Otherwise we draw the table regardless of the
		// Ajax source - this allows the table to look initialised for Ajax sourcing
		// data (show 'loading' message possibly)
		_fnReDraw( settings );
	
		// Server-side processing init complete is done by _fnAjaxUpdateDraw
		var dataSrc = _fnDataSource( settings );
		if ( dataSrc != 'ssp' || deferLoading ) {
			// if there is an ajax source load the data
			if ( dataSrc == 'ajax' ) {
				_fnBuildAjax( settings, [], function(json) {
					var aData = _fnAjaxDataSrc( settings, json );
	
					// Got the data - add it to the table
					for ( i=0 ; i<aData.length ; i++ ) {
						_fnAddData( settings, aData[i] );
					}
	
					// Reset the init display for cookie saving. We've already done
					// a filter, and therefore cleared it before. So we need to make
					// it appear 'fresh'
					settings.iInitDisplayStart = iAjaxStart;
	
					_fnReDraw( settings );
	
					_fnProcessingDisplay( settings, false );
					_fnInitComplete( settings, json );
				}, settings );
			}
			else {
				_fnProcessingDisplay( settings, false );
				_fnInitComplete( settings );
			}
		}
	}
	
	
	/**
	 * Draw the table for the first time, adding all required features
	 *  @param {object} oSettings dataTables settings object
	 *  @param {object} [json] JSON from the server that completed the table, if using Ajax source
	 *    with client-side processing (optional)
	 *  @memberof DataTable#oApi
	 */
	function _fnInitComplete ( settings, json )
	{
		settings._bInitComplete = true;
	
		// When data was added after the initialisation (data or Ajax) we need to
		// calculate the column sizing
		if ( json || settings.oInit.aaData ) {
			_fnAdjustColumnSizing( settings );
		}
	
		_fnCallbackFire( settings, null, 'plugin-init', [settings, json] );
		_fnCallbackFire( settings, 'aoInitComplete', 'init', [settings, json] );
	}
	
	
	function _fnLengthChange ( settings, val )
	{
		var len = parseInt( val, 10 );
		settings._iDisplayLength = len;
	
		_fnLengthOverflow( settings );
	
		// Fire length change event
		_fnCallbackFire( settings, null, 'length', [settings, len] );
	}
	
	
	/**
	 * Generate the node required for user display length changing
	 *  @param {object} settings dataTables settings object
	 *  @returns {node} Display length feature node
	 *  @memberof DataTable#oApi
	 */
	function _fnFeatureHtmlLength ( settings )
	{
		var
			classes  = settings.oClasses,
			tableId  = settings.sTableId,
			menu     = settings.aLengthMenu,
			d2       = $.isArray( menu[0] ),
			lengths  = d2 ? menu[0] : menu,
			language = d2 ? menu[1] : menu;
	
		var select = $('<select/>', {
			'name':          tableId+'_length',
			'aria-controls': tableId,
			'class':         classes.sLengthSelect
		} );
	
		for ( var i=0, ien=lengths.length ; i<ien ; i++ ) {
			select[0][ i ] = new Option( language[i], lengths[i] );
		}
	
		var div = $('<div><label/></div>').addClass( classes.sLength );
		if ( ! settings.aanFeatures.l ) {
			div[0].id = tableId+'_length';
		}
	
		div.children().append(
			settings.oLanguage.sLengthMenu.replace( '_MENU_', select[0].outerHTML )
		);
	
		// Can't use `select` variable as user might provide their own and the
		// reference is broken by the use of outerHTML
		$('select', div)
			.val( settings._iDisplayLength )
			.bind( 'change.DT', function(e) {
				_fnLengthChange( settings, $(this).val() );
				_fnDraw( settings );
			} );
	
		// Update node value whenever anything changes the table's length
		$(settings.nTable).bind( 'length.dt.DT', function (e, s, len) {
			if ( settings === s ) {
				$('select', div).val( len );
			}
		} );
	
		return div[0];
	}
	
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Note that most of the paging logic is done in
	 * DataTable.ext.pager
	 */
	
	/**
	 * Generate the node required for default pagination
	 *  @param {object} oSettings dataTables settings object
	 *  @returns {node} Pagination feature node
	 *  @memberof DataTable#oApi
	 */
	function _fnFeatureHtmlPaginate ( settings )
	{
		var
			type   = settings.sPaginationType,
			plugin = DataTable.ext.pager[ type ],
			modern = typeof plugin === 'function',
			redraw = function( settings ) {
				_fnDraw( settings );
			},
			node = $('<div/>').addClass( settings.oClasses.sPaging + type )[0],
			features = settings.aanFeatures;
	
		if ( ! modern ) {
			plugin.fnInit( settings, node, redraw );
		}
	
		/* Add a draw callback for the pagination on first instance, to update the paging display */
		if ( ! features.p )
		{
			node.id = settings.sTableId+'_paginate';
	
			settings.aoDrawCallback.push( {
				"fn": function( settings ) {
					if ( modern ) {
						var
							start      = settings._iDisplayStart,
							len        = settings._iDisplayLength,
							visRecords = settings.fnRecordsDisplay(),
							all        = len === -1,
							page = all ? 0 : Math.ceil( start / len ),
							pages = all ? 1 : Math.ceil( visRecords / len ),
							buttons = plugin(page, pages),
							i, ien;
	
						for ( i=0, ien=features.p.length ; i<ien ; i++ ) {
							_fnRenderer( settings, 'pageButton' )(
								settings, features.p[i], i, buttons, page, pages
							);
						}
					}
					else {
						plugin.fnUpdate( settings, redraw );
					}
				},
				"sName": "pagination"
			} );
		}
	
		return node;
	}
	
	
	/**
	 * Alter the display settings to change the page
	 *  @param {object} settings DataTables settings object
	 *  @param {string|int} action Paging action to take: "first", "previous",
	 *    "next" or "last" or page number to jump to (integer)
	 *  @param [bool] redraw Automatically draw the update or not
	 *  @returns {bool} true page has changed, false - no change
	 *  @memberof DataTable#oApi
	 */
	function _fnPageChange ( settings, action, redraw )
	{
		var
			start     = settings._iDisplayStart,
			len       = settings._iDisplayLength,
			records   = settings.fnRecordsDisplay();
	
		if ( records === 0 || len === -1 )
		{
			start = 0;
		}
		else if ( typeof action === "number" )
		{
			start = action * len;
	
			if ( start > records )
			{
				start = 0;
			}
		}
		else if ( action == "first" )
		{
			start = 0;
		}
		else if ( action == "previous" )
		{
			start = len >= 0 ?
				start - len :
				0;
	
			if ( start < 0 )
			{
			  start = 0;
			}
		}
		else if ( action == "next" )
		{
			if ( start + len < records )
			{
				start += len;
			}
		}
		else if ( action == "last" )
		{
			start = Math.floor( (records-1) / len) * len;
		}
		else
		{
			_fnLog( settings, 0, "Unknown paging action: "+action, 5 );
		}
	
		var changed = settings._iDisplayStart !== start;
		settings._iDisplayStart = start;
	
		if ( changed ) {
			_fnCallbackFire( settings, null, 'page', [settings] );
	
			if ( redraw ) {
				_fnDraw( settings );
			}
		}
	
		return changed;
	}
	
	
	
	/**
	 * Generate the node required for the processing node
	 *  @param {object} settings dataTables settings object
	 *  @returns {node} Processing element
	 *  @memberof DataTable#oApi
	 */
	function _fnFeatureHtmlProcessing ( settings )
	{
		return $('<div/>', {
				'id': ! settings.aanFeatures.r ? settings.sTableId+'_processing' : null,
				'class': settings.oClasses.sProcessing
			} )
			.html( settings.oLanguage.sProcessing )
			.insertBefore( settings.nTable )[0];
	}
	
	
	/**
	 * Display or hide the processing indicator
	 *  @param {object} settings dataTables settings object
	 *  @param {bool} show Show the processing indicator (true) or not (false)
	 *  @memberof DataTable#oApi
	 */
	function _fnProcessingDisplay ( settings, show )
	{
		if ( settings.oFeatures.bProcessing ) {
			$(settings.aanFeatures.r).css( 'display', show ? 'block' : 'none' );
		}
	
		_fnCallbackFire( settings, null, 'processing', [settings, show] );
	}
	
	/**
	 * Add any control elements for the table - specifically scrolling
	 *  @param {object} settings dataTables settings object
	 *  @returns {node} Node to add to the DOM
	 *  @memberof DataTable#oApi
	 */
	function _fnFeatureHtmlTable ( settings )
	{
		var table = $(settings.nTable);
	
		// Add the ARIA grid role to the table
		table.attr( 'role', 'grid' );
	
		// Scrolling from here on in
		var scroll = settings.oScroll;
	
		if ( scroll.sX === '' && scroll.sY === '' ) {
			return settings.nTable;
		}
	
		var scrollX = scroll.sX;
		var scrollY = scroll.sY;
		var classes = settings.oClasses;
		var caption = table.children('caption');
		var captionSide = caption.length ? caption[0]._captionSide : null;
		var headerClone = $( table[0].cloneNode(false) );
		var footerClone = $( table[0].cloneNode(false) );
		var footer = table.children('tfoot');
		var _div = '<div/>';
		var size = function ( s ) {
			return !s ? null : _fnStringToCss( s );
		};
	
		if ( ! footer.length ) {
			footer = null;
		}
	
		/*
		 * The HTML structure that we want to generate in this function is:
		 *  div - scroller
		 *    div - scroll head
		 *      div - scroll head inner
		 *        table - scroll head table
		 *          thead - thead
		 *    div - scroll body
		 *      table - table (master table)
		 *        thead - thead clone for sizing
		 *        tbody - tbody
		 *    div - scroll foot
		 *      div - scroll foot inner
		 *        table - scroll foot table
		 *          tfoot - tfoot
		 */
		var scroller = $( _div, { 'class': classes.sScrollWrapper } )
			.append(
				$(_div, { 'class': classes.sScrollHead } )
					.css( {
						overflow: 'hidden',
						position: 'relative',
						border: 0,
						width: scrollX ? size(scrollX) : '100%'
					} )
					.append(
						$(_div, { 'class': classes.sScrollHeadInner } )
							.css( {
								'box-sizing': 'content-box',
								width: scroll.sXInner || '100%'
							} )
							.append(
								headerClone
									.removeAttr('id')
									.css( 'margin-left', 0 )
									.append( captionSide === 'top' ? caption : null )
									.append(
										table.children('thead')
									)
							)
					)
			)
			.append(
				$(_div, { 'class': classes.sScrollBody } )
					.css( {
						position: 'relative',
						overflow: 'auto',
						width: size( scrollX )
					} )
					.append( table )
			);
	
		if ( footer ) {
			scroller.append(
				$(_div, { 'class': classes.sScrollFoot } )
					.css( {
						overflow: 'hidden',
						border: 0,
						width: scrollX ? size(scrollX) : '100%'
					} )
					.append(
						$(_div, { 'class': classes.sScrollFootInner } )
							.append(
								footerClone
									.removeAttr('id')
									.css( 'margin-left', 0 )
									.append( captionSide === 'bottom' ? caption : null )
									.append(
										table.children('tfoot')
									)
							)
					)
			);
		}
	
		var children = scroller.children();
		var scrollHead = children[0];
		var scrollBody = children[1];
		var scrollFoot = footer ? children[2] : null;
	
		// When the body is scrolled, then we also want to scroll the headers
		if ( scrollX ) {
			$(scrollBody).on( 'scroll.DT', function (e) {
				var scrollLeft = this.scrollLeft;
	
				scrollHead.scrollLeft = scrollLeft;
	
				if ( footer ) {
					scrollFoot.scrollLeft = scrollLeft;
				}
			} );
		}
	
		$(scrollBody).css(
			scrollY && scroll.bCollapse ? 'max-height' : 'height', 
			scrollY
		);
	
		settings.nScrollHead = scrollHead;
		settings.nScrollBody = scrollBody;
		settings.nScrollFoot = scrollFoot;
	
		// On redraw - align columns
		settings.aoDrawCallback.push( {
			"fn": _fnScrollDraw,
			"sName": "scrolling"
		} );
	
		return scroller[0];
	}
	
	
	
	/**
	 * Update the header, footer and body tables for resizing - i.e. column
	 * alignment.
	 *
	 * Welcome to the most horrible function DataTables. The process that this
	 * function follows is basically:
	 *   1. Re-create the table inside the scrolling div
	 *   2. Take live measurements from the DOM
	 *   3. Apply the measurements to align the columns
	 *   4. Clean up
	 *
	 *  @param {object} settings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnScrollDraw ( settings )
	{
		// Given that this is such a monster function, a lot of variables are use
		// to try and keep the minimised size as small as possible
		var
			scroll         = settings.oScroll,
			scrollX        = scroll.sX,
			scrollXInner   = scroll.sXInner,
			scrollY        = scroll.sY,
			barWidth       = scroll.iBarWidth,
			divHeader      = $(settings.nScrollHead),
			divHeaderStyle = divHeader[0].style,
			divHeaderInner = divHeader.children('div'),
			divHeaderInnerStyle = divHeaderInner[0].style,
			divHeaderTable = divHeaderInner.children('table'),
			divBodyEl      = settings.nScrollBody,
			divBody        = $(divBodyEl),
			divBodyStyle   = divBodyEl.style,
			divFooter      = $(settings.nScrollFoot),
			divFooterInner = divFooter.children('div'),
			divFooterTable = divFooterInner.children('table'),
			header         = $(settings.nTHead),
			table          = $(settings.nTable),
			tableEl        = table[0],
			tableStyle     = tableEl.style,
			footer         = settings.nTFoot ? $(settings.nTFoot) : null,
			browser        = settings.oBrowser,
			ie67           = browser.bScrollOversize,
			headerTrgEls, footerTrgEls,
			headerSrcEls, footerSrcEls,
			headerCopy, footerCopy,
			headerWidths=[], footerWidths=[],
			headerContent=[],
			idx, correction, sanityWidth,
			zeroOut = function(nSizer) {
				var style = nSizer.style;
				style.paddingTop = "0";
				style.paddingBottom = "0";
				style.borderTopWidth = "0";
				style.borderBottomWidth = "0";
				style.height = 0;
			};
	
		// If the scrollbar visibility has changed from the last draw, we need to
		// adjust the column sizes as the table width will have changed to account
		// for the scrollbar
		var scrollBarVis = divBodyEl.scrollHeight > divBodyEl.clientHeight;
		
		if ( settings.scrollBarVis !== scrollBarVis && settings.scrollBarVis !== undefined ) {
			settings.scrollBarVis = scrollBarVis;
			_fnAdjustColumnSizing( settings );
			return; // adjust column sizing will call this function again
		}
		else {
			settings.scrollBarVis = scrollBarVis;
		}
	
		/*
		 * 1. Re-create the table inside the scrolling div
		 */
	
		// Remove the old minimised thead and tfoot elements in the inner table
		table.children('thead, tfoot').remove();
	
		// Clone the current header and footer elements and then place it into the inner table
		headerCopy = header.clone().prependTo( table );
		headerTrgEls = header.find('tr'); // original header is in its own table
		headerSrcEls = headerCopy.find('tr');
		headerCopy.find('th, td').removeAttr('tabindex');
	
		if ( footer ) {
			footerCopy = footer.clone().prependTo( table );
			footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized
			footerSrcEls = footerCopy.find('tr');
		}
	
	
		/*
		 * 2. Take live measurements from the DOM - do not alter the DOM itself!
		 */
	
		// Remove old sizing and apply the calculated column widths
		// Get the unique column headers in the newly created (cloned) header. We want to apply the
		// calculated sizes to this header
		if ( ! scrollX )
		{
			divBodyStyle.width = '100%';
			divHeader[0].style.width = '100%';
		}
	
		$.each( _fnGetUniqueThs( settings, headerCopy ), function ( i, el ) {
			idx = _fnVisibleToColumnIndex( settings, i );
			el.style.width = settings.aoColumns[idx].sWidth;
		} );
	
		if ( footer ) {
			_fnApplyToChildren( function(n) {
				n.style.width = "";
			}, footerSrcEls );
		}
	
		// Size the table as a whole
		sanityWidth = table.outerWidth();
		if ( scrollX === "" ) {
			// No x scrolling
			tableStyle.width = "100%";
	
			// IE7 will make the width of the table when 100% include the scrollbar
			// - which is shouldn't. When there is a scrollbar we need to take this
			// into account.
			if ( ie67 && (table.find('tbody').height() > divBodyEl.offsetHeight ||
				divBody.css('overflow-y') == "scroll")
			) {
				tableStyle.width = _fnStringToCss( table.outerWidth() - barWidth);
			}
	
			// Recalculate the sanity width
			sanityWidth = table.outerWidth();
		}
		else if ( scrollXInner !== "" ) {
			// legacy x scroll inner has been given - use it
			tableStyle.width = _fnStringToCss(scrollXInner);
	
			// Recalculate the sanity width
			sanityWidth = table.outerWidth();
		}
	
		// Hidden header should have zero height, so remove padding and borders. Then
		// set the width based on the real headers
	
		// Apply all styles in one pass
		_fnApplyToChildren( zeroOut, headerSrcEls );
	
		// Read all widths in next pass
		_fnApplyToChildren( function(nSizer) {
			headerContent.push( nSizer.innerHTML );
			headerWidths.push( _fnStringToCss( $(nSizer).css('width') ) );
		}, headerSrcEls );
	
		// Apply all widths in final pass
		_fnApplyToChildren( function(nToSize, i) {
			nToSize.style.width = headerWidths[i];
		}, headerTrgEls );
	
		$(headerSrcEls).height(0);
	
		/* Same again with the footer if we have one */
		if ( footer )
		{
			_fnApplyToChildren( zeroOut, footerSrcEls );
	
			_fnApplyToChildren( function(nSizer) {
				footerWidths.push( _fnStringToCss( $(nSizer).css('width') ) );
			}, footerSrcEls );
	
			_fnApplyToChildren( function(nToSize, i) {
				nToSize.style.width = footerWidths[i];
			}, footerTrgEls );
	
			$(footerSrcEls).height(0);
		}
	
	
		/*
		 * 3. Apply the measurements
		 */
	
		// "Hide" the header and footer that we used for the sizing. We need to keep
		// the content of the cell so that the width applied to the header and body
		// both match, but we want to hide it completely. We want to also fix their
		// width to what they currently are
		_fnApplyToChildren( function(nSizer, i) {
			nSizer.innerHTML = '<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+headerContent[i]+'</div>';
			nSizer.style.width = headerWidths[i];
		}, headerSrcEls );
	
		if ( footer )
		{
			_fnApplyToChildren( function(nSizer, i) {
				nSizer.innerHTML = "";
				nSizer.style.width = footerWidths[i];
			}, footerSrcEls );
		}
	
		// Sanity check that the table is of a sensible width. If not then we are going to get
		// misalignment - try to prevent this by not allowing the table to shrink below its min width
		if ( table.outerWidth() < sanityWidth )
		{
			// The min width depends upon if we have a vertical scrollbar visible or not */
			correction = ((divBodyEl.scrollHeight > divBodyEl.offsetHeight ||
				divBody.css('overflow-y') == "scroll")) ?
					sanityWidth+barWidth :
					sanityWidth;
	
			// IE6/7 are a law unto themselves...
			if ( ie67 && (divBodyEl.scrollHeight >
				divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll")
			) {
				tableStyle.width = _fnStringToCss( correction-barWidth );
			}
	
			// And give the user a warning that we've stopped the table getting too small
			if ( scrollX === "" || scrollXInner !== "" ) {
				_fnLog( settings, 1, 'Possible column misalignment', 6 );
			}
		}
		else
		{
			correction = '100%';
		}
	
		// Apply to the container elements
		divBodyStyle.width = _fnStringToCss( correction );
		divHeaderStyle.width = _fnStringToCss( correction );
	
		if ( footer ) {
			settings.nScrollFoot.style.width = _fnStringToCss( correction );
		}
	
	
		/*
		 * 4. Clean up
		 */
		if ( ! scrollY ) {
			/* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting
			 * the scrollbar height from the visible display, rather than adding it on. We need to
			 * set the height in order to sort this. Don't want to do it in any other browsers.
			 */
			if ( ie67 ) {
				divBodyStyle.height = _fnStringToCss( tableEl.offsetHeight+barWidth );
			}
		}
	
		/* Finally set the width's of the header and footer tables */
		var iOuterWidth = table.outerWidth();
		divHeaderTable[0].style.width = _fnStringToCss( iOuterWidth );
		divHeaderInnerStyle.width = _fnStringToCss( iOuterWidth );
	
		// Figure out if there are scrollbar present - if so then we need a the header and footer to
		// provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar)
		var bScrolling = table.height() > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll";
		var padding = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' );
		divHeaderInnerStyle[ padding ] = bScrolling ? barWidth+"px" : "0px";
	
		if ( footer ) {
			divFooterTable[0].style.width = _fnStringToCss( iOuterWidth );
			divFooterInner[0].style.width = _fnStringToCss( iOuterWidth );
			divFooterInner[0].style[padding] = bScrolling ? barWidth+"px" : "0px";
		}
	
		/* Adjust the position of the header in case we loose the y-scrollbar */
		divBody.scroll();
	
		// If sorting or filtering has occurred, jump the scrolling back to the top
		// only if we aren't holding the position
		if ( (settings.bSorted || settings.bFiltered) && ! settings._drawHold ) {
			divBodyEl.scrollTop = 0;
		}
	}
	
	
	
	/**
	 * Apply a given function to the display child nodes of an element array (typically
	 * TD children of TR rows
	 *  @param {function} fn Method to apply to the objects
	 *  @param array {nodes} an1 List of elements to look through for display children
	 *  @param array {nodes} an2 Another list (identical structure to the first) - optional
	 *  @memberof DataTable#oApi
	 */
	function _fnApplyToChildren( fn, an1, an2 )
	{
		var index=0, i=0, iLen=an1.length;
		var nNode1, nNode2;
	
		while ( i < iLen ) {
			nNode1 = an1[i].firstChild;
			nNode2 = an2 ? an2[i].firstChild : null;
	
			while ( nNode1 ) {
				if ( nNode1.nodeType === 1 ) {
					if ( an2 ) {
						fn( nNode1, nNode2, index );
					}
					else {
						fn( nNode1, index );
					}
	
					index++;
				}
	
				nNode1 = nNode1.nextSibling;
				nNode2 = an2 ? nNode2.nextSibling : null;
			}
	
			i++;
		}
	}
	
	
	
	var __re_html_remove = /<.*?>/g;
	
	
	/**
	 * Calculate the width of columns for the table
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnCalculateColumnWidths ( oSettings )
	{
		var
			table = oSettings.nTable,
			columns = oSettings.aoColumns,
			scroll = oSettings.oScroll,
			scrollY = scroll.sY,
			scrollX = scroll.sX,
			scrollXInner = scroll.sXInner,
			columnCount = columns.length,
			visibleColumns = _fnGetColumns( oSettings, 'bVisible' ),
			headerCells = $('th', oSettings.nTHead),
			tableWidthAttr = table.getAttribute('width'), // from DOM element
			tableContainer = table.parentNode,
			userInputs = false,
			i, column, columnIdx, width, outerWidth,
			browser = oSettings.oBrowser,
			ie67 = browser.bScrollOversize;
	
		var styleWidth = table.style.width;
		if ( styleWidth && styleWidth.indexOf('%') !== -1 ) {
			tableWidthAttr = styleWidth;
		}
	
		/* Convert any user input sizes into pixel sizes */
		for ( i=0 ; i<visibleColumns.length ; i++ ) {
			column = columns[ visibleColumns[i] ];
	
			if ( column.sWidth !== null ) {
				column.sWidth = _fnConvertToWidth( column.sWidthOrig, tableContainer );
	
				userInputs = true;
			}
		}
	
		/* If the number of columns in the DOM equals the number that we have to
		 * process in DataTables, then we can use the offsets that are created by
		 * the web- browser. No custom sizes can be set in order for this to happen,
		 * nor scrolling used
		 */
		if ( ie67 || ! userInputs && ! scrollX && ! scrollY &&
		     columnCount == _fnVisbleColumns( oSettings ) &&
		     columnCount == headerCells.length
		) {
			for ( i=0 ; i<columnCount ; i++ ) {
				var colIdx = _fnVisibleToColumnIndex( oSettings, i );
	
				if ( colIdx !== null ) {
					columns[ colIdx ].sWidth = _fnStringToCss( headerCells.eq(i).width() );
				}
			}
		}
		else
		{
			// Otherwise construct a single row, worst case, table with the widest
			// node in the data, assign any user defined widths, then insert it into
			// the DOM and allow the browser to do all the hard work of calculating
			// table widths
			var tmpTable = $(table).clone() // don't use cloneNode - IE8 will remove events on the main table
				.css( 'visibility', 'hidden' )
				.removeAttr( 'id' );
	
			// Clean up the table body
			tmpTable.find('tbody tr').remove();
			var tr = $('<tr/>').appendTo( tmpTable.find('tbody') );
	
			// Clone the table header and footer - we can't use the header / footer
			// from the cloned table, since if scrolling is active, the table's
			// real header and footer are contained in different table tags
			tmpTable.find('thead, tfoot').remove();
			tmpTable
				.append( $(oSettings.nTHead).clone() )
				.append( $(oSettings.nTFoot).clone() );
	
			// Remove any assigned widths from the footer (from scrolling)
			tmpTable.find('tfoot th, tfoot td').css('width', '');
	
			// Apply custom sizing to the cloned header
			headerCells = _fnGetUniqueThs( oSettings, tmpTable.find('thead')[0] );
	
			for ( i=0 ; i<visibleColumns.length ; i++ ) {
				column = columns[ visibleColumns[i] ];
	
				headerCells[i].style.width = column.sWidthOrig !== null && column.sWidthOrig !== '' ?
					_fnStringToCss( column.sWidthOrig ) :
					'';
	
				// For scrollX we need to force the column width otherwise the
				// browser will collapse it. If this width is smaller than the
				// width the column requires, then it will have no effect
				if ( column.sWidthOrig && scrollX ) {
					$( headerCells[i] ).append( $('<div/>').css( {
						width: column.sWidthOrig,
						margin: 0,
						padding: 0,
						border: 0,
						height: 1
					} ) );
				}
			}
	
			// Find the widest cell for each column and put it into the table
			if ( oSettings.aoData.length ) {
				for ( i=0 ; i<visibleColumns.length ; i++ ) {
					columnIdx = visibleColumns[i];
					column = columns[ columnIdx ];
	
					$( _fnGetWidestNode( oSettings, columnIdx ) )
						.clone( false )
						.append( column.sContentPadding )
						.appendTo( tr );
				}
			}
	
			// Table has been built, attach to the document so we can work with it.
			// A holding element is used, positioned at the top of the container
			// with minimal height, so it has no effect on if the container scrolls
			// or not. Otherwise it might trigger scrolling when it actually isn't
			// needed
			var holder = $('<div/>').css( scrollX || scrollY ?
					{
						position: 'absolute',
						top: 0,
						left: 0,
						height: 1,
						right: 0,
						overflow: 'hidden'
					} :
					{}
				)
				.append( tmpTable )
				.appendTo( tableContainer );
	
			// When scrolling (X or Y) we want to set the width of the table as 
			// appropriate. However, when not scrolling leave the table width as it
			// is. This results in slightly different, but I think correct behaviour
			if ( scrollX && scrollXInner ) {
				tmpTable.width( scrollXInner );
			}
			else if ( scrollX ) {
				tmpTable.css( 'width', 'auto' );
				tmpTable.removeAttr('width');
	
				// If there is no width attribute or style, then allow the table to
				// collapse
				if ( tmpTable.width() < tableContainer.clientWidth && tableWidthAttr ) {
					tmpTable.width( tableContainer.clientWidth );
				}
			}
			else if ( scrollY ) {
				tmpTable.width( tableContainer.clientWidth );
			}
			else if ( tableWidthAttr ) {
				tmpTable.width( tableWidthAttr );
			}
	
			// Get the width of each column in the constructed table - we need to
			// know the inner width (so it can be assigned to the other table's
			// cells) and the outer width so we can calculate the full width of the
			// table. This is safe since DataTables requires a unique cell for each
			// column, but if ever a header can span multiple columns, this will
			// need to be modified.
			var total = 0;
			for ( i=0 ; i<visibleColumns.length ; i++ ) {
				var cell = $(headerCells[i]);
				var border = cell.outerWidth() - cell.width();
	
				// Use getBounding... where possible (not IE8-) because it can give
				// sub-pixel accuracy, which we then want to round up!
				var bounding = browser.bBounding ?
					Math.ceil( headerCells[i].getBoundingClientRect().width ) :
					cell.outerWidth();
	
				// Total is tracked to remove any sub-pixel errors as the outerWidth
				// of the table might not equal the total given here (IE!).
				total += bounding;
	
				// Width for each column to use
				columns[ visibleColumns[i] ].sWidth = _fnStringToCss( bounding - border );
			}
	
			table.style.width = _fnStringToCss( total );
	
			// Finished with the table - ditch it
			holder.remove();
		}
	
		// If there is a width attr, we want to attach an event listener which
		// allows the table sizing to automatically adjust when the window is
		// resized. Use the width attr rather than CSS, since we can't know if the
		// CSS is a relative value or absolute - DOM read is always px.
		if ( tableWidthAttr ) {
			table.style.width = _fnStringToCss( tableWidthAttr );
		}
	
		if ( (tableWidthAttr || scrollX) && ! oSettings._reszEvt ) {
			var bindResize = function () {
				$(window).bind('resize.DT-'+oSettings.sInstance, _fnThrottle( function () {
					_fnAdjustColumnSizing( oSettings );
				} ) );
			};
	
			// IE6/7 will crash if we bind a resize event handler on page load.
			// To be removed in 1.11 which drops IE6/7 support
			if ( ie67 ) {
				setTimeout( bindResize, 1000 );
			}
			else {
				bindResize();
			}
	
			oSettings._reszEvt = true;
		}
	}
	
	
	/**
	 * Throttle the calls to a function. Arguments and context are maintained for
	 * the throttled function
	 *  @param {function} fn Function to be called
	 *  @param {int} [freq=200] call frequency in mS
	 *  @returns {function} wrapped function
	 *  @memberof DataTable#oApi
	 */
	function _fnThrottle( fn, freq ) {
		var
			frequency = freq !== undefined ? freq : 200,
			last,
			timer;
	
		return function () {
			var
				that = this,
				now  = +new Date(),
				args = arguments;
	
			if ( last && now < last + frequency ) {
				clearTimeout( timer );
	
				timer = setTimeout( function () {
					last = undefined;
					fn.apply( that, args );
				}, frequency );
			}
			else {
				last = now;
				fn.apply( that, args );
			}
		};
	}
	
	
	/**
	 * Convert a CSS unit width to pixels (e.g. 2em)
	 *  @param {string} width width to be converted
	 *  @param {node} parent parent to get the with for (required for relative widths) - optional
	 *  @returns {int} width in pixels
	 *  @memberof DataTable#oApi
	 */
	function _fnConvertToWidth ( width, parent )
	{
		if ( ! width ) {
			return 0;
		}
	
		var n = $('<div/>')
			.css( 'width', _fnStringToCss( width ) )
			.appendTo( parent || document.body );
	
		var val = n[0].offsetWidth;
		n.remove();
	
		return val;
	}
	
	
	/**
	 * Get the widest node
	 *  @param {object} settings dataTables settings object
	 *  @param {int} colIdx column of interest
	 *  @returns {node} widest table node
	 *  @memberof DataTable#oApi
	 */
	function _fnGetWidestNode( settings, colIdx )
	{
		var idx = _fnGetMaxLenString( settings, colIdx );
		if ( idx < 0 ) {
			return null;
		}
	
		var data = settings.aoData[ idx ];
		return ! data.nTr ? // Might not have been created when deferred rendering
			$('<td/>').html( _fnGetCellData( settings, idx, colIdx, 'display' ) )[0] :
			data.anCells[ colIdx ];
	}
	
	
	/**
	 * Get the maximum strlen for each data column
	 *  @param {object} settings dataTables settings object
	 *  @param {int} colIdx column of interest
	 *  @returns {string} max string length for each column
	 *  @memberof DataTable#oApi
	 */
	function _fnGetMaxLenString( settings, colIdx )
	{
		var s, max=-1, maxIdx = -1;
	
		for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
			s = _fnGetCellData( settings, i, colIdx, 'display' )+'';
			s = s.replace( __re_html_remove, '' );
			s = s.replace( /&nbsp;/g, ' ' );
	
			if ( s.length > max ) {
				max = s.length;
				maxIdx = i;
			}
		}
	
		return maxIdx;
	}
	
	
	/**
	 * Append a CSS unit (only if required) to a string
	 *  @param {string} value to css-ify
	 *  @returns {string} value with css unit
	 *  @memberof DataTable#oApi
	 */
	function _fnStringToCss( s )
	{
		if ( s === null ) {
			return '0px';
		}
	
		if ( typeof s == 'number' ) {
			return s < 0 ?
				'0px' :
				s+'px';
		}
	
		// Check it has a unit character already
		return s.match(/\d$/) ?
			s+'px' :
			s;
	}
	
	
	
	function _fnSortFlatten ( settings )
	{
		var
			i, iLen, k, kLen,
			aSort = [],
			aiOrig = [],
			aoColumns = settings.aoColumns,
			aDataSort, iCol, sType, srcCol,
			fixed = settings.aaSortingFixed,
			fixedObj = $.isPlainObject( fixed ),
			nestedSort = [],
			add = function ( a ) {
				if ( a.length && ! $.isArray( a[0] ) ) {
					// 1D array
					nestedSort.push( a );
				}
				else {
					// 2D array
					$.merge( nestedSort, a );
				}
			};
	
		// Build the sort array, with pre-fix and post-fix options if they have been
		// specified
		if ( $.isArray( fixed ) ) {
			add( fixed );
		}
	
		if ( fixedObj && fixed.pre ) {
			add( fixed.pre );
		}
	
		add( settings.aaSorting );
	
		if (fixedObj && fixed.post ) {
			add( fixed.post );
		}
	
		for ( i=0 ; i<nestedSort.length ; i++ )
		{
			srcCol = nestedSort[i][0];
			aDataSort = aoColumns[ srcCol ].aDataSort;
	
			for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
			{
				iCol = aDataSort[k];
				sType = aoColumns[ iCol ].sType || 'string';
	
				if ( nestedSort[i]._idx === undefined ) {
					nestedSort[i]._idx = $.inArray( nestedSort[i][1], aoColumns[iCol].asSorting );
				}
	
				aSort.push( {
					src:       srcCol,
					col:       iCol,
					dir:       nestedSort[i][1],
					index:     nestedSort[i]._idx,
					type:      sType,
					formatter: DataTable.ext.type.order[ sType+"-pre" ]
				} );
			}
		}
	
		return aSort;
	}
	
	/**
	 * Change the order of the table
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 *  @todo This really needs split up!
	 */
	function _fnSort ( oSettings )
	{
		var
			i, ien, iLen, j, jLen, k, kLen,
			sDataType, nTh,
			aiOrig = [],
			oExtSort = DataTable.ext.type.order,
			aoData = oSettings.aoData,
			aoColumns = oSettings.aoColumns,
			aDataSort, data, iCol, sType, oSort,
			formatters = 0,
			sortCol,
			displayMaster = oSettings.aiDisplayMaster,
			aSort;
	
		// Resolve any column types that are unknown due to addition or invalidation
		// @todo Can this be moved into a 'data-ready' handler which is called when
		//   data is going to be used in the table?
		_fnColumnTypes( oSettings );
	
		aSort = _fnSortFlatten( oSettings );
	
		for ( i=0, ien=aSort.length ; i<ien ; i++ ) {
			sortCol = aSort[i];
	
			// Track if we can use the fast sort algorithm
			if ( sortCol.formatter ) {
				formatters++;
			}
	
			// Load the data needed for the sort, for each cell
			_fnSortData( oSettings, sortCol.col );
		}
	
		/* No sorting required if server-side or no sorting array */
		if ( _fnDataSource( oSettings ) != 'ssp' && aSort.length !== 0 )
		{
			// Create a value - key array of the current row positions such that we can use their
			// current position during the sort, if values match, in order to perform stable sorting
			for ( i=0, iLen=displayMaster.length ; i<iLen ; i++ ) {
				aiOrig[ displayMaster[i] ] = i;
			}
	
			/* Do the sort - here we want multi-column sorting based on a given data source (column)
			 * and sorting function (from oSort) in a certain direction. It's reasonably complex to
			 * follow on it's own, but this is what we want (example two column sorting):
			 *  fnLocalSorting = function(a,b){
			 *    var iTest;
			 *    iTest = oSort['string-asc']('data11', 'data12');
			 *      if (iTest !== 0)
			 *        return iTest;
			 *    iTest = oSort['numeric-desc']('data21', 'data22');
			 *    if (iTest !== 0)
			 *      return iTest;
			 *    return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
			 *  }
			 * Basically we have a test for each sorting column, if the data in that column is equal,
			 * test the next column. If all columns match, then we use a numeric sort on the row
			 * positions in the original data array to provide a stable sort.
			 *
			 * Note - I know it seems excessive to have two sorting methods, but the first is around
			 * 15% faster, so the second is only maintained for backwards compatibility with sorting
			 * methods which do not have a pre-sort formatting function.
			 */
			if ( formatters === aSort.length ) {
				// All sort types have formatting functions
				displayMaster.sort( function ( a, b ) {
					var
						x, y, k, test, sort,
						len=aSort.length,
						dataA = aoData[a]._aSortData,
						dataB = aoData[b]._aSortData;
	
					for ( k=0 ; k<len ; k++ ) {
						sort = aSort[k];
	
						x = dataA[ sort.col ];
						y = dataB[ sort.col ];
	
						test = x<y ? -1 : x>y ? 1 : 0;
						if ( test !== 0 ) {
							return sort.dir === 'asc' ? test : -test;
						}
					}
	
					x = aiOrig[a];
					y = aiOrig[b];
					return x<y ? -1 : x>y ? 1 : 0;
				} );
			}
			else {
				// Depreciated - remove in 1.11 (providing a plug-in option)
				// Not all sort types have formatting methods, so we have to call their sorting
				// methods.
				displayMaster.sort( function ( a, b ) {
					var
						x, y, k, l, test, sort, fn,
						len=aSort.length,
						dataA = aoData[a]._aSortData,
						dataB = aoData[b]._aSortData;
	
					for ( k=0 ; k<len ; k++ ) {
						sort = aSort[k];
	
						x = dataA[ sort.col ];
						y = dataB[ sort.col ];
	
						fn = oExtSort[ sort.type+"-"+sort.dir ] || oExtSort[ "string-"+sort.dir ];
						test = fn( x, y );
						if ( test !== 0 ) {
							return test;
						}
					}
	
					x = aiOrig[a];
					y = aiOrig[b];
					return x<y ? -1 : x>y ? 1 : 0;
				} );
			}
		}
	
		/* Tell the draw function that we have sorted the data */
		oSettings.bSorted = true;
	}
	
	
	function _fnSortAria ( settings )
	{
		var label;
		var nextSort;
		var columns = settings.aoColumns;
		var aSort = _fnSortFlatten( settings );
		var oAria = settings.oLanguage.oAria;
	
		// ARIA attributes - need to loop all columns, to update all (removing old
		// attributes as needed)
		for ( var i=0, iLen=columns.length ; i<iLen ; i++ )
		{
			var col = columns[i];
			var asSorting = col.asSorting;
			var sTitle = col.sTitle.replace( /<.*?>/g, "" );
			var th = col.nTh;
	
			// IE7 is throwing an error when setting these properties with jQuery's
			// attr() and removeAttr() methods...
			th.removeAttribute('aria-sort');
	
			/* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */
			if ( col.bSortable ) {
				if ( aSort.length > 0 && aSort[0].col == i ) {
					th.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" );
					nextSort = asSorting[ aSort[0].index+1 ] || asSorting[0];
				}
				else {
					nextSort = asSorting[0];
				}
	
				label = sTitle + ( nextSort === "asc" ?
					oAria.sSortAscending :
					oAria.sSortDescending
				);
			}
			else {
				label = sTitle;
			}
	
			th.setAttribute('aria-label', label);
		}
	}
	
	
	/**
	 * Function to run on user sort request
	 *  @param {object} settings dataTables settings object
	 *  @param {node} attachTo node to attach the handler to
	 *  @param {int} colIdx column sorting index
	 *  @param {boolean} [append=false] Append the requested sort to the existing
	 *    sort if true (i.e. multi-column sort)
	 *  @param {function} [callback] callback function
	 *  @memberof DataTable#oApi
	 */
	function _fnSortListener ( settings, colIdx, append, callback )
	{
		var col = settings.aoColumns[ colIdx ];
		var sorting = settings.aaSorting;
		var asSorting = col.asSorting;
		var nextSortIdx;
		var next = function ( a, overflow ) {
			var idx = a._idx;
			if ( idx === undefined ) {
				idx = $.inArray( a[1], asSorting );
			}
	
			return idx+1 < asSorting.length ?
				idx+1 :
				overflow ?
					null :
					0;
		};
	
		// Convert to 2D array if needed
		if ( typeof sorting[0] === 'number' ) {
			sorting = settings.aaSorting = [ sorting ];
		}
	
		// If appending the sort then we are multi-column sorting
		if ( append && settings.oFeatures.bSortMulti ) {
			// Are we already doing some kind of sort on this column?
			var sortIdx = $.inArray( colIdx, _pluck(sorting, '0') );
	
			if ( sortIdx !== -1 ) {
				// Yes, modify the sort
				nextSortIdx = next( sorting[sortIdx], true );
	
				if ( nextSortIdx === null && sorting.length === 1 ) {
					nextSortIdx = 0; // can't remove sorting completely
				}
	
				if ( nextSortIdx === null ) {
					sorting.splice( sortIdx, 1 );
				}
				else {
					sorting[sortIdx][1] = asSorting[ nextSortIdx ];
					sorting[sortIdx]._idx = nextSortIdx;
				}
			}
			else {
				// No sort on this column yet
				sorting.push( [ colIdx, asSorting[0], 0 ] );
				sorting[sorting.length-1]._idx = 0;
			}
		}
		else if ( sorting.length && sorting[0][0] == colIdx ) {
			// Single column - already sorting on this column, modify the sort
			nextSortIdx = next( sorting[0] );
	
			sorting.length = 1;
			sorting[0][1] = asSorting[ nextSortIdx ];
			sorting[0]._idx = nextSortIdx;
		}
		else {
			// Single column - sort only on this column
			sorting.length = 0;
			sorting.push( [ colIdx, asSorting[0] ] );
			sorting[0]._idx = 0;
		}
	
		// Run the sort by calling a full redraw
		_fnReDraw( settings );
	
		// callback used for async user interaction
		if ( typeof callback == 'function' ) {
			callback( settings );
		}
	}
	
	
	/**
	 * Attach a sort handler (click) to a node
	 *  @param {object} settings dataTables settings object
	 *  @param {node} attachTo node to attach the handler to
	 *  @param {int} colIdx column sorting index
	 *  @param {function} [callback] callback function
	 *  @memberof DataTable#oApi
	 */
	function _fnSortAttachListener ( settings, attachTo, colIdx, callback )
	{
		var col = settings.aoColumns[ colIdx ];
	
		_fnBindAction( attachTo, {}, function (e) {
			/* If the column is not sortable - don't to anything */
			if ( col.bSortable === false ) {
				return;
			}
	
			// If processing is enabled use a timeout to allow the processing
			// display to be shown - otherwise to it synchronously
			if ( settings.oFeatures.bProcessing ) {
				_fnProcessingDisplay( settings, true );
	
				setTimeout( function() {
					_fnSortListener( settings, colIdx, e.shiftKey, callback );
	
					// In server-side processing, the draw callback will remove the
					// processing display
					if ( _fnDataSource( settings ) !== 'ssp' ) {
						_fnProcessingDisplay( settings, false );
					}
				}, 0 );
			}
			else {
				_fnSortListener( settings, colIdx, e.shiftKey, callback );
			}
		} );
	}
	
	
	/**
	 * Set the sorting classes on table's body, Note: it is safe to call this function
	 * when bSort and bSortClasses are false
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnSortingClasses( settings )
	{
		var oldSort = settings.aLastSort;
		var sortClass = settings.oClasses.sSortColumn;
		var sort = _fnSortFlatten( settings );
		var features = settings.oFeatures;
		var i, ien, colIdx;
	
		if ( features.bSort && features.bSortClasses ) {
			// Remove old sorting classes
			for ( i=0, ien=oldSort.length ; i<ien ; i++ ) {
				colIdx = oldSort[i].src;
	
				// Remove column sorting
				$( _pluck( settings.aoData, 'anCells', colIdx ) )
					.removeClass( sortClass + (i<2 ? i+1 : 3) );
			}
	
			// Add new column sorting
			for ( i=0, ien=sort.length ; i<ien ; i++ ) {
				colIdx = sort[i].src;
	
				$( _pluck( settings.aoData, 'anCells', colIdx ) )
					.addClass( sortClass + (i<2 ? i+1 : 3) );
			}
		}
	
		settings.aLastSort = sort;
	}
	
	
	// Get the data to sort a column, be it from cache, fresh (populating the
	// cache), or from a sort formatter
	function _fnSortData( settings, idx )
	{
		// Custom sorting function - provided by the sort data type
		var column = settings.aoColumns[ idx ];
		var customSort = DataTable.ext.order[ column.sSortDataType ];
		var customData;
	
		if ( customSort ) {
			customData = customSort.call( settings.oInstance, settings, idx,
				_fnColumnIndexToVisible( settings, idx )
			);
		}
	
		// Use / populate cache
		var row, cellData;
		var formatter = DataTable.ext.type.order[ column.sType+"-pre" ];
	
		for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
			row = settings.aoData[i];
	
			if ( ! row._aSortData ) {
				row._aSortData = [];
			}
	
			if ( ! row._aSortData[idx] || customSort ) {
				cellData = customSort ?
					customData[i] : // If there was a custom sort function, use data from there
					_fnGetCellData( settings, i, idx, 'sort' );
	
				row._aSortData[ idx ] = formatter ?
					formatter( cellData ) :
					cellData;
			}
		}
	}
	
	
	
	/**
	 * Save the state of a table
	 *  @param {object} oSettings dataTables settings object
	 *  @memberof DataTable#oApi
	 */
	function _fnSaveState ( settings )
	{
		if ( !settings.oFeatures.bStateSave || settings.bDestroying )
		{
			return;
		}
	
		/* Store the interesting variables */
		var state = {
			time:    +new Date(),
			start:   settings._iDisplayStart,
			length:  settings._iDisplayLength,
			order:   $.extend( true, [], settings.aaSorting ),
			search:  _fnSearchToCamel( settings.oPreviousSearch ),
			columns: $.map( settings.aoColumns, function ( col, i ) {
				return {
					visible: col.bVisible,
					search: _fnSearchToCamel( settings.aoPreSearchCols[i] )
				};
			} )
		};
	
		_fnCallbackFire( settings, "aoStateSaveParams", 'stateSaveParams', [settings, state] );
	
		settings.oSavedState = state;
		settings.fnStateSaveCallback.call( settings.oInstance, settings, state );
	}
	
	
	/**
	 * Attempt to load a saved table state
	 *  @param {object} oSettings dataTables settings object
	 *  @param {object} oInit DataTables init object so we can override settings
	 *  @memberof DataTable#oApi
	 */
	function _fnLoadState ( settings, oInit )
	{
		var i, ien;
		var columns = settings.aoColumns;
	
		if ( ! settings.oFeatures.bStateSave ) {
			return;
		}
	
		var state = settings.fnStateLoadCallback.call( settings.oInstance, settings );
		if ( ! state || ! state.time ) {
			return;
		}
	
		/* Allow custom and plug-in manipulation functions to alter the saved data set and
		 * cancelling of loading by returning false
		 */
		var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, state] );
		if ( $.inArray( false, abStateLoad ) !== -1 ) {
			return;
		}
	
		/* Reject old data */
		var duration = settings.iStateDuration;
		if ( duration > 0 && state.time < +new Date() - (duration*1000) ) {
			return;
		}
	
		// Number of columns have changed - all bets are off, no restore of settings
		if ( columns.length !== state.columns.length ) {
			return;
		}
	
		// Store the saved state so it might be accessed at any time
		settings.oLoadedState = $.extend( true, {}, state );
	
		// Restore key features - todo - for 1.11 this needs to be done by
		// subscribed events
		if ( state.start !== undefined ) {
			settings._iDisplayStart    = state.start;
			settings.iInitDisplayStart = state.start;
		}
		if ( state.length !== undefined ) {
			settings._iDisplayLength   = state.length;
		}
	
		// Order
		if ( state.order !== undefined ) {
			settings.aaSorting = [];
			$.each( state.order, function ( i, col ) {
				settings.aaSorting.push( col[0] >= columns.length ?
					[ 0, col[1] ] :
					col
				);
			} );
		}
	
		// Search
		if ( state.search !== undefined ) {
			$.extend( settings.oPreviousSearch, _fnSearchToHung( state.search ) );
		}
	
		// Columns
		for ( i=0, ien=state.columns.length ; i<ien ; i++ ) {
			var col = state.columns[i];
	
			// Visibility
			if ( col.visible !== undefined ) {
				columns[i].bVisible = col.visible;
			}
	
			// Search
			if ( col.search !== undefined ) {
				$.extend( settings.aoPreSearchCols[i], _fnSearchToHung( col.search ) );
			}
		}
	
		_fnCallbackFire( settings, 'aoStateLoaded', 'stateLoaded', [settings, state] );
	}
	
	
	/**
	 * Return the settings object for a particular table
	 *  @param {node} table table we are using as a dataTable
	 *  @returns {object} Settings object - or null if not found
	 *  @memberof DataTable#oApi
	 */
	function _fnSettingsFromNode ( table )
	{
		var settings = DataTable.settings;
		var idx = $.inArray( table, _pluck( settings, 'nTable' ) );
	
		return idx !== -1 ?
			settings[ idx ] :
			null;
	}
	
	
	/**
	 * Log an error message
	 *  @param {object} settings dataTables settings object
	 *  @param {int} level log error messages, or display them to the user
	 *  @param {string} msg error message
	 *  @param {int} tn Technical note id to get more information about the error.
	 *  @memberof DataTable#oApi
	 */
	function _fnLog( settings, level, msg, tn )
	{
		msg = 'DataTables warning: '+
			(settings ? 'table id='+settings.sTableId+' - ' : '')+msg;
	
		if ( tn ) {
			msg += '. For more information about this error, please see '+
			'http://datatables.net/tn/'+tn;
		}
	
		if ( ! level  ) {
			// Backwards compatibility pre 1.10
			var ext = DataTable.ext;
			var type = ext.sErrMode || ext.errMode;
	
			if ( settings ) {
				_fnCallbackFire( settings, null, 'error', [ settings, tn, msg ] );
			}
	
			if ( type == 'alert' ) {
				alert( msg );
			}
			else if ( type == 'throw' ) {
				throw new Error(msg);
			}
			else if ( typeof type == 'function' ) {
				type( settings, tn, msg );
			}
		}
		else if ( window.console && console.log ) {
			console.log( msg );
		}
	}
	
	
	/**
	 * See if a property is defined on one object, if so assign it to the other object
	 *  @param {object} ret target object
	 *  @param {object} src source object
	 *  @param {string} name property
	 *  @param {string} [mappedName] name to map too - optional, name used if not given
	 *  @memberof DataTable#oApi
	 */
	function _fnMap( ret, src, name, mappedName )
	{
		if ( $.isArray( name ) ) {
			$.each( name, function (i, val) {
				if ( $.isArray( val ) ) {
					_fnMap( ret, src, val[0], val[1] );
				}
				else {
					_fnMap( ret, src, val );
				}
			} );
	
			return;
		}
	
		if ( mappedName === undefined ) {
			mappedName = name;
		}
	
		if ( src[name] !== undefined ) {
			ret[mappedName] = src[name];
		}
	}
	
	
	/**
	 * Extend objects - very similar to jQuery.extend, but deep copy objects, and
	 * shallow copy arrays. The reason we need to do this, is that we don't want to
	 * deep copy array init values (such as aaSorting) since the dev wouldn't be
	 * able to override them, but we do want to deep copy arrays.
	 *  @param {object} out Object to extend
	 *  @param {object} extender Object from which the properties will be applied to
	 *      out
	 *  @param {boolean} breakRefs If true, then arrays will be sliced to take an
	 *      independent copy with the exception of the `data` or `aaData` parameters
	 *      if they are present. This is so you can pass in a collection to
	 *      DataTables and have that used as your data source without breaking the
	 *      references
	 *  @returns {object} out Reference, just for convenience - out === the return.
	 *  @memberof DataTable#oApi
	 *  @todo This doesn't take account of arrays inside the deep copied objects.
	 */
	function _fnExtend( out, extender, breakRefs )
	{
		var val;
	
		for ( var prop in extender ) {
			if ( extender.hasOwnProperty(prop) ) {
				val = extender[prop];
	
				if ( $.isPlainObject( val ) ) {
					if ( ! $.isPlainObject( out[prop] ) ) {
						out[prop] = {};
					}
					$.extend( true, out[prop], val );
				}
				else if ( breakRefs && prop !== 'data' && prop !== 'aaData' && $.isArray(val) ) {
					out[prop] = val.slice();
				}
				else {
					out[prop] = val;
				}
			}
		}
	
		return out;
	}
	
	
	/**
	 * Bind an event handers to allow a click or return key to activate the callback.
	 * This is good for accessibility since a return on the keyboard will have the
	 * same effect as a click, if the element has focus.
	 *  @param {element} n Element to bind the action to
	 *  @param {object} oData Data object to pass to the triggered function
	 *  @param {function} fn Callback function for when the event is triggered
	 *  @memberof DataTable#oApi
	 */
	function _fnBindAction( n, oData, fn )
	{
		$(n)
			.bind( 'click.DT', oData, function (e) {
					n.blur(); // Remove focus outline for mouse users
					fn(e);
				} )
			.bind( 'keypress.DT', oData, function (e){
					if ( e.which === 13 ) {
						e.preventDefault();
						fn(e);
					}
				} )
			.bind( 'selectstart.DT', function () {
					/* Take the brutal approach to cancelling text selection */
					return false;
				} );
	}
	
	
	/**
	 * Register a callback function. Easily allows a callback function to be added to
	 * an array store of callback functions that can then all be called together.
	 *  @param {object} oSettings dataTables settings object
	 *  @param {string} sStore Name of the array storage for the callbacks in oSettings
	 *  @param {function} fn Function to be called back
	 *  @param {string} sName Identifying name for the callback (i.e. a label)
	 *  @memberof DataTable#oApi
	 */
	function _fnCallbackReg( oSettings, sStore, fn, sName )
	{
		if ( fn )
		{
			oSettings[sStore].push( {
				"fn": fn,
				"sName": sName
			} );
		}
	}
	
	
	/**
	 * Fire callback functions and trigger events. Note that the loop over the
	 * callback array store is done backwards! Further note that you do not want to
	 * fire off triggers in time sensitive applications (for example cell creation)
	 * as its slow.
	 *  @param {object} settings dataTables settings object
	 *  @param {string} callbackArr Name of the array storage for the callbacks in
	 *      oSettings
	 *  @param {string} eventName Name of the jQuery custom event to trigger. If
	 *      null no trigger is fired
	 *  @param {array} args Array of arguments to pass to the callback function /
	 *      trigger
	 *  @memberof DataTable#oApi
	 */
	function _fnCallbackFire( settings, callbackArr, eventName, args )
	{
		var ret = [];
	
		if ( callbackArr ) {
			ret = $.map( settings[callbackArr].slice().reverse(), function (val, i) {
				return val.fn.apply( settings.oInstance, args );
			} );
		}
	
		if ( eventName !== null ) {
			var e = $.Event( eventName+'.dt' );
	
			$(settings.nTable).trigger( e, args );
	
			ret.push( e.result );
		}
	
		return ret;
	}
	
	
	function _fnLengthOverflow ( settings )
	{
		var
			start = settings._iDisplayStart,
			end = settings.fnDisplayEnd(),
			len = settings._iDisplayLength;
	
		/* If we have space to show extra rows (backing up from the end point - then do so */
		if ( start >= end )
		{
			start = end - len;
		}
	
		// Keep the start record on the current page
		start -= (start % len);
	
		if ( len === -1 || start < 0 )
		{
			start = 0;
		}
	
		settings._iDisplayStart = start;
	}
	
	
	function _fnRenderer( settings, type )
	{
		var renderer = settings.renderer;
		var host = DataTable.ext.renderer[type];
	
		if ( $.isPlainObject( renderer ) && renderer[type] ) {
			// Specific renderer for this type. If available use it, otherwise use
			// the default.
			return host[renderer[type]] || host._;
		}
		else if ( typeof renderer === 'string' ) {
			// Common renderer - if there is one available for this type use it,
			// otherwise use the default
			return host[renderer] || host._;
		}
	
		// Use the default
		return host._;
	}
	
	
	/**
	 * Detect the data source being used for the table. Used to simplify the code
	 * a little (ajax) and to make it compress a little smaller.
	 *
	 *  @param {object} settings dataTables settings object
	 *  @returns {string} Data source
	 *  @memberof DataTable#oApi
	 */
	function _fnDataSource ( settings )
	{
		if ( settings.oFeatures.bServerSide ) {
			return 'ssp';
		}
		else if ( settings.ajax || settings.sAjaxSource ) {
			return 'ajax';
		}
		return 'dom';
	}
	

	DataTable = function( options )
	{
		/**
		 * Perform a jQuery selector action on the table's TR elements (from the tbody) and
		 * return the resulting jQuery object.
		 *  @param {string|node|jQuery} sSelector jQuery selector or node collection to act on
		 *  @param {object} [oOpts] Optional parameters for modifying the rows to be included
		 *  @param {string} [oOpts.filter=none] Select TR elements that meet the current filter
		 *    criterion ("applied") or all TR elements (i.e. no filter).
		 *  @param {string} [oOpts.order=current] Order of the TR elements in the processed array.
		 *    Can be either 'current', whereby the current sorting of the table is used, or
		 *    'original' whereby the original order the data was read into the table is used.
		 *  @param {string} [oOpts.page=all] Limit the selection to the currently displayed page
		 *    ("current") or not ("all"). If 'current' is given, then order is assumed to be
		 *    'current' and filter is 'applied', regardless of what they might be given as.
		 *  @returns {object} jQuery object, filtered by the given selector.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Highlight every second row
		 *      oTable.$('tr:odd').css('backgroundColor', 'blue');
		 *    } );
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Filter to rows with 'Webkit' in them, add a background colour and then
		 *      // remove the filter, thus highlighting the 'Webkit' rows only.
		 *      oTable.fnFilter('Webkit');
		 *      oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue');
		 *      oTable.fnFilter('');
		 *    } );
		 */
		this.$ = function ( sSelector, oOpts )
		{
			return this.api(true).$( sSelector, oOpts );
		};
		
		
		/**
		 * Almost identical to $ in operation, but in this case returns the data for the matched
		 * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes
		 * rather than any descendants, so the data can be obtained for the row/cell. If matching
		 * rows are found, the data returned is the original data array/object that was used to
		 * create the row (or a generated array if from a DOM source).
		 *
		 * This method is often useful in-combination with $ where both functions are given the
		 * same parameters and the array indexes will match identically.
		 *  @param {string|node|jQuery} sSelector jQuery selector or node collection to act on
		 *  @param {object} [oOpts] Optional parameters for modifying the rows to be included
		 *  @param {string} [oOpts.filter=none] Select elements that meet the current filter
		 *    criterion ("applied") or all elements (i.e. no filter).
		 *  @param {string} [oOpts.order=current] Order of the data in the processed array.
		 *    Can be either 'current', whereby the current sorting of the table is used, or
		 *    'original' whereby the original order the data was read into the table is used.
		 *  @param {string} [oOpts.page=all] Limit the selection to the currently displayed page
		 *    ("current") or not ("all"). If 'current' is given, then order is assumed to be
		 *    'current' and filter is 'applied', regardless of what they might be given as.
		 *  @returns {array} Data for the matched elements. If any elements, as a result of the
		 *    selector, were not TR, TD or TH elements in the DataTable, they will have a null
		 *    entry in the array.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Get the data from the first row in the table
		 *      var data = oTable._('tr:first');
		 *
		 *      // Do something useful with the data
		 *      alert( "First cell is: "+data[0] );
		 *    } );
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Filter to 'Webkit' and get all data for
		 *      oTable.fnFilter('Webkit');
		 *      var data = oTable._('tr', {"search": "applied"});
		 *
		 *      // Do something with the data
		 *      alert( data.length+" rows matched the search" );
		 *    } );
		 */
		this._ = function ( sSelector, oOpts )
		{
			return this.api(true).rows( sSelector, oOpts ).data();
		};
		
		
		/**
		 * Create a DataTables Api instance, with the currently selected tables for
		 * the Api's context.
		 * @param {boolean} [traditional=false] Set the API instance's context to be
		 *   only the table referred to by the `DataTable.ext.iApiIndex` option, as was
		 *   used in the API presented by DataTables 1.9- (i.e. the traditional mode),
		 *   or if all tables captured in the jQuery object should be used.
		 * @return {DataTables.Api}
		 */
		this.api = function ( traditional )
		{
			return traditional ?
				new _Api(
					_fnSettingsFromNode( this[ _ext.iApiIndex ] )
				) :
				new _Api( this );
		};
		
		
		/**
		 * Add a single new row or multiple rows of data to the table. Please note
		 * that this is suitable for client-side processing only - if you are using
		 * server-side processing (i.e. "bServerSide": true), then to add data, you
		 * must add it to the data source, i.e. the server-side, through an Ajax call.
		 *  @param {array|object} data The data to be added to the table. This can be:
		 *    <ul>
		 *      <li>1D array of data - add a single row with the data provided</li>
		 *      <li>2D array of arrays - add multiple rows in a single call</li>
		 *      <li>object - data object when using <i>mData</i></li>
		 *      <li>array of objects - multiple data objects when using <i>mData</i></li>
		 *    </ul>
		 *  @param {bool} [redraw=true] redraw the table or not
		 *  @returns {array} An array of integers, representing the list of indexes in
		 *    <i>aoData</i> ({@link DataTable.models.oSettings}) that have been added to
		 *    the table.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    // Global var for counter
		 *    var giCount = 2;
		 *
		 *    $(document).ready(function() {
		 *      $('#example').dataTable();
		 *    } );
		 *
		 *    function fnClickAddRow() {
		 *      $('#example').dataTable().fnAddData( [
		 *        giCount+".1",
		 *        giCount+".2",
		 *        giCount+".3",
		 *        giCount+".4" ]
		 *      );
		 *
		 *      giCount++;
		 *    }
		 */
		this.fnAddData = function( data, redraw )
		{
			var api = this.api( true );
		
			/* Check if we want to add multiple rows or not */
			var rows = $.isArray(data) && ( $.isArray(data[0]) || $.isPlainObject(data[0]) ) ?
				api.rows.add( data ) :
				api.row.add( data );
		
			if ( redraw === undefined || redraw ) {
				api.draw();
			}
		
			return rows.flatten().toArray();
		};
		
		
		/**
		 * This function will make DataTables recalculate the column sizes, based on the data
		 * contained in the table and the sizes applied to the columns (in the DOM, CSS or
		 * through the sWidth parameter). This can be useful when the width of the table's
		 * parent element changes (for example a window resize).
		 *  @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable( {
		 *        "sScrollY": "200px",
		 *        "bPaginate": false
		 *      } );
		 *
		 *      $(window).bind('resize', function () {
		 *        oTable.fnAdjustColumnSizing();
		 *      } );
		 *    } );
		 */
		this.fnAdjustColumnSizing = function ( bRedraw )
		{
			var api = this.api( true ).columns.adjust();
			var settings = api.settings()[0];
			var scroll = settings.oScroll;
		
			if ( bRedraw === undefined || bRedraw ) {
				api.draw( false );
			}
			else if ( scroll.sX !== "" || scroll.sY !== "" ) {
				/* If not redrawing, but scrolling, we want to apply the new column sizes anyway */
				_fnScrollDraw( settings );
			}
		};
		
		
		/**
		 * Quickly and simply clear a table
		 *  @param {bool} [bRedraw=true] redraw the table or not
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...)
		 *      oTable.fnClearTable();
		 *    } );
		 */
		this.fnClearTable = function( bRedraw )
		{
			var api = this.api( true ).clear();
		
			if ( bRedraw === undefined || bRedraw ) {
				api.draw();
			}
		};
		
		
		/**
		 * The exact opposite of 'opening' a row, this function will close any rows which
		 * are currently 'open'.
		 *  @param {node} nTr the table row to 'close'
		 *  @returns {int} 0 on success, or 1 if failed (can't find the row)
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable;
		 *
		 *      // 'open' an information row when a row is clicked on
		 *      $('#example tbody tr').click( function () {
		 *        if ( oTable.fnIsOpen(this) ) {
		 *          oTable.fnClose( this );
		 *        } else {
		 *          oTable.fnOpen( this, "Temporary row opened", "info_row" );
		 *        }
		 *      } );
		 *
		 *      oTable = $('#example').dataTable();
		 *    } );
		 */
		this.fnClose = function( nTr )
		{
			this.api( true ).row( nTr ).child.hide();
		};
		
		
		/**
		 * Remove a row for the table
		 *  @param {mixed} target The index of the row from aoData to be deleted, or
		 *    the TR element you want to delete
		 *  @param {function|null} [callBack] Callback function
		 *  @param {bool} [redraw=true] Redraw the table or not
		 *  @returns {array} The row that was deleted
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Immediately remove the first row
		 *      oTable.fnDeleteRow( 0 );
		 *    } );
		 */
		this.fnDeleteRow = function( target, callback, redraw )
		{
			var api = this.api( true );
			var rows = api.rows( target );
			var settings = rows.settings()[0];
			var data = settings.aoData[ rows[0][0] ];
		
			rows.remove();
		
			if ( callback ) {
				callback.call( this, settings, data );
			}
		
			if ( redraw === undefined || redraw ) {
				api.draw();
			}
		
			return data;
		};
		
		
		/**
		 * Restore the table to it's original state in the DOM by removing all of DataTables
		 * enhancements, alterations to the DOM structure of the table and event listeners.
		 *  @param {boolean} [remove=false] Completely remove the table from the DOM
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      // This example is fairly pointless in reality, but shows how fnDestroy can be used
		 *      var oTable = $('#example').dataTable();
		 *      oTable.fnDestroy();
		 *    } );
		 */
		this.fnDestroy = function ( remove )
		{
			this.api( true ).destroy( remove );
		};
		
		
		/**
		 * Redraw the table
		 *  @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Re-draw the table - you wouldn't want to do it here, but it's an example :-)
		 *      oTable.fnDraw();
		 *    } );
		 */
		this.fnDraw = function( complete )
		{
			// Note that this isn't an exact match to the old call to _fnDraw - it takes
			// into account the new data, but can hold position.
			this.api( true ).draw( complete );
		};
		
		
		/**
		 * Filter the input based on data
		 *  @param {string} sInput String to filter the table on
		 *  @param {int|null} [iColumn] Column to limit filtering to
		 *  @param {bool} [bRegex=false] Treat as regular expression or not
		 *  @param {bool} [bSmart=true] Perform smart filtering or not
		 *  @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es)
		 *  @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false)
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Sometime later - filter...
		 *      oTable.fnFilter( 'test string' );
		 *    } );
		 */
		this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive )
		{
			var api = this.api( true );
		
			if ( iColumn === null || iColumn === undefined ) {
				api.search( sInput, bRegex, bSmart, bCaseInsensitive );
			}
			else {
				api.column( iColumn ).search( sInput, bRegex, bSmart, bCaseInsensitive );
			}
		
			api.draw();
		};
		
		
		/**
		 * Get the data for the whole table, an individual row or an individual cell based on the
		 * provided parameters.
		 *  @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as
		 *    a TR node then the data source for the whole row will be returned. If given as a
		 *    TD/TH cell node then iCol will be automatically calculated and the data for the
		 *    cell returned. If given as an integer, then this is treated as the aoData internal
		 *    data index for the row (see fnGetPosition) and the data for that row used.
		 *  @param {int} [col] Optional column index that you want the data of.
		 *  @returns {array|object|string} If mRow is undefined, then the data for all rows is
		 *    returned. If mRow is defined, just data for that row, and is iCol is
		 *    defined, only data for the designated cell is returned.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    // Row data
		 *    $(document).ready(function() {
		 *      oTable = $('#example').dataTable();
		 *
		 *      oTable.$('tr').click( function () {
		 *        var data = oTable.fnGetData( this );
		 *        // ... do something with the array / object of data for the row
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Individual cell data
		 *    $(document).ready(function() {
		 *      oTable = $('#example').dataTable();
		 *
		 *      oTable.$('td').click( function () {
		 *        var sData = oTable.fnGetData( this );
		 *        alert( 'The cell clicked on had the value of '+sData );
		 *      } );
		 *    } );
		 */
		this.fnGetData = function( src, col )
		{
			var api = this.api( true );
		
			if ( src !== undefined ) {
				var type = src.nodeName ? src.nodeName.toLowerCase() : '';
		
				return col !== undefined || type == 'td' || type == 'th' ?
					api.cell( src, col ).data() :
					api.row( src ).data() || null;
			}
		
			return api.data().toArray();
		};
		
		
		/**
		 * Get an array of the TR nodes that are used in the table's body. Note that you will
		 * typically want to use the '$' API method in preference to this as it is more
		 * flexible.
		 *  @param {int} [iRow] Optional row index for the TR element you want
		 *  @returns {array|node} If iRow is undefined, returns an array of all TR elements
		 *    in the table's body, or iRow is defined, just the TR element requested.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Get the nodes from the table
		 *      var nNodes = oTable.fnGetNodes( );
		 *    } );
		 */
		this.fnGetNodes = function( iRow )
		{
			var api = this.api( true );
		
			return iRow !== undefined ?
				api.row( iRow ).node() :
				api.rows().nodes().flatten().toArray();
		};
		
		
		/**
		 * Get the array indexes of a particular cell from it's DOM element
		 * and column index including hidden columns
		 *  @param {node} node this can either be a TR, TD or TH in the table's body
		 *  @returns {int} If nNode is given as a TR, then a single index is returned, or
		 *    if given as a cell, an array of [row index, column index (visible),
		 *    column index (all)] is given.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      $('#example tbody td').click( function () {
		 *        // Get the position of the current data from the node
		 *        var aPos = oTable.fnGetPosition( this );
		 *
		 *        // Get the data array for this row
		 *        var aData = oTable.fnGetData( aPos[0] );
		 *
		 *        // Update the data array and return the value
		 *        aData[ aPos[1] ] = 'clicked';
		 *        this.innerHTML = 'clicked';
		 *      } );
		 *
		 *      // Init DataTables
		 *      oTable = $('#example').dataTable();
		 *    } );
		 */
		this.fnGetPosition = function( node )
		{
			var api = this.api( true );
			var nodeName = node.nodeName.toUpperCase();
		
			if ( nodeName == 'TR' ) {
				return api.row( node ).index();
			}
			else if ( nodeName == 'TD' || nodeName == 'TH' ) {
				var cell = api.cell( node ).index();
		
				return [
					cell.row,
					cell.columnVisible,
					cell.column
				];
			}
			return null;
		};
		
		
		/**
		 * Check to see if a row is 'open' or not.
		 *  @param {node} nTr the table row to check
		 *  @returns {boolean} true if the row is currently open, false otherwise
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable;
		 *
		 *      // 'open' an information row when a row is clicked on
		 *      $('#example tbody tr').click( function () {
		 *        if ( oTable.fnIsOpen(this) ) {
		 *          oTable.fnClose( this );
		 *        } else {
		 *          oTable.fnOpen( this, "Temporary row opened", "info_row" );
		 *        }
		 *      } );
		 *
		 *      oTable = $('#example').dataTable();
		 *    } );
		 */
		this.fnIsOpen = function( nTr )
		{
			return this.api( true ).row( nTr ).child.isShown();
		};
		
		
		/**
		 * This function will place a new row directly after a row which is currently
		 * on display on the page, with the HTML contents that is passed into the
		 * function. This can be used, for example, to ask for confirmation that a
		 * particular record should be deleted.
		 *  @param {node} nTr The table row to 'open'
		 *  @param {string|node|jQuery} mHtml The HTML to put into the row
		 *  @param {string} sClass Class to give the new TD cell
		 *  @returns {node} The row opened. Note that if the table row passed in as the
		 *    first parameter, is not found in the table, this method will silently
		 *    return.
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable;
		 *
		 *      // 'open' an information row when a row is clicked on
		 *      $('#example tbody tr').click( function () {
		 *        if ( oTable.fnIsOpen(this) ) {
		 *          oTable.fnClose( this );
		 *        } else {
		 *          oTable.fnOpen( this, "Temporary row opened", "info_row" );
		 *        }
		 *      } );
		 *
		 *      oTable = $('#example').dataTable();
		 *    } );
		 */
		this.fnOpen = function( nTr, mHtml, sClass )
		{
			return this.api( true )
				.row( nTr )
				.child( mHtml, sClass )
				.show()
				.child()[0];
		};
		
		
		/**
		 * Change the pagination - provides the internal logic for pagination in a simple API
		 * function. With this function you can have a DataTables table go to the next,
		 * previous, first or last pages.
		 *  @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last"
		 *    or page number to jump to (integer), note that page 0 is the first page.
		 *  @param {bool} [bRedraw=true] Redraw the table or not
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      oTable.fnPageChange( 'next' );
		 *    } );
		 */
		this.fnPageChange = function ( mAction, bRedraw )
		{
			var api = this.api( true ).page( mAction );
		
			if ( bRedraw === undefined || bRedraw ) {
				api.draw(false);
			}
		};
		
		
		/**
		 * Show a particular column
		 *  @param {int} iCol The column whose display should be changed
		 *  @param {bool} bShow Show (true) or hide (false) the column
		 *  @param {bool} [bRedraw=true] Redraw the table or not
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Hide the second column after initialisation
		 *      oTable.fnSetColumnVis( 1, false );
		 *    } );
		 */
		this.fnSetColumnVis = function ( iCol, bShow, bRedraw )
		{
			var api = this.api( true ).column( iCol ).visible( bShow );
		
			if ( bRedraw === undefined || bRedraw ) {
				api.columns.adjust().draw();
			}
		};
		
		
		/**
		 * Get the settings for a particular table for external manipulation
		 *  @returns {object} DataTables settings object. See
		 *    {@link DataTable.models.oSettings}
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      var oSettings = oTable.fnSettings();
		 *
		 *      // Show an example parameter from the settings
		 *      alert( oSettings._iDisplayStart );
		 *    } );
		 */
		this.fnSettings = function()
		{
			return _fnSettingsFromNode( this[_ext.iApiIndex] );
		};
		
		
		/**
		 * Sort the table by a particular column
		 *  @param {int} iCol the data index to sort on. Note that this will not match the
		 *    'display index' if you have hidden data entries
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Sort immediately with columns 0 and 1
		 *      oTable.fnSort( [ [0,'asc'], [1,'asc'] ] );
		 *    } );
		 */
		this.fnSort = function( aaSort )
		{
			this.api( true ).order( aaSort ).draw();
		};
		
		
		/**
		 * Attach a sort listener to an element for a given column
		 *  @param {node} nNode the element to attach the sort listener to
		 *  @param {int} iColumn the column that a click on this node will sort on
		 *  @param {function} [fnCallback] callback function when sort is run
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *
		 *      // Sort on column 1, when 'sorter' is clicked on
		 *      oTable.fnSortListener( document.getElementById('sorter'), 1 );
		 *    } );
		 */
		this.fnSortListener = function( nNode, iColumn, fnCallback )
		{
			this.api( true ).order.listener( nNode, iColumn, fnCallback );
		};
		
		
		/**
		 * Update a table cell or row - this method will accept either a single value to
		 * update the cell with, an array of values with one element for each column or
		 * an object in the same format as the original data source. The function is
		 * self-referencing in order to make the multi column updates easier.
		 *  @param {object|array|string} mData Data to update the cell/row with
		 *  @param {node|int} mRow TR element you want to update or the aoData index
		 *  @param {int} [iColumn] The column to update, give as null or undefined to
		 *    update a whole row.
		 *  @param {bool} [bRedraw=true] Redraw the table or not
		 *  @param {bool} [bAction=true] Perform pre-draw actions or not
		 *  @returns {int} 0 on success, 1 on error
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell
		 *      oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row
		 *    } );
		 */
		this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction )
		{
			var api = this.api( true );
		
			if ( iColumn === undefined || iColumn === null ) {
				api.row( mRow ).data( mData );
			}
			else {
				api.cell( mRow, iColumn ).data( mData );
			}
		
			if ( bAction === undefined || bAction ) {
				api.columns.adjust();
			}
		
			if ( bRedraw === undefined || bRedraw ) {
				api.draw();
			}
			return 0;
		};
		
		
		/**
		 * Provide a common method for plug-ins to check the version of DataTables being used, in order
		 * to ensure compatibility.
		 *  @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the
		 *    formats "X" and "X.Y" are also acceptable.
		 *  @returns {boolean} true if this version of DataTables is greater or equal to the required
		 *    version, or false if this version of DataTales is not suitable
		 *  @method
		 *  @dtopt API
		 *  @deprecated Since v1.10
		 *
		 *  @example
		 *    $(document).ready(function() {
		 *      var oTable = $('#example').dataTable();
		 *      alert( oTable.fnVersionCheck( '1.9.0' ) );
		 *    } );
		 */
		this.fnVersionCheck = _ext.fnVersionCheck;
		

		var _that = this;
		var emptyInit = options === undefined;
		var len = this.length;

		if ( emptyInit ) {
			options = {};
		}

		this.oApi = this.internal = _ext.internal;

		// Extend with old style plug-in API methods
		for ( var fn in DataTable.ext.internal ) {
			if ( fn ) {
				this[fn] = _fnExternApiFunc(fn);
			}
		}

		this.each(function() {
			// For each initialisation we want to give it a clean initialisation
			// object that can be bashed around
			var o = {};
			var oInit = len > 1 ? // optimisation for single table case
				_fnExtend( o, options, true ) :
				options;

			/*global oInit,_that,emptyInit*/
			var i=0, iLen, j, jLen, k, kLen;
			var sId = this.getAttribute( 'id' );
			var bInitHandedOff = false;
			var defaults = DataTable.defaults;
			var $this = $(this);
			
			
			/* Sanity check */
			if ( this.nodeName.toLowerCase() != 'table' )
			{
				_fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 );
				return;
			}
			
			/* Backwards compatibility for the defaults */
			_fnCompatOpts( defaults );
			_fnCompatCols( defaults.column );
			
			/* Convert the camel-case defaults to Hungarian */
			_fnCamelToHungarian( defaults, defaults, true );
			_fnCamelToHungarian( defaults.column, defaults.column, true );
			
			/* Setting up the initialisation object */
			_fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ) );
			
			
			
			/* Check to see if we are re-initialising a table */
			var allSettings = DataTable.settings;
			for ( i=0, iLen=allSettings.length ; i<iLen ; i++ )
			{
				var s = allSettings[i];
			
				/* Base check on table node */
				if ( s.nTable == this || s.nTHead.parentNode == this || (s.nTFoot && s.nTFoot.parentNode == this) )
				{
					var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve;
					var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy;
			
					if ( emptyInit || bRetrieve )
					{
						return s.oInstance;
					}
					else if ( bDestroy )
					{
						s.oInstance.fnDestroy();
						break;
					}
					else
					{
						_fnLog( s, 0, 'Cannot reinitialise DataTable', 3 );
						return;
					}
				}
			
				/* If the element we are initialising has the same ID as a table which was previously
				 * initialised, but the table nodes don't match (from before) then we destroy the old
				 * instance by simply deleting it. This is under the assumption that the table has been
				 * destroyed by other methods. Anyone using non-id selectors will need to do this manually
				 */
				if ( s.sTableId == this.id )
				{
					allSettings.splice( i, 1 );
					break;
				}
			}
			
			/* Ensure the table has an ID - required for accessibility */
			if ( sId === null || sId === "" )
			{
				sId = "DataTables_Table_"+(DataTable.ext._unique++);
				this.id = sId;
			}
			
			/* Create the settings object for this table and set some of the default parameters */
			var oSettings = $.extend( true, {}, DataTable.models.oSettings, {
				"sDestroyWidth": $this[0].style.width,
				"sInstance":     sId,
				"sTableId":      sId
			} );
			oSettings.nTable = this;
			oSettings.oApi   = _that.internal;
			oSettings.oInit  = oInit;
			
			allSettings.push( oSettings );
			
			// Need to add the instance after the instance after the settings object has been added
			// to the settings array, so we can self reference the table instance if more than one
			oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable();
			
			// Backwards compatibility, before we apply all the defaults
			_fnCompatOpts( oInit );
			
			if ( oInit.oLanguage )
			{
				_fnLanguageCompat( oInit.oLanguage );
			}
			
			// If the length menu is given, but the init display length is not, use the length menu
			if ( oInit.aLengthMenu && ! oInit.iDisplayLength )
			{
				oInit.iDisplayLength = $.isArray( oInit.aLengthMenu[0] ) ?
					oInit.aLengthMenu[0][0] : oInit.aLengthMenu[0];
			}
			
			// Apply the defaults and init options to make a single init object will all
			// options defined from defaults and instance options.
			oInit = _fnExtend( $.extend( true, {}, defaults ), oInit );
			
			
			// Map the initialisation options onto the settings object
			_fnMap( oSettings.oFeatures, oInit, [
				"bPaginate",
				"bLengthChange",
				"bFilter",
				"bSort",
				"bSortMulti",
				"bInfo",
				"bProcessing",
				"bAutoWidth",
				"bSortClasses",
				"bServerSide",
				"bDeferRender"
			] );
			_fnMap( oSettings, oInit, [
				"asStripeClasses",
				"ajax",
				"fnServerData",
				"fnFormatNumber",
				"sServerMethod",
				"aaSorting",
				"aaSortingFixed",
				"aLengthMenu",
				"sPaginationType",
				"sAjaxSource",
				"sAjaxDataProp",
				"iStateDuration",
				"sDom",
				"bSortCellsTop",
				"iTabIndex",
				"fnStateLoadCallback",
				"fnStateSaveCallback",
				"renderer",
				"searchDelay",
				"rowId",
				[ "iCookieDuration", "iStateDuration" ], // backwards compat
				[ "oSearch", "oPreviousSearch" ],
				[ "aoSearchCols", "aoPreSearchCols" ],
				[ "iDisplayLength", "_iDisplayLength" ],
				[ "bJQueryUI", "bJUI" ]
			] );
			_fnMap( oSettings.oScroll, oInit, [
				[ "sScrollX", "sX" ],
				[ "sScrollXInner", "sXInner" ],
				[ "sScrollY", "sY" ],
				[ "bScrollCollapse", "bCollapse" ]
			] );
			_fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" );
			
			/* Callback functions which are array driven */
			_fnCallbackReg( oSettings, 'aoDrawCallback',       oInit.fnDrawCallback,      'user' );
			_fnCallbackReg( oSettings, 'aoServerParams',       oInit.fnServerParams,      'user' );
			_fnCallbackReg( oSettings, 'aoStateSaveParams',    oInit.fnStateSaveParams,   'user' );
			_fnCallbackReg( oSettings, 'aoStateLoadParams',    oInit.fnStateLoadParams,   'user' );
			_fnCallbackReg( oSettings, 'aoStateLoaded',        oInit.fnStateLoaded,       'user' );
			_fnCallbackReg( oSettings, 'aoRowCallback',        oInit.fnRowCallback,       'user' );
			_fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow,        'user' );
			_fnCallbackReg( oSettings, 'aoHeaderCallback',     oInit.fnHeaderCallback,    'user' );
			_fnCallbackReg( oSettings, 'aoFooterCallback',     oInit.fnFooterCallback,    'user' );
			_fnCallbackReg( oSettings, 'aoInitComplete',       oInit.fnInitComplete,      'user' );
			_fnCallbackReg( oSettings, 'aoPreDrawCallback',    oInit.fnPreDrawCallback,   'user' );
			
			oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId );
			
			/* Browser support detection */
			_fnBrowserDetect( oSettings );
			
			var oClasses = oSettings.oClasses;
			
			// @todo Remove in 1.11
			if ( oInit.bJQueryUI )
			{
				/* Use the JUI classes object for display. You could clone the oStdClasses object if
				 * you want to have multiple tables with multiple independent classes
				 */
				$.extend( oClasses, DataTable.ext.oJUIClasses, oInit.oClasses );
			
				if ( oInit.sDom === defaults.sDom && defaults.sDom === "lfrtip" )
				{
					/* Set the DOM to use a layout suitable for jQuery UI's theming */
					oSettings.sDom = '<"H"lfr>t<"F"ip>';
				}
			
				if ( ! oSettings.renderer ) {
					oSettings.renderer = 'jqueryui';
				}
				else if ( $.isPlainObject( oSettings.renderer ) && ! oSettings.renderer.header ) {
					oSettings.renderer.header = 'jqueryui';
				}
			}
			else
			{
				$.extend( oClasses, DataTable.ext.classes, oInit.oClasses );
			}
			$this.addClass( oClasses.sTable );
			
			
			if ( oSettings.iInitDisplayStart === undefined )
			{
				/* Display start point, taking into account the save saving */
				oSettings.iInitDisplayStart = oInit.iDisplayStart;
				oSettings._iDisplayStart = oInit.iDisplayStart;
			}
			
			if ( oInit.iDeferLoading !== null )
			{
				oSettings.bDeferLoading = true;
				var tmp = $.isArray( oInit.iDeferLoading );
				oSettings._iRecordsDisplay = tmp ? oInit.iDeferLoading[0] : oInit.iDeferLoading;
				oSettings._iRecordsTotal = tmp ? oInit.iDeferLoading[1] : oInit.iDeferLoading;
			}
			
			/* Language definitions */
			var oLanguage = oSettings.oLanguage;
			$.extend( true, oLanguage, oInit.oLanguage );
			
			if ( oLanguage.sUrl !== "" )
			{
				/* Get the language definitions from a file - because this Ajax call makes the language
				 * get async to the remainder of this function we use bInitHandedOff to indicate that
				 * _fnInitialise will be fired by the returned Ajax handler, rather than the constructor
				 */
				$.ajax( {
					dataType: 'json',
					url: oLanguage.sUrl,
					success: function ( json ) {
						_fnLanguageCompat( json );
						_fnCamelToHungarian( defaults.oLanguage, json );
						$.extend( true, oLanguage, json );
						_fnInitialise( oSettings );
					},
					error: function () {
						// Error occurred loading language file, continue on as best we can
						_fnInitialise( oSettings );
					}
				} );
				bInitHandedOff = true;
			}
			
			/*
			 * Stripes
			 */
			if ( oInit.asStripeClasses === null )
			{
				oSettings.asStripeClasses =[
					oClasses.sStripeOdd,
					oClasses.sStripeEven
				];
			}
			
			/* Remove row stripe classes if they are already on the table row */
			var stripeClasses = oSettings.asStripeClasses;
			var rowOne = $this.children('tbody').find('tr').eq(0);
			if ( $.inArray( true, $.map( stripeClasses, function(el, i) {
				return rowOne.hasClass(el);
			} ) ) !== -1 ) {
				$('tbody tr', this).removeClass( stripeClasses.join(' ') );
				oSettings.asDestroyStripes = stripeClasses.slice();
			}
			
			/*
			 * Columns
			 * See if we should load columns automatically or use defined ones
			 */
			var anThs = [];
			var aoColumnsInit;
			var nThead = this.getElementsByTagName('thead');
			if ( nThead.length !== 0 )
			{
				_fnDetectHeader( oSettings.aoHeader, nThead[0] );
				anThs = _fnGetUniqueThs( oSettings );
			}
			
			/* If not given a column array, generate one with nulls */
			if ( oInit.aoColumns === null )
			{
				aoColumnsInit = [];
				for ( i=0, iLen=anThs.length ; i<iLen ; i++ )
				{
					aoColumnsInit.push( null );
				}
			}
			else
			{
				aoColumnsInit = oInit.aoColumns;
			}
			
			/* Add the columns */
			for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ )
			{
				_fnAddColumn( oSettings, anThs ? anThs[i] : null );
			}
			
			/* Apply the column definitions */
			_fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, aoColumnsInit, function (iCol, oDef) {
				_fnColumnOptions( oSettings, iCol, oDef );
			} );
			
			/* HTML5 attribute detection - build an mData object automatically if the
			 * attributes are found
			 */
			if ( rowOne.length ) {
				var a = function ( cell, name ) {
					return cell.getAttribute( 'data-'+name ) !== null ? name : null;
				};
			
				$( rowOne[0] ).children('th, td').each( function (i, cell) {
					var col = oSettings.aoColumns[i];
			
					if ( col.mData === i ) {
						var sort = a( cell, 'sort' ) || a( cell, 'order' );
						var filter = a( cell, 'filter' ) || a( cell, 'search' );
			
						if ( sort !== null || filter !== null ) {
							col.mData = {
								_:      i+'.display',
								sort:   sort !== null   ? i+'.@data-'+sort   : undefined,
								type:   sort !== null   ? i+'.@data-'+sort   : undefined,
								filter: filter !== null ? i+'.@data-'+filter : undefined
							};
			
							_fnColumnOptions( oSettings, i );
						}
					}
				} );
			}
			
			var features = oSettings.oFeatures;
			
			/* Must be done after everything which can be overridden by the state saving! */
			if ( oInit.bStateSave )
			{
				features.bStateSave = true;
				_fnLoadState( oSettings, oInit );
				_fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState, 'state_save' );
			}
			
			
			/*
			 * Sorting
			 * @todo For modularisation (1.11) this needs to do into a sort start up handler
			 */
			
			// If aaSorting is not defined, then we use the first indicator in asSorting
			// in case that has been altered, so the default sort reflects that option
			if ( oInit.aaSorting === undefined )
			{
				var sorting = oSettings.aaSorting;
				for ( i=0, iLen=sorting.length ; i<iLen ; i++ )
				{
					sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0];
				}
			}
			
			/* Do a first pass on the sorting classes (allows any size changes to be taken into
			 * account, and also will apply sorting disabled classes if disabled
			 */
			_fnSortingClasses( oSettings );
			
			if ( features.bSort )
			{
				_fnCallbackReg( oSettings, 'aoDrawCallback', function () {
					if ( oSettings.bSorted ) {
						var aSort = _fnSortFlatten( oSettings );
						var sortedColumns = {};
			
						$.each( aSort, function (i, val) {
							sortedColumns[ val.src ] = val.dir;
						} );
			
						_fnCallbackFire( oSettings, null, 'order', [oSettings, aSort, sortedColumns] );
						_fnSortAria( oSettings );
					}
				} );
			}
			
			_fnCallbackReg( oSettings, 'aoDrawCallback', function () {
				if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) {
					_fnSortingClasses( oSettings );
				}
			}, 'sc' );
			
			
			/*
			 * Final init
			 * Cache the header, body and footer as required, creating them if needed
			 */
			
			// Work around for Webkit bug 83867 - store the caption-side before removing from doc
			var captions = $this.children('caption').each( function () {
				this._captionSide = $this.css('caption-side');
			} );
			
			var thead = $this.children('thead');
			if ( thead.length === 0 )
			{
				thead = $('<thead/>').appendTo(this);
			}
			oSettings.nTHead = thead[0];
			
			var tbody = $this.children('tbody');
			if ( tbody.length === 0 )
			{
				tbody = $('<tbody/>').appendTo(this);
			}
			oSettings.nTBody = tbody[0];
			
			var tfoot = $this.children('tfoot');
			if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") )
			{
				// If we are a scrolling table, and no footer has been given, then we need to create
				// a tfoot element for the caption element to be appended to
				tfoot = $('<tfoot/>').appendTo(this);
			}
			
			if ( tfoot.length === 0 || tfoot.children().length === 0 ) {
				$this.addClass( oClasses.sNoFooter );
			}
			else if ( tfoot.length > 0 ) {
				oSettings.nTFoot = tfoot[0];
				_fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot );
			}
			
			/* Check if there is data passing into the constructor */
			if ( oInit.aaData )
			{
				for ( i=0 ; i<oInit.aaData.length ; i++ )
				{
					_fnAddData( oSettings, oInit.aaData[ i ] );
				}
			}
			else if ( oSettings.bDeferLoading || _fnDataSource( oSettings ) == 'dom' )
			{
				/* Grab the data from the page - only do this when deferred loading or no Ajax
				 * source since there is no point in reading the DOM data if we are then going
				 * to replace it with Ajax data
				 */
				_fnAddTr( oSettings, $(oSettings.nTBody).children('tr') );
			}
			
			/* Copy the data index array */
			oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
			
			/* Initialisation complete - table can be drawn */
			oSettings.bInitialised = true;
			
			/* Check if we need to initialise the table (it might not have been handed off to the
			 * language processor)
			 */
			if ( bInitHandedOff === false )
			{
				_fnInitialise( oSettings );
			}
		} );
		_that = null;
		return this;
	};

	
	
	/**
	 * Computed structure of the DataTables API, defined by the options passed to
	 * `DataTable.Api.register()` when building the API.
	 *
	 * The structure is built in order to speed creation and extension of the Api
	 * objects since the extensions are effectively pre-parsed.
	 *
	 * The array is an array of objects with the following structure, where this
	 * base array represents the Api prototype base:
	 *
	 *     [
	 *       {
	 *         name:      'data'                -- string   - Property name
	 *         val:       function () {},       -- function - Api method (or undefined if just an object
	 *         methodExt: [ ... ],              -- array    - Array of Api object definitions to extend the method result
	 *         propExt:   [ ... ]               -- array    - Array of Api object definitions to extend the property
	 *       },
	 *       {
	 *         name:     'row'
	 *         val:       {},
	 *         methodExt: [ ... ],
	 *         propExt:   [
	 *           {
	 *             name:      'data'
	 *             val:       function () {},
	 *             methodExt: [ ... ],
	 *             propExt:   [ ... ]
	 *           },
	 *           ...
	 *         ]
	 *       }
	 *     ]
	 *
	 * @type {Array}
	 * @ignore
	 */
	var __apiStruct = [];
	
	
	/**
	 * `Array.prototype` reference.
	 *
	 * @type object
	 * @ignore
	 */
	var __arrayProto = Array.prototype;
	
	
	/**
	 * Abstraction for `context` parameter of the `Api` constructor to allow it to
	 * take several different forms for ease of use.
	 *
	 * Each of the input parameter types will be converted to a DataTables settings
	 * object where possible.
	 *
	 * @param  {string|node|jQuery|object} mixed DataTable identifier. Can be one
	 *   of:
	 *
	 *   * `string` - jQuery selector. Any DataTables' matching the given selector
	 *     with be found and used.
	 *   * `node` - `TABLE` node which has already been formed into a DataTable.
	 *   * `jQuery` - A jQuery object of `TABLE` nodes.
	 *   * `object` - DataTables settings object
	 *   * `DataTables.Api` - API instance
	 * @return {array|null} Matching DataTables settings objects. `null` or
	 *   `undefined` is returned if no matching DataTable is found.
	 * @ignore
	 */
	var _toSettings = function ( mixed )
	{
		var idx, jq;
		var settings = DataTable.settings;
		var tables = $.map( settings, function (el, i) {
			return el.nTable;
		} );
	
		if ( ! mixed ) {
			return [];
		}
		else if ( mixed.nTable && mixed.oApi ) {
			// DataTables settings object
			return [ mixed ];
		}
		else if ( mixed.nodeName && mixed.nodeName.toLowerCase() === 'table' ) {
			// Table node
			idx = $.inArray( mixed, tables );
			return idx !== -1 ? [ settings[idx] ] : null;
		}
		else if ( mixed && typeof mixed.settings === 'function' ) {
			return mixed.settings().toArray();
		}
		else if ( typeof mixed === 'string' ) {
			// jQuery selector
			jq = $(mixed);
		}
		else if ( mixed instanceof $ ) {
			// jQuery object (also DataTables instance)
			jq = mixed;
		}
	
		if ( jq ) {
			return jq.map( function(i) {
				idx = $.inArray( this, tables );
				return idx !== -1 ? settings[idx] : null;
			} ).toArray();
		}
	};
	
	
	/**
	 * DataTables API class - used to control and interface with  one or more
	 * DataTables enhanced tables.
	 *
	 * The API class is heavily based on jQuery, presenting a chainable interface
	 * that you can use to interact with tables. Each instance of the API class has
	 * a "context" - i.e. the tables that it will operate on. This could be a single
	 * table, all tables on a page or a sub-set thereof.
	 *
	 * Additionally the API is designed to allow you to easily work with the data in
	 * the tables, retrieving and manipulating it as required. This is done by
	 * presenting the API class as an array like interface. The contents of the
	 * array depend upon the actions requested by each method (for example
	 * `rows().nodes()` will return an array of nodes, while `rows().data()` will
	 * return an array of objects or arrays depending upon your table's
	 * configuration). The API object has a number of array like methods (`push`,
	 * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`,
	 * `unique` etc) to assist your working with the data held in a table.
	 *
	 * Most methods (those which return an Api instance) are chainable, which means
	 * the return from a method call also has all of the methods available that the
	 * top level object had. For example, these two calls are equivalent:
	 *
	 *     // Not chained
	 *     api.row.add( {...} );
	 *     api.draw();
	 *
	 *     // Chained
	 *     api.row.add( {...} ).draw();
	 *
	 * @class DataTable.Api
	 * @param {array|object|string|jQuery} context DataTable identifier. This is
	 *   used to define which DataTables enhanced tables this API will operate on.
	 *   Can be one of:
	 *
	 *   * `string` - jQuery selector. Any DataTables' matching the given selector
	 *     with be found and used.
	 *   * `node` - `TABLE` node which has already been formed into a DataTable.
	 *   * `jQuery` - A jQuery object of `TABLE` nodes.
	 *   * `object` - DataTables settings object
	 * @param {array} [data] Data to initialise the Api instance with.
	 *
	 * @example
	 *   // Direct initialisation during DataTables construction
	 *   var api = $('#example').DataTable();
	 *
	 * @example
	 *   // Initialisation using a DataTables jQuery object
	 *   var api = $('#example').dataTable().api();
	 *
	 * @example
	 *   // Initialisation as a constructor
	 *   var api = new $.fn.DataTable.Api( 'table.dataTable' );
	 */
	_Api = function ( context, data )
	{
		if ( ! (this instanceof _Api) ) {
			return new _Api( context, data );
		}
	
		var settings = [];
		var ctxSettings = function ( o ) {
			var a = _toSettings( o );
			if ( a ) {
				settings = settings.concat( a );
			}
		};
	
		if ( $.isArray( context ) ) {
			for ( var i=0, ien=context.length ; i<ien ; i++ ) {
				ctxSettings( context[i] );
			}
		}
		else {
			ctxSettings( context );
		}
	
		// Remove duplicates
		this.context = _unique( settings );
	
		// Initial data
		if ( data ) {
			$.merge( this, data );
		}
	
		// selector
		this.selector = {
			rows: null,
			cols: null,
			opts: null
		};
	
		_Api.extend( this, this, __apiStruct );
	};
	
	DataTable.Api = _Api;
	
	// Don't destroy the existing prototype, just extend it. Required for jQuery 2's
	// isPlainObject.
	$.extend( _Api.prototype, {
		any: function ()
		{
			return this.count() !== 0;
		},
	
	
		concat:  __arrayProto.concat,
	
	
		context: [], // array of table settings objects
	
	
		count: function ()
		{
			return this.flatten().length;
		},
	
	
		each: function ( fn )
		{
			for ( var i=0, ien=this.length ; i<ien; i++ ) {
				fn.call( this, this[i], i, this );
			}
	
			return this;
		},
	
	
		eq: function ( idx )
		{
			var ctx = this.context;
	
			return ctx.length > idx ?
				new _Api( ctx[idx], this[idx] ) :
				null;
		},
	
	
		filter: function ( fn )
		{
			var a = [];
	
			if ( __arrayProto.filter ) {
				a = __arrayProto.filter.call( this, fn, this );
			}
			else {
				// Compatibility for browsers without EMCA-252-5 (JS 1.6)
				for ( var i=0, ien=this.length ; i<ien ; i++ ) {
					if ( fn.call( this, this[i], i, this ) ) {
						a.push( this[i] );
					}
				}
			}
	
			return new _Api( this.context, a );
		},
	
	
		flatten: function ()
		{
			var a = [];
			return new _Api( this.context, a.concat.apply( a, this.toArray() ) );
		},
	
	
		join:    __arrayProto.join,
	
	
		indexOf: __arrayProto.indexOf || function (obj, start)
		{
			for ( var i=(start || 0), ien=this.length ; i<ien ; i++ ) {
				if ( this[i] === obj ) {
					return i;
				}
			}
			return -1;
		},
	
		iterator: function ( flatten, type, fn, alwaysNew ) {
			var
				a = [], ret,
				i, ien, j, jen,
				context = this.context,
				rows, items, item,
				selector = this.selector;
	
			// Argument shifting
			if ( typeof flatten === 'string' ) {
				alwaysNew = fn;
				fn = type;
				type = flatten;
				flatten = false;
			}
	
			for ( i=0, ien=context.length ; i<ien ; i++ ) {
				var apiInst = new _Api( context[i] );
	
				if ( type === 'table' ) {
					ret = fn.call( apiInst, context[i], i );
	
					if ( ret !== undefined ) {
						a.push( ret );
					}
				}
				else if ( type === 'columns' || type === 'rows' ) {
					// this has same length as context - one entry for each table
					ret = fn.call( apiInst, context[i], this[i], i );
	
					if ( ret !== undefined ) {
						a.push( ret );
					}
				}
				else if ( type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell' ) {
					// columns and rows share the same structure.
					// 'this' is an array of column indexes for each context
					items = this[i];
	
					if ( type === 'column-rows' ) {
						rows = _selector_row_indexes( context[i], selector.opts );
					}
	
					for ( j=0, jen=items.length ; j<jen ; j++ ) {
						item = items[j];
	
						if ( type === 'cell' ) {
							ret = fn.call( apiInst, context[i], item.row, item.column, i, j );
						}
						else {
							ret = fn.call( apiInst, context[i], item, i, j, rows );
						}
	
						if ( ret !== undefined ) {
							a.push( ret );
						}
					}
				}
			}
	
			if ( a.length || alwaysNew ) {
				var api = new _Api( context, flatten ? a.concat.apply( [], a ) : a );
				var apiSelector = api.selector;
				apiSelector.rows = selector.rows;
				apiSelector.cols = selector.cols;
				apiSelector.opts = selector.opts;
				return api;
			}
			return this;
		},
	
	
		lastIndexOf: __arrayProto.lastIndexOf || function (obj, start)
		{
			// Bit cheeky...
			return this.indexOf.apply( this.toArray.reverse(), arguments );
		},
	
	
		length:  0,
	
	
		map: function ( fn )
		{
			var a = [];
	
			if ( __arrayProto.map ) {
				a = __arrayProto.map.call( this, fn, this );
			}
			else {
				// Compatibility for browsers without EMCA-252-5 (JS 1.6)
				for ( var i=0, ien=this.length ; i<ien ; i++ ) {
					a.push( fn.call( this, this[i], i ) );
				}
			}
	
			return new _Api( this.context, a );
		},
	
	
		pluck: function ( prop )
		{
			return this.map( function ( el ) {
				return el[ prop ];
			} );
		},
	
		pop:     __arrayProto.pop,
	
	
		push:    __arrayProto.push,
	
	
		// Does not return an API instance
		reduce: __arrayProto.reduce || function ( fn, init )
		{
			return _fnReduce( this, fn, init, 0, this.length, 1 );
		},
	
	
		reduceRight: __arrayProto.reduceRight || function ( fn, init )
		{
			return _fnReduce( this, fn, init, this.length-1, -1, -1 );
		},
	
	
		reverse: __arrayProto.reverse,
	
	
		// Object with rows, columns and opts
		selector: null,
	
	
		shift:   __arrayProto.shift,
	
	
		sort:    __arrayProto.sort, // ? name - order?
	
	
		splice:  __arrayProto.splice,
	
	
		toArray: function ()
		{
			return __arrayProto.slice.call( this );
		},
	
	
		to$: function ()
		{
			return $( this );
		},
	
	
		toJQuery: function ()
		{
			return $( this );
		},
	
	
		unique: function ()
		{
			return new _Api( this.context, _unique(this) );
		},
	
	
		unshift: __arrayProto.unshift
	} );
	
	
	_Api.extend = function ( scope, obj, ext )
	{
		// Only extend API instances and static properties of the API
		if ( ! ext.length || ! obj || ( ! (obj instanceof _Api) && ! obj.__dt_wrapper ) ) {
			return;
		}
	
		var
			i, ien,
			j, jen,
			struct, inner,
			methodScoping = function ( scope, fn, struc ) {
				return function () {
					var ret = fn.apply( scope, arguments );
	
					// Method extension
					_Api.extend( ret, ret, struc.methodExt );
					return ret;
				};
			};
	
		for ( i=0, ien=ext.length ; i<ien ; i++ ) {
			struct = ext[i];
	
			// Value
			obj[ struct.name ] = typeof struct.val === 'function' ?
				methodScoping( scope, struct.val, struct ) :
				$.isPlainObject( struct.val ) ?
					{} :
					struct.val;
	
			obj[ struct.name ].__dt_wrapper = true;
	
			// Property extension
			_Api.extend( scope, obj[ struct.name ], struct.propExt );
		}
	};
	
	
	// @todo - Is there need for an augment function?
	// _Api.augment = function ( inst, name )
	// {
	// 	// Find src object in the structure from the name
	// 	var parts = name.split('.');
	
	// 	_Api.extend( inst, obj );
	// };
	
	
	//     [
	//       {
	//         name:      'data'                -- string   - Property name
	//         val:       function () {},       -- function - Api method (or undefined if just an object
	//         methodExt: [ ... ],              -- array    - Array of Api object definitions to extend the method result
	//         propExt:   [ ... ]               -- array    - Array of Api object definitions to extend the property
	//       },
	//       {
	//         name:     'row'
	//         val:       {},
	//         methodExt: [ ... ],
	//         propExt:   [
	//           {
	//             name:      'data'
	//             val:       function () {},
	//             methodExt: [ ... ],
	//             propExt:   [ ... ]
	//           },
	//           ...
	//         ]
	//       }
	//     ]
	
	_Api.register = _api_register = function ( name, val )
	{
		if ( $.isArray( name ) ) {
			for ( var j=0, jen=name.length ; j<jen ; j++ ) {
				_Api.register( name[j], val );
			}
			return;
		}
	
		var
			i, ien,
			heir = name.split('.'),
			struct = __apiStruct,
			key, method;
	
		var find = function ( src, name ) {
			for ( var i=0, ien=src.length ; i<ien ; i++ ) {
				if ( src[i].name === name ) {
					return src[i];
				}
			}
			return null;
		};
	
		for ( i=0, ien=heir.length ; i<ien ; i++ ) {
			method = heir[i].indexOf('()') !== -1;
			key = method ?
				heir[i].replace('()', '') :
				heir[i];
	
			var src = find( struct, key );
			if ( ! src ) {
				src = {
					name:      key,
					val:       {},
					methodExt: [],
					propExt:   []
				};
				struct.push( src );
			}
	
			if ( i === ien-1 ) {
				src.val = val;
			}
			else {
				struct = method ?
					src.methodExt :
					src.propExt;
			}
		}
	};
	
	
	_Api.registerPlural = _api_registerPlural = function ( pluralName, singularName, val ) {
		_Api.register( pluralName, val );
	
		_Api.register( singularName, function () {
			var ret = val.apply( this, arguments );
	
			if ( ret === this ) {
				// Returned item is the API instance that was passed in, return it
				return this;
			}
			else if ( ret instanceof _Api ) {
				// New API instance returned, want the value from the first item
				// in the returned array for the singular result.
				return ret.length ?
					$.isArray( ret[0] ) ?
						new _Api( ret.context, ret[0] ) : // Array results are 'enhanced'
						ret[0] :
					undefined;
			}
	
			// Non-API return - just fire it back
			return ret;
		} );
	};
	
	
	/**
	 * Selector for HTML tables. Apply the given selector to the give array of
	 * DataTables settings objects.
	 *
	 * @param {string|integer} [selector] jQuery selector string or integer
	 * @param  {array} Array of DataTables settings objects to be filtered
	 * @return {array}
	 * @ignore
	 */
	var __table_selector = function ( selector, a )
	{
		// Integer is used to pick out a table by index
		if ( typeof selector === 'number' ) {
			return [ a[ selector ] ];
		}
	
		// Perform a jQuery selector on the table nodes
		var nodes = $.map( a, function (el, i) {
			return el.nTable;
		} );
	
		return $(nodes)
			.filter( selector )
			.map( function (i) {
				// Need to translate back from the table node to the settings
				var idx = $.inArray( this, nodes );
				return a[ idx ];
			} )
			.toArray();
	};
	
	
	
	/**
	 * Context selector for the API's context (i.e. the tables the API instance
	 * refers to.
	 *
	 * @name    DataTable.Api#tables
	 * @param {string|integer} [selector] Selector to pick which tables the iterator
	 *   should operate on. If not given, all tables in the current context are
	 *   used. This can be given as a jQuery selector (for example `':gt(0)'`) to
	 *   select multiple tables or as an integer to select a single table.
	 * @returns {DataTable.Api} Returns a new API instance if a selector is given.
	 */
	_api_register( 'tables()', function ( selector ) {
		// A new instance is created if there was a selector specified
		return selector ?
			new _Api( __table_selector( selector, this.context ) ) :
			this;
	} );
	
	
	_api_register( 'table()', function ( selector ) {
		var tables = this.tables( selector );
		var ctx = tables.context;
	
		// Truncate to the first matched table
		return ctx.length ?
			new _Api( ctx[0] ) :
			tables;
	} );
	
	
	_api_registerPlural( 'tables().nodes()', 'table().node()' , function () {
		return this.iterator( 'table', function ( ctx ) {
			return ctx.nTable;
		}, 1 );
	} );
	
	
	_api_registerPlural( 'tables().body()', 'table().body()' , function () {
		return this.iterator( 'table', function ( ctx ) {
			return ctx.nTBody;
		}, 1 );
	} );
	
	
	_api_registerPlural( 'tables().header()', 'table().header()' , function () {
		return this.iterator( 'table', function ( ctx ) {
			return ctx.nTHead;
		}, 1 );
	} );
	
	
	_api_registerPlural( 'tables().footer()', 'table().footer()' , function () {
		return this.iterator( 'table', function ( ctx ) {
			return ctx.nTFoot;
		}, 1 );
	} );
	
	
	_api_registerPlural( 'tables().containers()', 'table().container()' , function () {
		return this.iterator( 'table', function ( ctx ) {
			return ctx.nTableWrapper;
		}, 1 );
	} );
	
	
	
	/**
	 * Redraw the tables in the current context.
	 */
	_api_register( 'draw()', function ( paging ) {
		return this.iterator( 'table', function ( settings ) {
			if ( paging === 'page' ) {
				_fnDraw( settings );
			}
			else {
				if ( typeof paging === 'string' ) {
					paging = paging === 'full-hold' ?
						false :
						true;
				}
	
				_fnReDraw( settings, paging===false );
			}
		} );
	} );
	
	
	
	/**
	 * Get the current page index.
	 *
	 * @return {integer} Current page index (zero based)
	 *//**
	 * Set the current page.
	 *
	 * Note that if you attempt to show a page which does not exist, DataTables will
	 * not throw an error, but rather reset the paging.
	 *
	 * @param {integer|string} action The paging action to take. This can be one of:
	 *  * `integer` - The page index to jump to
	 *  * `string` - An action to take:
	 *    * `first` - Jump to first page.
	 *    * `next` - Jump to the next page
	 *    * `previous` - Jump to previous page
	 *    * `last` - Jump to the last page.
	 * @returns {DataTables.Api} this
	 */
	_api_register( 'page()', function ( action ) {
		if ( action === undefined ) {
			return this.page.info().page; // not an expensive call
		}
	
		// else, have an action to take on all tables
		return this.iterator( 'table', function ( settings ) {
			_fnPageChange( settings, action );
		} );
	} );
	
	
	/**
	 * Paging information for the first table in the current context.
	 *
	 * If you require paging information for another table, use the `table()` method
	 * with a suitable selector.
	 *
	 * @return {object} Object with the following properties set:
	 *  * `page` - Current page index (zero based - i.e. the first page is `0`)
	 *  * `pages` - Total number of pages
	 *  * `start` - Display index for the first record shown on the current page
	 *  * `end` - Display index for the last record shown on the current page
	 *  * `length` - Display length (number of records). Note that generally `start
	 *    + length = end`, but this is not always true, for example if there are
	 *    only 2 records to show on the final page, with a length of 10.
	 *  * `recordsTotal` - Full data set length
	 *  * `recordsDisplay` - Data set length once the current filtering criterion
	 *    are applied.
	 */
	_api_register( 'page.info()', function ( action ) {
		if ( this.context.length === 0 ) {
			return undefined;
		}
	
		var
			settings   = this.context[0],
			start      = settings._iDisplayStart,
			len        = settings.oFeatures.bPaginate ? settings._iDisplayLength : -1,
			visRecords = settings.fnRecordsDisplay(),
			all        = len === -1;
	
		return {
			"page":           all ? 0 : Math.floor( start / len ),
			"pages":          all ? 1 : Math.ceil( visRecords / len ),
			"start":          start,
			"end":            settings.fnDisplayEnd(),
			"length":         len,
			"recordsTotal":   settings.fnRecordsTotal(),
			"recordsDisplay": visRecords,
			"serverSide":     _fnDataSource( settings ) === 'ssp'
		};
	} );
	
	
	/**
	 * Get the current page length.
	 *
	 * @return {integer} Current page length. Note `-1` indicates that all records
	 *   are to be shown.
	 *//**
	 * Set the current page length.
	 *
	 * @param {integer} Page length to set. Use `-1` to show all records.
	 * @returns {DataTables.Api} this
	 */
	_api_register( 'page.len()', function ( len ) {
		// Note that we can't call this function 'length()' because `length`
		// is a Javascript property of functions which defines how many arguments
		// the function expects.
		if ( len === undefined ) {
			return this.context.length !== 0 ?
				this.context[0]._iDisplayLength :
				undefined;
		}
	
		// else, set the page length
		return this.iterator( 'table', function ( settings ) {
			_fnLengthChange( settings, len );
		} );
	} );
	
	
	
	var __reload = function ( settings, holdPosition, callback ) {
		// Use the draw event to trigger a callback
		if ( callback ) {
			var api = new _Api( settings );
	
			api.one( 'draw', function () {
				callback( api.ajax.json() );
			} );
		}
	
		if ( _fnDataSource( settings ) == 'ssp' ) {
			_fnReDraw( settings, holdPosition );
		}
		else {
			_fnProcessingDisplay( settings, true );
	
			// Cancel an existing request
			var xhr = settings.jqXHR;
			if ( xhr && xhr.readyState !== 4 ) {
				xhr.abort();
			}
	
			// Trigger xhr
			_fnBuildAjax( settings, [], function( json ) {
				_fnClearTable( settings );
	
				var data = _fnAjaxDataSrc( settings, json );
				for ( var i=0, ien=data.length ; i<ien ; i++ ) {
					_fnAddData( settings, data[i] );
				}
	
				_fnReDraw( settings, holdPosition );
				_fnProcessingDisplay( settings, false );
			} );
		}
	};
	
	
	/**
	 * Get the JSON response from the last Ajax request that DataTables made to the
	 * server. Note that this returns the JSON from the first table in the current
	 * context.
	 *
	 * @return {object} JSON received from the server.
	 */
	_api_register( 'ajax.json()', function () {
		var ctx = this.context;
	
		if ( ctx.length > 0 ) {
			return ctx[0].json;
		}
	
		// else return undefined;
	} );
	
	
	/**
	 * Get the data submitted in the last Ajax request
	 */
	_api_register( 'ajax.params()', function () {
		var ctx = this.context;
	
		if ( ctx.length > 0 ) {
			return ctx[0].oAjaxData;
		}
	
		// else return undefined;
	} );
	
	
	/**
	 * Reload tables from the Ajax data source. Note that this function will
	 * automatically re-draw the table when the remote data has been loaded.
	 *
	 * @param {boolean} [reset=true] Reset (default) or hold the current paging
	 *   position. A full re-sort and re-filter is performed when this method is
	 *   called, which is why the pagination reset is the default action.
	 * @returns {DataTables.Api} this
	 */
	_api_register( 'ajax.reload()', function ( callback, resetPaging ) {
		return this.iterator( 'table', function (settings) {
			__reload( settings, resetPaging===false, callback );
		} );
	} );
	
	
	/**
	 * Get the current Ajax URL. Note that this returns the URL from the first
	 * table in the current context.
	 *
	 * @return {string} Current Ajax source URL
	 *//**
	 * Set the Ajax URL. Note that this will set the URL for all tables in the
	 * current context.
	 *
	 * @param {string} url URL to set.
	 * @returns {DataTables.Api} this
	 */
	_api_register( 'ajax.url()', function ( url ) {
		var ctx = this.context;
	
		if ( url === undefined ) {
			// get
			if ( ctx.length === 0 ) {
				return undefined;
			}
			ctx = ctx[0];
	
			return ctx.ajax ?
				$.isPlainObject( ctx.ajax ) ?
					ctx.ajax.url :
					ctx.ajax :
				ctx.sAjaxSource;
		}
	
		// set
		return this.iterator( 'table', function ( settings ) {
			if ( $.isPlainObject( settings.ajax ) ) {
				settings.ajax.url = url;
			}
			else {
				settings.ajax = url;
			}
			// No need to consider sAjaxSource here since DataTables gives priority
			// to `ajax` over `sAjaxSource`. So setting `ajax` here, renders any
			// value of `sAjaxSource` redundant.
		} );
	} );
	
	
	/**
	 * Load data from the newly set Ajax URL. Note that this method is only
	 * available when `ajax.url()` is used to set a URL. Additionally, this method
	 * has the same effect as calling `ajax.reload()` but is provided for
	 * convenience when setting a new URL. Like `ajax.reload()` it will
	 * automatically redraw the table once the remote data has been loaded.
	 *
	 * @returns {DataTables.Api} this
	 */
	_api_register( 'ajax.url().load()', function ( callback, resetPaging ) {
		// Same as a reload, but makes sense to present it for easy access after a
		// url change
		return this.iterator( 'table', function ( ctx ) {
			__reload( ctx, resetPaging===false, callback );
		} );
	} );
	
	
	
	
	var _selector_run = function ( type, selector, selectFn, settings, opts )
	{
		var
			out = [], res,
			a, i, ien, j, jen,
			selectorType = typeof selector;
	
		// Can't just check for isArray here, as an API or jQuery instance might be
		// given with their array like look
		if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) {
			selector = [ selector ];
		}
	
		for ( i=0, ien=selector.length ; i<ien ; i++ ) {
			a = selector[i] && selector[i].split ?
				selector[i].split(',') :
				[ selector[i] ];
	
			for ( j=0, jen=a.length ; j<jen ; j++ ) {
				res = selectFn( typeof a[j] === 'string' ? $.trim(a[j]) : a[j] );
	
				if ( res && res.length ) {
					out = out.concat( res );
				}
			}
		}
	
		// selector extensions
		var ext = _ext.selector[ type ];
		if ( ext.length ) {
			for ( i=0, ien=ext.length ; i<ien ; i++ ) {
				out = ext[i]( settings, opts, out );
			}
		}
	
		return _unique( out );
	};
	
	
	var _selector_opts = function ( opts )
	{
		if ( ! opts ) {
			opts = {};
		}
	
		// Backwards compatibility for 1.9- which used the terminology filter rather
		// than search
		if ( opts.filter && opts.search === undefined ) {
			opts.search = opts.filter;
		}
	
		return $.extend( {
			search: 'none',
			order: 'current',
			page: 'all'
		}, opts );
	};
	
	
	var _selector_first = function ( inst )
	{
		// Reduce the API instance to the first item found
		for ( var i=0, ien=inst.length ; i<ien ; i++ ) {
			if ( inst[i].length > 0 ) {
				// Assign the first element to the first item in the instance
				// and truncate the instance and context
				inst[0] = inst[i];
				inst[0].length = 1;
				inst.length = 1;
				inst.context = [ inst.context[i] ];
	
				return inst;
			}
		}
	
		// Not found - return an empty instance
		inst.length = 0;
		return inst;
	};
	
	
	var _selector_row_indexes = function ( settings, opts )
	{
		var
			i, ien, tmp, a=[],
			displayFiltered = settings.aiDisplay,
			displayMaster = settings.aiDisplayMaster;
	
		var
			search = opts.search,  // none, applied, removed
			order  = opts.order,   // applied, current, index (original - compatibility with 1.9)
			page   = opts.page;    // all, current
	
		if ( _fnDataSource( settings ) == 'ssp' ) {
			// In server-side processing mode, most options are irrelevant since
			// rows not shown don't exist and the index order is the applied order
			// Removed is a special case - for consistency just return an empty
			// array
			return search === 'removed' ?
				[] :
				_range( 0, displayMaster.length );
		}
		else if ( page == 'current' ) {
			// Current page implies that order=current and fitler=applied, since it is
			// fairly senseless otherwise, regardless of what order and search actually
			// are
			for ( i=settings._iDisplayStart, ien=settings.fnDisplayEnd() ; i<ien ; i++ ) {
				a.push( displayFiltered[i] );
			}
		}
		else if ( order == 'current' || order == 'applied' ) {
			a = search == 'none' ?
				displayMaster.slice() :                      // no search
				search == 'applied' ?
					displayFiltered.slice() :                // applied search
					$.map( displayMaster, function (el, i) { // removed search
						return $.inArray( el, displayFiltered ) === -1 ? el : null;
					} );
		}
		else if ( order == 'index' || order == 'original' ) {
			for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
				if ( search == 'none' ) {
					a.push( i );
				}
				else { // applied | removed
					tmp = $.inArray( i, displayFiltered );
	
					if ((tmp === -1 && search == 'removed') ||
						(tmp >= 0   && search == 'applied') )
					{
						a.push( i );
					}
				}
			}
		}
	
		return a;
	};
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Rows
	 *
	 * {}          - no selector - use all available rows
	 * {integer}   - row aoData index
	 * {node}      - TR node
	 * {string}    - jQuery selector to apply to the TR elements
	 * {array}     - jQuery array of nodes, or simply an array of TR nodes
	 *
	 */
	
	
	var __row_selector = function ( settings, selector, opts )
	{
		var run = function ( sel ) {
			var selInt = _intVal( sel );
			var i, ien;
	
			// Short cut - selector is a number and no options provided (default is
			// all records, so no need to check if the index is in there, since it
			// must be - dev error if the index doesn't exist).
			if ( selInt !== null && ! opts ) {
				return [ selInt ];
			}
	
			var rows = _selector_row_indexes( settings, opts );
	
			if ( selInt !== null && $.inArray( selInt, rows ) !== -1 ) {
				// Selector - integer
				return [ selInt ];
			}
			else if ( ! sel ) {
				// Selector - none
				return rows;
			}
	
			// Selector - function
			if ( typeof sel === 'function' ) {
				return $.map( rows, function (idx) {
					var row = settings.aoData[ idx ];
					return sel( idx, row._aData, row.nTr ) ? idx : null;
				} );
			}
	
			// Get nodes in the order from the `rows` array with null values removed
			var nodes = _removeEmpty(
				_pluck_order( settings.aoData, rows, 'nTr' )
			);
	
			// Selector - node
			if ( sel.nodeName ) {
				if ( $.inArray( sel, nodes ) !== -1 ) {
					return [ sel._DT_RowIndex ]; // sel is a TR node that is in the table
					                             // and DataTables adds a prop for fast lookup
				}
			}
	
			// ID selector. Want to always be able to select rows by id, regardless
			// of if the tr element has been created or not, so can't rely upon
			// jQuery here - hence a custom implementation. This does not match
			// Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything,
			// but to select it using a CSS selector engine (like Sizzle or
			// querySelect) it would need to need to be escaped for some characters.
			// DataTables simplifies this for row selectors since you can select
			// only a row. A # indicates an id any anything that follows is the id -
			// unescaped.
			if ( typeof sel === 'string' && sel.charAt(0) === '#' ) {
				// get row index from id
				var rowObj = settings.aIds[ sel.replace( /^#/, '' ) ];
				if ( rowObj !== undefined ) {
					return [ rowObj.idx ];
				}
	
				// need to fall through to jQuery in case there is DOM id that
				// matches
			}
	
			// Selector - jQuery selector string, array of nodes or jQuery object/
			// As jQuery's .filter() allows jQuery objects to be passed in filter,
			// it also allows arrays, so this will cope with all three options
			return $(nodes)
				.filter( sel )
				.map( function () {
					return this._DT_RowIndex;
				} )
				.toArray();
		};
	
		return _selector_run( 'row', selector, run, settings, opts );
	};
	
	
	_api_register( 'rows()', function ( selector, opts ) {
		// argument shifting
		if ( selector === undefined ) {
			selector = '';
		}
		else if ( $.isPlainObject( selector ) ) {
			opts = selector;
			selector = '';
		}
	
		opts = _selector_opts( opts );
	
		var inst = this.iterator( 'table', function ( settings ) {
			return __row_selector( settings, selector, opts );
		}, 1 );
	
		// Want argument shifting here and in __row_selector?
		inst.selector.rows = selector;
		inst.selector.opts = opts;
	
		return inst;
	} );
	
	_api_register( 'rows().nodes()', function () {
		return this.iterator( 'row', function ( settings, row ) {
			return settings.aoData[ row ].nTr || undefined;
		}, 1 );
	} );
	
	_api_register( 'rows().data()', function () {
		return this.iterator( true, 'rows', function ( settings, rows ) {
			return _pluck_order( settings.aoData, rows, '_aData' );
		}, 1 );
	} );
	
	_api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) {
		return this.iterator( 'row', function ( settings, row ) {
			var r = settings.aoData[ row ];
			return type === 'search' ? r._aFilterData : r._aSortData;
		}, 1 );
	} );
	
	_api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) {
		return this.iterator( 'row', function ( settings, row ) {
			_fnInvalidate( settings, row, src );
		} );
	} );
	
	_api_registerPlural( 'rows().indexes()', 'row().index()', function () {
		return this.iterator( 'row', function ( settings, row ) {
			return row;
		}, 1 );
	} );
	
	_api_registerPlural( 'rows().ids()', 'row().id()', function ( hash ) {
		var a = [];
		var context = this.context;
	
		// `iterator` will drop undefined values, but in this case we want them
		for ( var i=0, ien=context.length ; i<ien ; i++ ) {
			for ( var j=0, jen=this[i].length ; j<jen ; j++ ) {
				var id = context[i].rowIdFn( context[i].aoData[ this[i][j] ]._aData );
				a.push( (hash === true ? '#' : '' )+ id );
			}
		}
	
		return new _Api( context, a );
	} );
	
	_api_registerPlural( 'rows().remove()', 'row().remove()', function () {
		var that = this;
	
		this.iterator( 'row', function ( settings, row, thatIdx ) {
			var data = settings.aoData;
			var rowData = data[ row ];
			var i, ien, j, jen;
			var loopRow, loopCells;
	
			data.splice( row, 1 );
	
			// Update the cached indexes
			for ( i=0, ien=data.length ; i<ien ; i++ ) {
				loopRow = data[i];
				loopCells = loopRow.anCells;
	
				// Rows
				if ( loopRow.nTr !== null ) {
					loopRow.nTr._DT_RowIndex = i;
				}
	
				// Cells
				if ( loopCells !== null ) {
					for ( j=0, jen=loopCells.length ; j<jen ; j++ ) {
						loopCells[j]._DT_CellIndex.row = i;
					}
				}
			}
	
			// Delete from the display arrays
			_fnDeleteIndex( settings.aiDisplayMaster, row );
			_fnDeleteIndex( settings.aiDisplay, row );
			_fnDeleteIndex( that[ thatIdx ], row, false ); // maintain local indexes
	
			// Check for an 'overflow' they case for displaying the table
			_fnLengthOverflow( settings );
	
			// Remove the row's ID reference if there is one
			var id = settings.rowIdFn( rowData._aData );
			if ( id !== undefined ) {
				delete settings.aIds[ id ];
			}
		} );
	
		this.iterator( 'table', function ( settings ) {
			for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
				settings.aoData[i].idx = i;
			}
		} );
	
		return this;
	} );
	
	
	_api_register( 'rows.add()', function ( rows ) {
		var newRows = this.iterator( 'table', function ( settings ) {
				var row, i, ien;
				var out = [];
	
				for ( i=0, ien=rows.length ; i<ien ; i++ ) {
					row = rows[i];
	
					if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) {
						out.push( _fnAddTr( settings, row )[0] );
					}
					else {
						out.push( _fnAddData( settings, row ) );
					}
				}
	
				return out;
			}, 1 );
	
		// Return an Api.rows() extended instance, so rows().nodes() etc can be used
		var modRows = this.rows( -1 );
		modRows.pop();
		$.merge( modRows, newRows );
	
		return modRows;
	} );
	
	
	
	
	
	/**
	 *
	 */
	_api_register( 'row()', function ( selector, opts ) {
		return _selector_first( this.rows( selector, opts ) );
	} );
	
	
	_api_register( 'row().data()', function ( data ) {
		var ctx = this.context;
	
		if ( data === undefined ) {
			// Get
			return ctx.length && this.length ?
				ctx[0].aoData[ this[0] ]._aData :
				undefined;
		}
	
		// Set
		ctx[0].aoData[ this[0] ]._aData = data;
	
		// Automatically invalidate
		_fnInvalidate( ctx[0], this[0], 'data' );
	
		return this;
	} );
	
	
	_api_register( 'row().node()', function () {
		var ctx = this.context;
	
		return ctx.length && this.length ?
			ctx[0].aoData[ this[0] ].nTr || null :
			null;
	} );
	
	
	_api_register( 'row.add()', function ( row ) {
		// Allow a jQuery object to be passed in - only a single row is added from
		// it though - the first element in the set
		if ( row instanceof $ && row.length ) {
			row = row[0];
		}
	
		var rows = this.iterator( 'table', function ( settings ) {
			if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) {
				return _fnAddTr( settings, row )[0];
			}
			return _fnAddData( settings, row );
		} );
	
		// Return an Api.rows() extended instance, with the newly added row selected
		return this.row( rows[0] );
	} );
	
	
	
	var __details_add = function ( ctx, row, data, klass )
	{
		// Convert to array of TR elements
		var rows = [];
		var addRow = function ( r, k ) {
			// Recursion to allow for arrays of jQuery objects
			if ( $.isArray( r ) || r instanceof $ ) {
				for ( var i=0, ien=r.length ; i<ien ; i++ ) {
					addRow( r[i], k );
				}
				return;
			}
	
			// If we get a TR element, then just add it directly - up to the dev
			// to add the correct number of columns etc
			if ( r.nodeName && r.nodeName.toLowerCase() === 'tr' ) {
				rows.push( r );
			}
			else {
				// Otherwise create a row with a wrapper
				var created = $('<tr><td/></tr>').addClass( k );
				$('td', created)
					.addClass( k )
					.html( r )
					[0].colSpan = _fnVisbleColumns( ctx );
	
				rows.push( created[0] );
			}
		};
	
		addRow( data, klass );
	
		if ( row._details ) {
			row._details.remove();
		}
	
		row._details = $(rows);
	
		// If the children were already shown, that state should be retained
		if ( row._detailsShow ) {
			row._details.insertAfter( row.nTr );
		}
	};
	
	
	var __details_remove = function ( api, idx )
	{
		var ctx = api.context;
	
		if ( ctx.length ) {
			var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ];
	
			if ( row && row._details ) {
				row._details.remove();
	
				row._detailsShow = undefined;
				row._details = undefined;
			}
		}
	};
	
	
	var __details_display = function ( api, show ) {
		var ctx = api.context;
	
		if ( ctx.length && api.length ) {
			var row = ctx[0].aoData[ api[0] ];
	
			if ( row._details ) {
				row._detailsShow = show;
	
				if ( show ) {
					row._details.insertAfter( row.nTr );
				}
				else {
					row._details.detach();
				}
	
				__details_events( ctx[0] );
			}
		}
	};
	
	
	var __details_events = function ( settings )
	{
		var api = new _Api( settings );
		var namespace = '.dt.DT_details';
		var drawEvent = 'draw'+namespace;
		var colvisEvent = 'column-visibility'+namespace;
		var destroyEvent = 'destroy'+namespace;
		var data = settings.aoData;
	
		api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent );
	
		if ( _pluck( data, '_details' ).length > 0 ) {
			// On each draw, insert the required elements into the document
			api.on( drawEvent, function ( e, ctx ) {
				if ( settings !== ctx ) {
					return;
				}
	
				api.rows( {page:'current'} ).eq(0).each( function (idx) {
					// Internal data grab
					var row = data[ idx ];
	
					if ( row._detailsShow ) {
						row._details.insertAfter( row.nTr );
					}
				} );
			} );
	
			// Column visibility change - update the colspan
			api.on( colvisEvent, function ( e, ctx, idx, vis ) {
				if ( settings !== ctx ) {
					return;
				}
	
				// Update the colspan for the details rows (note, only if it already has
				// a colspan)
				var row, visible = _fnVisbleColumns( ctx );
	
				for ( var i=0, ien=data.length ; i<ien ; i++ ) {
					row = data[i];
	
					if ( row._details ) {
						row._details.children('td[colspan]').attr('colspan', visible );
					}
				}
			} );
	
			// Table destroyed - nuke any child rows
			api.on( destroyEvent, function ( e, ctx ) {
				if ( settings !== ctx ) {
					return;
				}
	
				for ( var i=0, ien=data.length ; i<ien ; i++ ) {
					if ( data[i]._details ) {
						__details_remove( api, i );
					}
				}
			} );
		}
	};
	
	// Strings for the method names to help minification
	var _emp = '';
	var _child_obj = _emp+'row().child';
	var _child_mth = _child_obj+'()';
	
	// data can be:
	//  tr
	//  string
	//  jQuery or array of any of the above
	_api_register( _child_mth, function ( data, klass ) {
		var ctx = this.context;
	
		if ( data === undefined ) {
			// get
			return ctx.length && this.length ?
				ctx[0].aoData[ this[0] ]._details :
				undefined;
		}
		else if ( data === true ) {
			// show
			this.child.show();
		}
		else if ( data === false ) {
			// remove
			__details_remove( this );
		}
		else if ( ctx.length && this.length ) {
			// set
			__details_add( ctx[0], ctx[0].aoData[ this[0] ], data, klass );
		}
	
		return this;
	} );
	
	
	_api_register( [
		_child_obj+'.show()',
		_child_mth+'.show()' // only when `child()` was called with parameters (without
	], function ( show ) {   // it returns an object and this method is not executed)
		__details_display( this, true );
		return this;
	} );
	
	
	_api_register( [
		_child_obj+'.hide()',
		_child_mth+'.hide()' // only when `child()` was called with parameters (without
	], function () {         // it returns an object and this method is not executed)
		__details_display( this, false );
		return this;
	} );
	
	
	_api_register( [
		_child_obj+'.remove()',
		_child_mth+'.remove()' // only when `child()` was called with parameters (without
	], function () {           // it returns an object and this method is not executed)
		__details_remove( this );
		return this;
	} );
	
	
	_api_register( _child_obj+'.isShown()', function () {
		var ctx = this.context;
	
		if ( ctx.length && this.length ) {
			// _detailsShown as false or undefined will fall through to return false
			return ctx[0].aoData[ this[0] ]._detailsShow || false;
		}
		return false;
	} );
	
	
	
	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
	 * Columns
	 *
	 * {integer}           - column index (>=0 count from left, <0 count from right)
	 * "{integer}:visIdx"  - visible column index (i.e. translate to column index)  (>=0 count from left, <0 count from right)
	 * "{integer}:visible" - alias for {integer}:visIdx  (>=0 count from left, <0 count from right)
	 * "{string}:name"     - column name
	 * "{string}"          - jQuery selector on column header nodes
	 *
	 */
	
	// can be an array of these items, comma separated list, or an array of comma
	// separated lists
	
	var __re_column_selector = /^(.+):(name|visIdx|visible)$/;
	
	
	// r1 and r2 are redundant - but it means that the parameters match for the
	// iterator callback in columns().data()
	var __columnData = function ( settings, column, r1, r2, rows ) {
		var a = [];
		for ( var row=0, ien=rows.length ; row<ien ; row++ ) {
			a.push( _fnGetCellData( settings, rows[row], column ) );
		}
		return a;
	};
	
	
	var __column_selector = function ( settings, selector, opts )
	{
		var
			columns = settings.aoColumns,
			names = _pluck( columns, 'sName' ),
			nodes = _pluck( columns, 'nTh' );
	
		var run = function ( s ) {
			var selInt = _intVal( s );
	
			// Selector - all
			if ( s === '' ) {
				return _range( columns.length );
			}
			
			// Selector - index
			if ( selInt !== null ) {
				return [ selInt >= 0 ?
					selInt : // Count from left
					columns.length + selInt // Count from right (+ because its a negative value)
				];
			}
			
			// Selector = function
			if ( typeof s === 'function' ) {
				var rows = _selector_row_indexes( settings, opts );
	
				return $.map( columns, function (col, idx) {
					return s(
							idx,
							__columnData( settings, idx, 0, 0, rows ),
							nodes[ idx ]
						) ? idx : null;
				} );
			}
	
			// jQuery or string selector
			var match = typeof s === 'string' ?
				s.match( __re_column_selector ) :
				'';
	
			if ( match ) {
				switch( match[2] ) {
					case 'visIdx':
					case 'visible':
						var idx = parseInt( match[1], 10 );
						// Visible index given, convert to column index
						if ( idx < 0 ) {
							// Counting from the right
							var visColumns = $.map( columns, function (col,i) {
								return col.bVisible ? i : null;
							} );
							return [ visColumns[ visColumns.length + idx ] ];
						}
						// Counting from the left
						return [ _fnVisibleToColumnIndex( settings, idx ) ];
	
					case 'name':
						// match by name. `names` is column index complete and in order
						return $.map( names, function (name, i) {
							return name === match[1] ? i : null;
						} );
				}
			}
			else {
				// jQuery selector on the TH elements for the columns
				return $( nodes )
					.filter( s )
					.map( function () {
						return $.inArray( this, nodes ); // `nodes` is column index complete and in order
					} )
					.toArray();
			}
		};
	
		return _selector_run( 'column', selector, run, settings, opts );
	};
	
	
	var __setColumnVis = function ( settings, column, vis, recalc ) {
		var
			cols = settings.aoColumns,
			col  = cols[ column ],
			data = settings.aoData,
			row, cells, i, ien, tr;
	
		// Get
		if ( vis === undefined ) {
			return col.bVisible;
		}
	
		// Set
		// No change
		if ( col.bVisible === vis ) {
			return;
		}
	
		if ( vis ) {
			// Insert column
			// Need to decide if we should use appendChild or insertBefore
			var insertBefore = $.inArray( true, _pluck(cols, 'bVisible'), column+1 );
	
			for ( i=0, ien=data.length ; i<ien ; i++ ) {
				tr = data[i].nTr;
				cells = data[i].anCells;
	
				if ( tr ) {
					// insertBefore can act like appendChild if 2nd arg is null
					tr.insertBefore( cells[ column ], cells[ insertBefore ] || null );
				}
			}
		}
		else {
			// Remove column
			$( _pluck( settings.aoData, 'anCells', column ) ).detach();
		}
	
		// Common actions
		col.bVisible = vis;
		_fnDrawHead( settings, settings.aoHeader );
		_fnDrawHead( settings, settings.aoFooter );
	
		if ( recalc === undefined || recalc ) {
			// Automatically adjust column sizing
			_fnAdjustColumnSizing( settings );
	
			// Realign columns for scrolling
			if ( settings.oScroll.sX || settings.oScroll.sY ) {
				_fnScrollDraw( settings );
			}
		}
	
		_fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis, recalc] );
	
		_fnSaveState( settings );
	};
	
	
	_api_register( 'columns()', function ( selector, opts ) {
		// argument shifting
		if ( selector === undefined ) {
			selector = '';
		}
		else if ( $.isPlainObject( selector ) ) {
			opts = selector;
			selector = '';
		}
	
		opts = _selector_opts( opts );
	
		var inst = this.iterator( 'table', function ( settings ) {
			return __column_selector( settings, selector, opts );
		}, 1 );
	
		// Want argument shifting here and in _row_selector?
		inst.selector.cols = selector;
		inst.selector.opts = opts;
	
		return inst;
	} );
	
	_api_registerPlural( 'columns().header()', 'column().header()', function ( selector, opts ) {
		return this.iterator( 'column', function ( settings, column ) {
			return settings.aoColumns[column].nTh;
		}, 1 );
	} );
	
	_api_registerPlural( 'columns().footer()', 'column().footer()', function ( selector, opts ) {
		return this.iterator( 'column', function ( settings, column ) {
			return settings.aoColumns[column].nTf;
		}, 1 );
	} );
	
	_api_registerPlural( 'columns().data()', 'column().data()', function () {
		return this.iterator( 'column-rows', __columnData, 1 );
	} );
	
	_api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () {
		return this.iterator( 'column', function ( settings, column ) {
			return settings.aoColumns[column].mData;
		}, 1 );
	} );
	
	_api_registerPlural( 'columns().cache()', 'column().cache()', function ( type ) {
		return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
			return _pluck_order( settings.aoData, rows,
				type === 'search' ? '_aFilterData' : '_aSortData', column
			);
		}, 1 );
	} );
	
	_api_registerPlural( 'columns().nodes()', 'column().nodes()', function () {
		return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
			return _pluck_order( settings.aoData, rows, 'anCells', column ) ;
		}, 1 );
	} );
	
	_api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) {
		return this.iterator( 'column', function ( settings, column ) {
			if ( vis === undefined ) {
				return settings.aoColumns[ column ].bVisible;
			} // else
			__setColumnVis( settings, column, vis, calc );
		} );
	} );
	
	_api_registerPlural( 'columns().indexes()', 'column().index()', function ( type ) {
		return this.iterator( 'column', function ( settings, column ) {
			return type === 'visible' ?
				_fnColumnIndexToVisible( settings, column ) :
				column;
		}, 1 );
	} );
	
	_api_register( 'columns.adjust()', function () {
		return this.iterator( 'table', function ( settings ) {
			_fnAdjustColumnSizing( settings );
		}, 1 );
	} );
	
	_api_register( 'column.index()', function ( type, idx ) {
		if ( this.context.length !== 0 ) {
			var ctx = this.context[0];
	
			if ( type === 'fromVisible' || type === 'toData' ) {
				return _fnVisibleToColumnIndex( ctx, idx );
			}
			else if ( type === 'fromData' || type === 'toVisible' ) {
				return _fnColumnIndexToVisible( ctx, idx );
			}
		}
	} );
	
	_api_register( 'column()', function ( selector, opts ) {
		return _selector_first( this.columns( selector, opts ) );
	} );
	
	
	
	
	var __cell_selector = function ( settings, selector, opts )
	{
		var data = settings.aoData;
		var rows = _selector_row_indexes( settings, opts );
		var cells = _removeEmpty( _pluck_order( data, rows, 'anCells' ) );
		var allCells = $( [].concat.apply([], cells) );
		var row;
		var columns = settings.aoColumns.length;
		var a, i, ien, j, o, host;
	
		var run = function ( s ) {
			var fnSelector = typeof s === 'function';
	
			if ( s === null || s === undefined || fnSelector ) {
				// All cells and function selectors
				a = [];
	
				for ( i=0, ien=rows.length ; i<ien ; i++ ) {
					row = rows[i];
	
					for ( j=0 ; j<columns ; j++ ) {
						o = {
							row: row,
							column: j
						};
	
						if ( fnSelector ) {
							// Selector - function
							host = data[ row ];
	
							if ( s( o, _fnGetCellData(settings, row, j), host.anCells ? host.anCells[j] : null ) ) {
								a.push( o );
							}
						}
						else {
							// Selector - all
							a.push( o );
						}
					}
				}
	
				return a;
			}
			
			// Selector - index
			if ( $.isPlainObject( s ) ) {
				return [s];
			}
	
			// Selector - jQuery filtered cells
			return allCells
				.filter( s )
				.map( function (i, el) {
					return { // use a new object, in case someone changes the values
						row:    el._DT_CellIndex.row,
						column: el._DT_CellIndex.column
	 				};
				} )
				.toArray();
		};
	
		return _selector_run( 'cell', selector, run, settings, opts );
	};
	
	
	
	
	_api_register( 'cells()', function ( rowSelector, columnSelector, opts ) {
		// Argument shifting
		if ( $.isPlainObject( rowSelector ) ) {
			// Indexes
			if ( rowSelector.row === undefined ) {
				// Selector options in first parameter
				opts = rowSelector;
				rowSelector = null;
			}
			else {
				// Cell index objects in first parameter
				opts = columnSelector;
				columnSelector = null;
			}
		}
		if ( $.isPlainObject( columnSelector ) ) {
			opts = columnSelector;
			columnSelector = null;
		}
	
		// Cell selector
		if ( columnSelector === null || columnSelector === undefined ) {
			return this.iterator( 'table', function ( settings ) {
				return __cell_selector( settings, rowSelector, _selector_opts( opts ) );
			} );
		}
	
		// Row + column selector
		var columns = this.columns( columnSelector, opts );
		var rows = this.rows( rowSelector, opts );
		var a, i, ien, j, jen;
	
		var cells = this.iterator( 'table', function ( settings, idx ) {
			a = [];
	
			for ( i=0, ien=rows[idx].length ; i<ien ; i++ ) {
				for ( j=0, jen=columns[idx].length ; j<jen ; j++ ) {
					a.push( {
						row:    rows[idx][i],
						column: columns[idx][j]
					} );
				}
			}
	
			return a;
		}, 1 );
	
		$.extend( cells.selector, {
			cols: columnSelector,
			rows: rowSelector,
			opts: opts
		} );
	
		return cells;
	} );
	
	
	_api_registerPlural( 'cells().nodes()', 'cell().node()', function () {
		return this.iterator( 'cell', function ( settings, row, column ) {
			var cells = settings.aoData[ row ].anCells;
			return cells ?
				cells[ column ] :
				undefined;
		}, 1 );
	} );
	
	
	_api_register( 'cells().data()', function () {
		return this.iterator( 'cell', function ( settings, row, column ) {
			return _fnGetCellData( settings, row, column );
		}, 1 );
	} );
	
	
	_api_registerPlural( 'cells().cache()', 'cell().cache()', function ( type ) {
		type = type === 'search' ? '_aFilterData' : '_aSortData';
	
		return this.iterator( 'cell', function ( settings, row, column ) {
			return settings.aoData[ row ][ type ][ column ];
		}, 1 );
	} );
	
	
	_api_registerPlural( 'cells().render()', 'cell().render()', function ( type ) {
		return this.iterator( 'cell', function ( settings, row, column ) {
			return _fnGetCellData( settings, row, column, type );
		}, 1 );
	} );
	
	
	_api_registerPlural( 'cells().indexes()', 'cell().index()', function () {
		return this.iterator( 'cell', function ( settings, row, column ) {
			return {
				row: row,
				column: column,
				columnVisible: _fnColumnIndexToVisible( settings, column )
			};
		}, 1 );
	} );
	
	
	_api_registerPlural( 'cells().invalidate()', 'cell().invalidate()', function ( src ) {
		return this.iterator( 'cell', function ( settings, row, column ) {
			_fnInvalidate( settings, row, src, column );
		} );
	} );
	
	
	
	_api_register( 'cell()', function ( rowSelector, columnSelector, opts ) {
		return _selector_first( this.cells( rowSelector, columnSelector, opts ) );
	} );
	
	
	_api_register( 'cell().data()', function ( data ) {
		var ctx = this.context;
		var cell = this[0];
	
		if ( data === undefined ) {
			// Get
			return ctx.length && cell.length ?
				_fnGetCellData( ctx[0], cell[0].row, cell[0].column ) :
				undefined;
		}
	
		// Set
		_fnSetCellData( ctx[0], cell[0].row, cell[0].column, data );
		_fnInvalidate( ctx[0], cell[0].row, 'data', cell[0].column );
	
		return this;
	} );
	
	
	
	/**
	 * Get current ordering (sorting) that has been applied to the table.
	 *
	 * @returns {array} 2D array containing the sorting information for the first
	 *   table in the current context. Each element in the parent array represents
	 *   a column being sorted upon (i.e. multi-sorting with two columns would have
	 *   2 inner arrays). The inner arrays may have 2 or 3 elements. The first is
	 *   the column index that the sorting condition applies to, the second is the
	 *   direction of the sort (`desc` or `asc`) and, optionally, the third is the
	 *   index of the sorting order from the `column.sorting` initialisation array.
	 *//**
	 * Set the ordering for the table.
	 *
	 * @param {integer} order Column index to sort upon.
	 * @param {string} direction Direction of the sort to be applied (`asc` or `desc`)
	 * @returns {DataTables.Api} this
	 *//**
	 * Set the ordering for the table.
	 *
	 * @param {array} order 1D array of sorting information to be applied.
	 * @param {array} [...] Optional additional sorting conditions
	 * @returns {DataTables.Api} this
	 *//**
	 * Set the ordering for the table.
	 *
	 * @param {array} order 2D array of sorting information to be applied.
	 * @returns {DataTables.Api} this
	 */
	_api_register( 'order()', function ( order, dir ) {
		var ctx = this.context;
	
		if ( order === undefined ) {
			// get
			return ctx.length !== 0 ?
				ctx[0].aaSorting :
				undefined;
		}
	
		// set
		if ( typeof order === 'number' ) {
			// Simple column / direction passed in
			order = [ [ order, dir ] ];
		}
		else if ( ! $.isArray( order[0] ) ) {
			// Arguments passed in (list of 1D arrays)
			order = Array.prototype.slice.call( arguments );
		}
		// otherwise a 2D array was passed in
	
		return this.iterator( 'table', function ( settings ) {
			settings.aaSorting = order.slice();
		} );
	} );
	
	
	/**
	 * Attach a sort listener to an element for a given column
	 *
	 * @param {node|jQuery|string} node Identifier for the element(s) to attach the
	 *   listener to. This can take the form of a single DOM node, a jQuery
	 *   collection of nodes or a jQuery selector which will identify the node(s).
	 * @param {integer} column the column that a click on this node will sort on
	 * @param {function} [callback] callback function when sort is run
	 * @returns {DataTables.Api} this
	 */
	_api_register( 'order.listener()', function ( node, column, callback ) {
		return this.iterator( 'table', function ( settings ) {
			_fnSortAttachListener( settings, node, column, callback );
		} );
	} );
	
	
	_api_register( 'order.fixed()', function ( set ) {
		if ( ! set ) {
			var ctx = this.context;
			var fixed = ctx.length ?
				ctx[0].aaSortingFixed :
				undefined;
	
			return $.isArray( fixed ) ?
				{ pre: fixed } :
				fixed;
		}
	
		return this.iterator( 'table', function ( settings ) {
			settings.aaSortingFixed = $.extend( true, {}, set );
		} );
	} );
	
	
	// Order by the selected column(s)
	_api_register( [
		'columns().order()',
		'column().order()'
	], function ( dir ) {
		var that = this;
	
		return this.iterator( 'table', function ( settings, i ) {
			var sort = [];
	
			$.each( that[i], function (j, col) {
				sort.push( [ col, dir ] );
			} );
	
			settings.aaSorting = sort;
		} );
	} );
	
	
	
	_api_register( 'search()', function ( input, regex, smart, caseInsen ) {
		var ctx = this.context;
	
		if ( input === undefined ) {
			// get
			return ctx.length !== 0 ?
				ctx[0].oPreviousSearch.sSearch :
				undefined;
		}
	
		// set
		return this.iterator( 'table', function ( settings ) {
			if ( ! settings.oFeatures.bFilter ) {
				return;
			}
	
			_fnFilterComplete( settings, $.extend( {}, settings.oPreviousSearch, {
				"sSearch": input+"",
				"bRegex":  regex === null ? false : regex,
				"bSmart":  smart === null ? true  : smart,
				"bCaseInsensitive": caseInsen === null ? true : caseInsen
			} ), 1 );
		} );
	} );
	
	
	_api_registerPlural(
		'columns().search()',
		'column().search()',
		function ( input, regex, smart, caseInsen ) {
			return this.iterator( 'column', function ( settings, column ) {
				var preSearch = settings.aoPreSearchCols;
	
				if ( input === undefined ) {
					// get
					return preSearch[ column ].sSearch;
				}
	
				// set
				if ( ! settings.oFeatures.bFilter ) {
					return;
				}
	
				$.extend( preSearch[ column ], {
					"sSearch": input+"",
					"bRegex":  regex === null ? false : regex,
					"bSmart":  smart === null ? true  : smart,
					"bCaseInsensitive": caseInsen === null ? true : caseInsen
				} );
	
				_fnFilterComplete( settings, settings.oPreviousSearch, 1 );
			} );
		}
	);
	
	/*
	 * State API methods
	 */
	
	_api_register( 'state()', function () {
		return this.context.length ?
			this.context[0].oSavedState :
			null;
	} );
	
	
	_api_register( 'state.clear()', function () {
		return this.iterator( 'table', function ( settings ) {
			// Save an empty object
			settings.fnStateSaveCallback.call( settings.oInstance, settings, {} );
		} );
	} );
	
	
	_api_register( 'state.loaded()', function () {
		return this.context.length ?
			this.context[0].oLoadedState :
			null;
	} );
	
	
	_api_register( 'state.save()', function () {
		return this.iterator( 'table', function ( settings ) {
			_fnSaveState( settings );
		} );
	} );
	
	
	
	/**
	 * Provide a common method for plug-ins to check the version of DataTables being
	 * used, in order to ensure compatibility.
	 *
	 *  @param {string} version Version string to check for, in the format "X.Y.Z".
	 *    Note that the formats "X" and "X.Y" are also acceptable.
	 *  @returns {boolean} true if this version of DataTables is greater or equal to
	 *    the required version, or false if this version of DataTales is not
	 *    suitable
	 *  @static
	 *  @dtopt API-Static
	 *
	 *  @example
	 *    alert( $.fn.dataTable.versionCheck( '1.9.0' ) );
	 */
	DataTable.versionCheck = DataTable.fnVersionCheck = function( version )
	{
		var aThis = DataTable.version.split('.');
		var aThat = version.split('.');
		var iThis, iThat;
	
		for ( var i=0, iLen=aThat.length ; i<iLen ; i++ ) {
			iThis = parseInt( aThis[i], 10 ) || 0;
			iThat = parseInt( aThat[i], 10 ) || 0;
	
			// Parts are the same, keep comparing
			if (iThis === iThat) {
				continue;
			}
	
			// Parts are different, return immediately
			return iThis > iThat;
		}
	
		return true;
	};
	
	
	/**
	 * Check if a `<table>` node is a DataTable table already or not.
	 *
	 *  @param {node|jquery|string} table Table node, jQuery object or jQuery
	 *      selector for the table to test. Note that if more than more than one
	 *      table is passed on, only the first will be checked
	 *  @returns {boolean} true the table given is a DataTable, or false otherwise
	 *  @static
	 *  @dtopt API-Static
	 *
	 *  @example
	 *    if ( ! $.fn.DataTable.isDataTable( '#example' ) ) {
	 *      $('#example').dataTable();
	 *    }
	 */
	DataTable.isDataTable = DataTable.fnIsDataTable = function ( table )
	{
		var t = $(table).get(0);
		var is = false;
	
		$.each( DataTable.settings, function (i, o) {
			var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null;
			var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null;
	
			if ( o.nTable === t || head === t || foot === t ) {
				is = true;
			}
		} );
	
		return is;
	};
	
	
	/**
	 * Get all DataTable tables that have been initialised - optionally you can
	 * select to get only currently visible tables.
	 *
	 *  @param {boolean} [visible=false] Flag to indicate if you want all (default)
	 *    or visible tables only.
	 *  @returns {array} Array of `table` nodes (not DataTable instances) which are
	 *    DataTables
	 *  @static
	 *  @dtopt API-Static
	 *
	 *  @example
	 *    $.each( $.fn.dataTable.tables(true), function () {
	 *      $(table).DataTable().columns.adjust();
	 *    } );
	 */
	DataTable.tables = DataTable.fnTables = function ( visible )
	{
		var api = false;
	
		if ( $.isPlainObject( visible ) ) {
			api = visible.api;
			visible = visible.visible;
		}
	
		var a = $.map( DataTable.settings, function (o) {
			if ( !visible || (visible && $(o.nTable).is(':visible')) ) {
				return o.nTable;
			}
		} );
	
		return api ?
			new _Api( a ) :
			a;
	};
	
	
	/**
	 * DataTables utility methods
	 * 
	 * This namespace provides helper methods that DataTables uses internally to
	 * create a DataTable, but which are not exclusively used only for DataTables.
	 * These methods can be used by extension authors to save the duplication of
	 * code.
	 *
	 *  @namespace
	 */
	DataTable.util = {
		/**
		 * Throttle the calls to a function. Arguments and context are maintained
		 * for the throttled function.
		 *
		 * @param {function} fn Function to be called
		 * @param {integer} freq Call frequency in mS
		 * @return {function} Wrapped function
		 */
		throttle: _fnThrottle,
	
	
		/**
		 * Escape a string such that it can be used in a regular expression
		 *
		 *  @param {string} sVal string to escape
		 *  @returns {string} escaped string
		 */
		escapeRegex: _fnEscapeRegex
	};
	
	
	/**
	 * Convert from camel case parameters to Hungarian notation. This is made public
	 * for the extensions to provide the same ability as DataTables core to accept
	 * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase
	 * parameters.
	 *
	 *  @param {object} src The model object which holds all parameters that can be
	 *    mapped.
	 *  @param {object} user The object to convert from camel case to Hungarian.
	 *  @param {boolean} force When set to `true`, properties which already have a
	 *    Hungarian value in the `user` object will be overwritten. Otherwise they
	 *    won't be.
	 */
	DataTable.camelToHungarian = _fnCamelToHungarian;
	
	
	
	/**
	 *
	 */
	_api_register( '$()', function ( selector, opts ) {
		var
			rows   = this.rows( opts ).nodes(), // Get all rows
			jqRows = $(rows);
	
		return $( [].concat(
			jqRows.filter( selector ).toArray(),
			jqRows.find( selector ).toArray()
		) );
	} );
	
	
	// jQuery functions to operate on the tables
	$.each( [ 'on', 'one', 'off' ], function (i, key) {
		_api_register( key+'()', function ( /* event, handler */ ) {
			var args = Array.prototype.slice.call(arguments);
	
			// Add the `dt` namespace automatically if it isn't already present
			if ( ! args[0].match(/\.dt\b/) ) {
				args[0] += '.dt';
			}
	
			var inst = $( this.tables().nodes() );
			inst[key].apply( inst, args );
			return this;
		} );
	} );
	
	
	_api_register( 'clear()', function () {
		return this.iterator( 'table', function ( settings ) {
			_fnClearTable( settings );
		} );
	} );
	
	
	_api_register( 'settings()', function () {
		return new _Api( this.context, this.context );
	} );
	
	
	_api_register( 'init()', function () {
		var ctx = this.context;
		return ctx.length ? ctx[0].oInit : null;
	} );
	
	
	_api_register( 'data()', function () {
		return this.iterator( 'table', function ( settings ) {
			return _pluck( settings.aoData, '_aData' );
		} ).flatten();
	} );
	
	
	_api_register( 'destroy()', function ( remove ) {
		remove = remove || false;
	
		return this.iterator( 'table', function ( settings ) {
			var orig      = settings.nTableWrapper.parentNode;
			var classes   = settings.oClasses;
			var table     = settings.nTable;
			var tbody     = settings.nTBody;
			var thead     = settings.nTHead;
			var tfoot     = settings.nTFoot;
			var jqTable   = $(table);
			var jqTbody   = $(tbody);
			var jqWrapper = $(settings.nTableWrapper);
			var rows      = $.map( settings.aoData, function (r) { return r.nTr; } );
			var i, ien;
	
			// Flag to note that the table is currently being destroyed - no action
			// should be taken
			settings.bDestroying = true;
	
			// Fire off the destroy callbacks for plug-ins etc
			_fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings] );
	
			// If not being removed from the document, make all columns visible
			if ( ! remove ) {
				new _Api( settings ).columns().visible( true );
			}
	
			// Blitz all `DT` namespaced events (these are internal events, the
			// lowercase, `dt` events are user subscribed and they are responsible
			// for removing them
			jqWrapper.unbind('.DT').find(':not(tbody *)').unbind('.DT');
			$(window).unbind('.DT-'+settings.sInstance);
	
			// When scrolling we had to break the table up - restore it
			if ( table != thead.parentNode ) {
				jqTable.children('thead').detach();
				jqTable.append( thead );
			}
	
			if ( tfoot && table != tfoot.parentNode ) {
				jqTable.children('tfoot').detach();
				jqTable.append( tfoot );
			}
	
			settings.aaSorting = [];
			settings.aaSortingFixed = [];
			_fnSortingClasses( settings );
	
			$( rows ).removeClass( settings.asStripeClasses.join(' ') );
	
			$('th, td', thead).removeClass( classes.sSortable+' '+
				classes.sSortableAsc+' '+classes.sSortableDesc+' '+classes.sSortableNone
			);
	
			if ( settings.bJUI ) {
				$('th span.'+classes.sSortIcon+ ', td span.'+classes.sSortIcon, thead).detach();
				$('th, td', thead).each( function () {
					var wrapper = $('div.'+classes.sSortJUIWrapper, this);
					$(this).append( wrapper.contents() );
					wrapper.detach();
				} );
			}
	
			// Add the TR elements back into the table in their original order
			jqTbody.children().detach();
			jqTbody.append( rows );
	
			// Remove the DataTables generated nodes, events and classes
			var removedMethod = remove ? 'remove' : 'detach';
			jqTable[ removedMethod ]();
			jqWrapper[ removedMethod ]();
	
			// If we need to reattach the table to the document
			if ( ! remove && orig ) {
				// insertBefore acts like appendChild if !arg[1]
				orig.insertBefore( table, settings.nTableReinsertBefore );
	
				// Restore the width of the original table - was read from the style property,
				// so we can restore directly to that
				jqTable
					.css( 'width', settings.sDestroyWidth )
					.removeClass( classes.sTable );
	
				// If the were originally stripe classes - then we add them back here.
				// Note this is not fool proof (for example if not all rows had stripe
				// classes - but it's a good effort without getting carried away
				ien = settings.asDestroyStripes.length;
	
				if ( ien ) {
					jqTbody.children().each( function (i) {
						$(this).addClass( settings.asDestroyStripes[i % ien] );
					} );
				}
			}
	
			/* Remove the settings object from the settings array */
			var idx = $.inArray( settings, DataTable.settings );
			if ( idx !== -1 ) {
				DataTable.settings.splice( idx, 1 );
			}
		} );
	} );
	
	
	// Add the `every()` method for rows, columns and cells in a compact form
	$.each( [ 'column', 'row', 'cell' ], function ( i, type ) {
		_api_register( type+'s().every()', function ( fn ) {
			var opts = this.selector.opts;
			var api = this;
	
			return this.iterator( type, function ( settings, arg1, arg2, arg3, arg4 ) {
				// Rows and columns:
				//  arg1 - index
				//  arg2 - table counter
				//  arg3 - loop counter
				//  arg4 - undefined
				// Cells:
				//  arg1 - row index
				//  arg2 - column index
				//  arg3 - table counter
				//  arg4 - loop counter
				fn.call(
					api[ type ](
						arg1,
						type==='cell' ? arg2 : opts,
						type==='cell' ? opts : undefined
					),
					arg1, arg2, arg3, arg4
				);
			} );
		} );
	} );
	
	
	// i18n method for extensions to be able to use the language object from the
	// DataTable
	_api_register( 'i18n()', function ( token, def, plural ) {
		var ctx = this.context[0];
		var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage );
	
		if ( resolved === undefined ) {
			resolved = def;
		}
	
		if ( plural !== undefined && $.isPlainObject( resolved ) ) {
			resolved = resolved[ plural ] !== undefined ?
				resolved[ plural ] :
				resolved._;
		}
	
		return resolved.replace( '%d', plural ); // nb: plural might be undefined,
	} );

	/**
	 * Version string for plug-ins to check compatibility. Allowed format is
	 * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used
	 * only for non-release builds. See http://semver.org/ for more information.
	 *  @member
	 *  @type string
	 *  @default Version number
	 */
	DataTable.version = "1.10.10";

	/**
	 * Private data store, containing all of the settings objects that are
	 * created for the tables on a given page.
	 *
	 * Note that the `DataTable.settings` object is aliased to
	 * `jQuery.fn.dataTableExt` through which it may be accessed and
	 * manipulated, or `jQuery.fn.dataTable.settings`.
	 *  @member
	 *  @type array
	 *  @default []
	 *  @private
	 */
	DataTable.settings = [];

	/**
	 * Object models container, for the various models that DataTables has
	 * available to it. These models define the objects that are used to hold
	 * the active state and configuration of the table.
	 *  @namespace
	 */
	DataTable.models = {};
	
	
	
	/**
	 * Template object for the way in which DataTables holds information about
	 * search information for the global filter and individual column filters.
	 *  @namespace
	 */
	DataTable.models.oSearch = {
		/**
		 * Flag to indicate if the filtering should be case insensitive or not
		 *  @type boolean
		 *  @default true
		 */
		"bCaseInsensitive": true,
	
		/**
		 * Applied search term
		 *  @type string
		 *  @default <i>Empty string</i>
		 */
		"sSearch": "",
	
		/**
		 * Flag to indicate if the search term should be interpreted as a
		 * regular expression (true) or not (false) and therefore and special
		 * regex characters escaped.
		 *  @type boolean
		 *  @default false
		 */
		"bRegex": false,
	
		/**
		 * Flag to indicate if DataTables is to use its smart filtering or not.
		 *  @type boolean
		 *  @default true
		 */
		"bSmart": true
	};
	
	
	
	
	/**
	 * Template object for the way in which DataTables holds information about
	 * each individual row. This is the object format used for the settings
	 * aoData array.
	 *  @namespace
	 */
	DataTable.models.oRow = {
		/**
		 * TR element for the row
		 *  @type node
		 *  @default null
		 */
		"nTr": null,
	
		/**
		 * Array of TD elements for each row. This is null until the row has been
		 * created.
		 *  @type array nodes
		 *  @default []
		 */
		"anCells": null,
	
		/**
		 * Data object from the original data source for the row. This is either
		 * an array if using the traditional form of DataTables, or an object if
		 * using mData options. The exact type will depend on the passed in
		 * data from the data source, or will be an array if using DOM a data
		 * source.
		 *  @type array|object
		 *  @default []
		 */
		"_aData": [],
	
		/**
		 * Sorting data cache - this array is ostensibly the same length as the
		 * number of columns (although each index is generated only as it is
		 * needed), and holds the data that is used for sorting each column in the
		 * row. We do this cache generation at the start of the sort in order that
		 * the formatting of the sort data need be done only once for each cell
		 * per sort. This array should not be read from or written to by anything
		 * other than the master sorting methods.
		 *  @type array
		 *  @default null
		 *  @private
		 */
		"_aSortData": null,
	
		/**
		 * Per cell filtering data cache. As per the sort data cache, used to
		 * increase the performance of the filtering in DataTables
		 *  @type array
		 *  @default null
		 *  @private
		 */
		"_aFilterData": null,
	
		/**
		 * Filtering data cache. This is the same as the cell filtering cache, but
		 * in this case a string rather than an array. This is easily computed with
		 * a join on `_aFilterData`, but is provided as a cache so the join isn't
		 * needed on every search (memory traded for performance)
		 *  @type array
		 *  @default null
		 *  @private
		 */
		"_sFilterRow": null,
	
		/**
		 * Cache of the class name that DataTables has applied to the row, so we
		 * can quickly look at this variable rather than needing to do a DOM check
		 * on className for the nTr property.
		 *  @type string
		 *  @default <i>Empty string</i>
		 *  @private
		 */
		"_sRowStripe": "",
	
		/**
		 * Denote if the original data source was from the DOM, or the data source
		 * object. This is used for invalidating data, so DataTables can
		 * automatically read data from the original source, unless uninstructed
		 * otherwise.
		 *  @type string
		 *  @default null
		 *  @private
		 */
		"src": null,
	
		/**
		 * Index in the aoData array. This saves an indexOf lookup when we have the
		 * object, but want to know the index
		 *  @type integer
		 *  @default -1
		 *  @private
		 */
		"idx": -1
	};
	
	
	/**
	 * Template object for the column information object in DataTables. This object
	 * is held in the settings aoColumns array and contains all the information that
	 * DataTables needs about each individual column.
	 *
	 * Note that this object is related to {@link DataTable.defaults.column}
	 * but this one is the internal data store for DataTables's cache of columns.
	 * It should NOT be manipulated outside of DataTables. Any configuration should
	 * be done through the initialisation options.
	 *  @namespace
	 */
	DataTable.models.oColumn = {
		/**
		 * Column index. This could be worked out on-the-fly with $.inArray, but it
		 * is faster to just hold it as a variable
		 *  @type integer
		 *  @default null
		 */
		"idx": null,
	
		/**
		 * A list of the columns that sorting should occur on when this column
		 * is sorted. That this property is an array allows multi-column sorting
		 * to be defined for a column (for example first name / last name columns
		 * would benefit from this). The values are integers pointing to the
		 * columns to be sorted on (typically it will be a single integer pointing
		 * at itself, but that doesn't need to be the case).
		 *  @type array
		 */
		"aDataSort": null,
	
		/**
		 * Define the sorting directions that are applied to the column, in sequence
		 * as the column is repeatedly sorted upon - i.e. the first value is used
		 * as the sorting direction when the column if first sorted (clicked on).
		 * Sort it again (click again) and it will move on to the next index.
		 * Repeat until loop.
		 *  @type array
		 */
		"asSorting": null,
	
		/**
		 * Flag to indicate if the column is searchable, and thus should be included
		 * in the filtering or not.
		 *  @type boolean
		 */
		"bSearchable": null,
	
		/**
		 * Flag to indicate if the column is sortable or not.
		 *  @type boolean
		 */
		"bSortable": null,
	
		/**
		 * Flag to indicate if the column is currently visible in the table or not
		 *  @type boolean
		 */
		"bVisible": null,
	
		/**
		 * Store for manual type assignment using the `column.type` option. This
		 * is held in store so we can manipulate the column's `sType` property.
		 *  @type string
		 *  @default null
		 *  @private
		 */
		"_sManualType": null,
	
		/**
		 * Flag to indicate if HTML5 data attributes should be used as the data
		 * source for filtering or sorting. True is either are.
		 *  @type boolean
		 *  @default false
		 *  @private
		 */
		"_bAttrSrc": false,
	
		/**
		 * Developer definable function that is called whenever a cell is created (Ajax source,
		 * etc) or processed for input (DOM source). This can be used as a compliment to mRender
		 * allowing you to modify the DOM element (add background colour for example) when the
		 * element is available.
		 *  @type function
		 *  @param {element} nTd The TD node that has been created
		 *  @param {*} sData The Data for the cell
		 *  @param {array|object} oData The data for the whole row
		 *  @param {int} iRow The row index for the aoData data store
		 *  @default null
		 */
		"fnCreatedCell": null,
	
		/**
		 * Function to get data from a cell in a column. You should <b>never</b>
		 * access data directly through _aData internally in DataTables - always use
		 * the method attached to this property. It allows mData to function as
		 * required. This function is automatically assigned by the column
		 * initialisation method
		 *  @type function
		 *  @param {array|object} oData The data array/object for the array
		 *    (i.e. aoData[]._aData)
		 *  @param {string} sSpecific The specific data type you want to get -
		 *    'display', 'type' 'filter' 'sort'
		 *  @returns {*} The data for the cell from the given row's data
		 *  @default null
		 */
		"fnGetData": null,
	
		/**
		 * Function to set data for a cell in the column. You should <b>never</b>
		 * set the data directly to _aData internally in DataTables - always use
		 * this method. It allows mData to function as required. This function
		 * is automatically assigned by the column initialisation method
		 *  @type function
		 *  @param {array|object} oData The data array/object for the array
		 *    (i.e. aoData[]._aData)
		 *  @param {*} sValue Value to set
		 *  @default null
		 */
		"fnSetData": null,
	
		/**
		 * Property to read the value for the cells in the column from the data
		 * source array / object. If null, then the default content is used, if a
		 * function is given then the return from the function is used.
		 *  @type function|int|string|null
		 *  @default null
		 */
		"mData": null,
	
		/**
		 * Partner property to mData which is used (only when defined) to get
		 * the data - i.e. it is basically the same as mData, but without the
		 * 'set' option, and also the data fed to it is the result from mData.
		 * This is the rendering method to match the data method of mData.
		 *  @type function|int|string|null
		 *  @default null
		 */
		"mRender": null,
	
		/**
		 * Unique header TH/TD element for this column - this is what the sorting
		 * listener is attached to (if sorting is enabled.)
		 *  @type node
		 *  @default null
		 */
		"nTh": null,
	
		/**
		 * Unique footer TH/TD element for this column (if there is one). Not used
		 * in DataTables as such, but can be used for plug-ins to reference the
		 * footer for each column.
		 *  @type node
		 *  @default null
		 */
		"nTf": null,
	
		/**
		 * The class to apply to all TD elements in the table's TBODY for the column
		 *  @type string
		 *  @default null
		 */
		"sClass": null,
	
		/**
		 * When DataTables calculates the column widths to assign to each column,
		 * it finds the longest string in each column and then constructs a
		 * temporary table and reads the widths from that. The problem with this
		 * is that "mmm" is much wider then "iiii", but the latter is a longer
		 * string - thus the calculation can go wrong (doing it properly and putting
		 * it into an DOM object and measuring that is horribly(!) slow). Thus as
		 * a "work around" we provide this option. It will append its value to the
		 * text that is found to be the longest string for the column - i.e. padding.
		 *  @type string
		 */
		"sContentPadding": null,
	
		/**
		 * Allows a default value to be given for a column's data, and will be used
		 * whenever a null data source is encountered (this can be because mData
		 * is set to null, or because the data source itself is null).
		 *  @type string
		 *  @default null
		 */
		"sDefaultContent": null,
	
		/**
		 * Name for the column, allowing reference to the column by name as well as
		 * by index (needs a lookup to work by name).
		 *  @type string
		 */
		"sName": null,
	
		/**
		 * Custom sorting data type - defines which of the available plug-ins in
		 * afnSortData the custom sorting will use - if any is defined.
		 *  @type string
		 *  @default std
		 */
		"sSortDataType": 'std',
	
		/**
		 * Class to be applied to the header element when sorting on this column
		 *  @type string
		 *  @default null
		 */
		"sSortingClass": null,
	
		/**
		 * Class to be applied to the header element when sorting on this column -
		 * when jQuery UI theming is used.
		 *  @type string
		 *  @default null
		 */
		"sSortingClassJUI": null,
	
		/**
		 * Title of the column - what is seen in the TH element (nTh).
		 *  @type string
		 */
		"sTitle": null,
	
		/**
		 * Column sorting and filtering type
		 *  @type string
		 *  @default null
		 */
		"sType": null,
	
		/**
		 * Width of the column
		 *  @type string
		 *  @default null
		 */
		"sWidth": null,
	
		/**
		 * Width of the column when it was first "encountered"
		 *  @type string
		 *  @default null
		 */
		"sWidthOrig": null
	};
	
	
	/*
	 * Developer note: The properties of the object below are given in Hungarian
	 * notation, that was used as the interface for DataTables prior to v1.10, however
	 * from v1.10 onwards the primary interface is camel case. In order to avoid
	 * breaking backwards compatibility utterly with this change, the Hungarian
	 * version is still, internally the primary interface, but is is not documented
	 * - hence the @name tags in each doc comment. This allows a Javascript function
	 * to create a map from Hungarian notation to camel case (going the other direction
	 * would require each property to be listed, which would at around 3K to the size
	 * of DataTables, while this method is about a 0.5K hit.
	 *
	 * Ultimately this does pave the way for Hungarian notation to be dropped
	 * completely, but that is a massive amount of work and will break current
	 * installs (therefore is on-hold until v2).
	 */
	
	/**
	 * Initialisation options that can be given to DataTables at initialisation
	 * time.
	 *  @namespace
	 */
	DataTable.defaults = {
		/**
		 * An array of data to use for the table, passed in at initialisation which
		 * will be used in preference to any data which is already in the DOM. This is
		 * particularly useful for constructing tables purely in Javascript, for
		 * example with a custom Ajax call.
		 *  @type array
		 *  @default null
		 *
		 *  @dtopt Option
		 *  @name DataTable.defaults.data
		 *
		 *  @example
		 *    // Using a 2D array data source
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "data": [
		 *          ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'],
		 *          ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'],
		 *        ],
		 *        "columns": [
		 *          { "title": "Engine" },
		 *          { "title": "Browser" },
		 *          { "title": "Platform" },
		 *          { "title": "Version" },
		 *          { "title": "Grade" }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using an array of objects as a data source (`data`)
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "data": [
		 *          {
		 *            "engine":   "Trident",
		 *            "browser":  "Internet Explorer 4.0",
		 *            "platform": "Win 95+",
		 *            "version":  4,
		 *            "grade":    "X"
		 *          },
		 *          {
		 *            "engine":   "Trident",
		 *            "browser":  "Internet Explorer 5.0",
		 *            "platform": "Win 95+",
		 *            "version":  5,
		 *            "grade":    "C"
		 *          }
		 *        ],
		 *        "columns": [
		 *          { "title": "Engine",   "data": "engine" },
		 *          { "title": "Browser",  "data": "browser" },
		 *          { "title": "Platform", "data": "platform" },
		 *          { "title": "Version",  "data": "version" },
		 *          { "title": "Grade",    "data": "grade" }
		 *        ]
		 *      } );
		 *    } );
		 */
		"aaData": null,
	
	
		/**
		 * If ordering is enabled, then DataTables will perform a first pass sort on
		 * initialisation. You can define which column(s) the sort is performed
		 * upon, and the sorting direction, with this variable. The `sorting` array
		 * should contain an array for each column to be sorted initially containing
		 * the column's index and a direction string ('asc' or 'desc').
		 *  @type array
		 *  @default [[0,'asc']]
		 *
		 *  @dtopt Option
		 *  @name DataTable.defaults.order
		 *
		 *  @example
		 *    // Sort by 3rd column first, and then 4th column
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "order": [[2,'asc'], [3,'desc']]
		 *      } );
		 *    } );
		 *
		 *    // No initial sorting
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "order": []
		 *      } );
		 *    } );
		 */
		"aaSorting": [[0,'asc']],
	
	
		/**
		 * This parameter is basically identical to the `sorting` parameter, but
		 * cannot be overridden by user interaction with the table. What this means
		 * is that you could have a column (visible or hidden) which the sorting
		 * will always be forced on first - any sorting after that (from the user)
		 * will then be performed as required. This can be useful for grouping rows
		 * together.
		 *  @type array
		 *  @default null
		 *
		 *  @dtopt Option
		 *  @name DataTable.defaults.orderFixed
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "orderFixed": [[0,'asc']]
		 *      } );
		 *    } )
		 */
		"aaSortingFixed": [],
	
	
		/**
		 * DataTables can be instructed to load data to display in the table from a
		 * Ajax source. This option defines how that Ajax call is made and where to.
		 *
		 * The `ajax` property has three different modes of operation, depending on
		 * how it is defined. These are:
		 *
		 * * `string` - Set the URL from where the data should be loaded from.
		 * * `object` - Define properties for `jQuery.ajax`.
		 * * `function` - Custom data get function
		 *
		 * `string`
		 * --------
		 *
		 * As a string, the `ajax` property simply defines the URL from which
		 * DataTables will load data.
		 *
		 * `object`
		 * --------
		 *
		 * As an object, the parameters in the object are passed to
		 * [jQuery.ajax](http://api.jquery.com/jQuery.ajax/) allowing fine control
		 * of the Ajax request. DataTables has a number of default parameters which
		 * you can override using this option. Please refer to the jQuery
		 * documentation for a full description of the options available, although
		 * the following parameters provide additional options in DataTables or
		 * require special consideration:
		 *
		 * * `data` - As with jQuery, `data` can be provided as an object, but it
		 *   can also be used as a function to manipulate the data DataTables sends
		 *   to the server. The function takes a single parameter, an object of
		 *   parameters with the values that DataTables has readied for sending. An
		 *   object may be returned which will be merged into the DataTables
		 *   defaults, or you can add the items to the object that was passed in and
		 *   not return anything from the function. This supersedes `fnServerParams`
		 *   from DataTables 1.9-.
		 *
		 * * `dataSrc` - By default DataTables will look for the property `data` (or
		 *   `aaData` for compatibility with DataTables 1.9-) when obtaining data
		 *   from an Ajax source or for server-side processing - this parameter
		 *   allows that property to be changed. You can use Javascript dotted
		 *   object notation to get a data source for multiple levels of nesting, or
		 *   it my be used as a function. As a function it takes a single parameter,
		 *   the JSON returned from the server, which can be manipulated as
		 *   required, with the returned value being that used by DataTables as the
		 *   data source for the table. This supersedes `sAjaxDataProp` from
		 *   DataTables 1.9-.
		 *
		 * * `success` - Should not be overridden it is used internally in
		 *   DataTables. To manipulate / transform the data returned by the server
		 *   use `ajax.dataSrc`, or use `ajax` as a function (see below).
		 *
		 * `function`
		 * ----------
		 *
		 * As a function, making the Ajax call is left up to yourself allowing
		 * complete control of the Ajax request. Indeed, if desired, a method other
		 * than Ajax could be used to obtain the required data, such as Web storage
		 * or an AIR database.
		 *
		 * The function is given four parameters and no return is required. The
		 * parameters are:
		 *
		 * 1. _object_ - Data to send to the server
		 * 2. _function_ - Callback function that must be executed when the required
		 *    data has been obtained. That data should be passed into the callback
		 *    as the only parameter
		 * 3. _object_ - DataTables settings object for the table
		 *
		 * Note that this supersedes `fnServerData` from DataTables 1.9-.
		 *
		 *  @type string|object|function
		 *  @default null
		 *
		 *  @dtopt Option
		 *  @name DataTable.defaults.ajax
		 *  @since 1.10.0
		 *
		 * @example
		 *   // Get JSON data from a file via Ajax.
		 *   // Note DataTables expects data in the form `{ data: [ ...data... ] }` by default).
		 *   $('#example').dataTable( {
		 *     "ajax": "data.json"
		 *   } );
		 *
		 * @example
		 *   // Get JSON data from a file via Ajax, using `dataSrc` to change
		 *   // `data` to `tableData` (i.e. `{ tableData: [ ...data... ] }`)
		 *   $('#example').dataTable( {
		 *     "ajax": {
		 *       "url": "data.json",
		 *       "dataSrc": "tableData"
		 *     }
		 *   } );
		 *
		 * @example
		 *   // Get JSON data from a file via Ajax, using `dataSrc` to read data
		 *   // from a plain array rather than an array in an object
		 *   $('#example').dataTable( {
		 *     "ajax": {
		 *       "url": "data.json",
		 *       "dataSrc": ""
		 *     }
		 *   } );
		 *
		 * @example
		 *   // Manipulate the data returned from the server - add a link to data
		 *   // (note this can, should, be done using `render` for the column - this
		 *   // is just a simple example of how the data can be manipulated).
		 *   $('#example').dataTable( {
		 *     "ajax": {
		 *       "url": "data.json",
		 *       "dataSrc": function ( json ) {
		 *         for ( var i=0, ien=json.length ; i<ien ; i++ ) {
		 *           json[i][0] = '<a href="/message/'+json[i][0]+'>View message</a>';
		 *         }
		 *         return json;
		 *       }
		 *     }
		 *   } );
		 *
		 * @example
		 *   // Add data to the request
		 *   $('#example').dataTable( {
		 *     "ajax": {
		 *       "url": "data.json",
		 *       "data": function ( d ) {
		 *         return {
		 *           "extra_search": $('#extra').val()
		 *         };
		 *       }
		 *     }
		 *   } );
		 *
		 * @example
		 *   // Send request as POST
		 *   $('#example').dataTable( {
		 *     "ajax": {
		 *       "url": "data.json",
		 *       "type": "POST"
		 *     }
		 *   } );
		 *
		 * @example
		 *   // Get the data from localStorage (could interface with a form for
		 *   // adding, editing and removing rows).
		 *   $('#example').dataTable( {
		 *     "ajax": function (data, callback, settings) {
		 *       callback(
		 *         JSON.parse( localStorage.getItem('dataTablesData') )
		 *       );
		 *     }
		 *   } );
		 */
		"ajax": null,
	
	
		/**
		 * This parameter allows you to readily specify the entries in the length drop
		 * down menu that DataTables shows when pagination is enabled. It can be
		 * either a 1D array of options which will be used for both the displayed
		 * option and the value, or a 2D array which will use the array in the first
		 * position as the value, and the array in the second position as the
		 * displayed options (useful for language strings such as 'All').
		 *
		 * Note that the `pageLength` property will be automatically set to the
		 * first value given in this array, unless `pageLength` is also provided.
		 *  @type array
		 *  @default [ 10, 25, 50, 100 ]
		 *
		 *  @dtopt Option
		 *  @name DataTable.defaults.lengthMenu
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]]
		 *      } );
		 *    } );
		 */
		"aLengthMenu": [ 10, 25, 50, 100 ],
	
	
		/**
		 * The `columns` option in the initialisation parameter allows you to define
		 * details about the way individual columns behave. For a full list of
		 * column options that can be set, please see
		 * {@link DataTable.defaults.column}. Note that if you use `columns` to
		 * define your columns, you must have an entry in the array for every single
		 * column that you have in your table (these can be null if you don't which
		 * to specify any options).
		 *  @member
		 *
		 *  @name DataTable.defaults.column
		 */
		"aoColumns": null,
	
		/**
		 * Very similar to `columns`, `columnDefs` allows you to target a specific
		 * column, multiple columns, or all columns, using the `targets` property of
		 * each object in the array. This allows great flexibility when creating
		 * tables, as the `columnDefs` arrays can be of any length, targeting the
		 * columns you specifically want. `columnDefs` may use any of the column
		 * options available: {@link DataTable.defaults.column}, but it _must_
		 * have `targets` defined in each object in the array. Values in the `targets`
		 * array may be:
		 *   <ul>
		 *     <li>a string - class name will be matched on the TH for the column</li>
		 *     <li>0 or a positive integer - column index counting from the left</li>
		 *     <li>a negative integer - column index counting from the right</li>
		 *     <li>the string "_all" - all columns (i.e. assign a default)</li>
		 *   </ul>
		 *  @member
		 *
		 *  @name DataTable.defaults.columnDefs
		 */
		"aoColumnDefs": null,
	
	
		/**
		 * Basically the same as `search`, this parameter defines the individual column
		 * filtering state at initialisation time. The array must be of the same size
		 * as the number of columns, and each element be an object with the parameters
		 * `search` and `escapeRegex` (the latter is optional). 'null' is also
		 * accepted and the default will be used.
		 *  @type array
		 *  @default []
		 *
		 *  @dtopt Option
		 *  @name DataTable.defaults.searchCols
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "searchCols": [
		 *          null,
		 *          { "search": "My filter" },
		 *          null,
		 *          { "search": "^[0-9]", "escapeRegex": false }
		 *        ]
		 *      } );
		 *    } )
		 */
		"aoSearchCols": [],
	
	
		/**
		 * An array of CSS classes that should be applied to displayed rows. This
		 * array may be of any length, and DataTables will apply each class
		 * sequentially, looping when required.
		 *  @type array
		 *  @default null <i>Will take the values determined by the `oClasses.stripe*`
		 *    options</i>
		 *
		 *  @dtopt Option
		 *  @name DataTable.defaults.stripeClasses
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stripeClasses": [ 'strip1', 'strip2', 'strip3' ]
		 *      } );
		 *    } )
		 */
		"asStripeClasses": null,
	
	
		/**
		 * Enable or disable automatic column width calculation. This can be disabled
		 * as an optimisation (it takes some time to calculate the widths) if the
		 * tables widths are passed in using `columns`.
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.autoWidth
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "autoWidth": false
		 *      } );
		 *    } );
		 */
		"bAutoWidth": true,
	
	
		/**
		 * Deferred rendering can provide DataTables with a huge speed boost when you
		 * are using an Ajax or JS data source for the table. This option, when set to
		 * true, will cause DataTables to defer the creation of the table elements for
		 * each row until they are needed for a draw - saving a significant amount of
		 * time.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.deferRender
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "ajax": "sources/arrays.txt",
		 *        "deferRender": true
		 *      } );
		 *    } );
		 */
		"bDeferRender": false,
	
	
		/**
		 * Replace a DataTable which matches the given selector and replace it with
		 * one which has the properties of the new initialisation object passed. If no
		 * table matches the selector, then the new DataTable will be constructed as
		 * per normal.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.destroy
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "srollY": "200px",
		 *        "paginate": false
		 *      } );
		 *
		 *      // Some time later....
		 *      $('#example').dataTable( {
		 *        "filter": false,
		 *        "destroy": true
		 *      } );
		 *    } );
		 */
		"bDestroy": false,
	
	
		/**
		 * Enable or disable filtering of data. Filtering in DataTables is "smart" in
		 * that it allows the end user to input multiple words (space separated) and
		 * will match a row containing those words, even if not in the order that was
		 * specified (this allow matching across multiple columns). Note that if you
		 * wish to use filtering in DataTables this must remain 'true' - to remove the
		 * default filtering input box and retain filtering abilities, please use
		 * {@link DataTable.defaults.dom}.
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.searching
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "searching": false
		 *      } );
		 *    } );
		 */
		"bFilter": true,
	
	
		/**
		 * Enable or disable the table information display. This shows information
		 * about the data that is currently visible on the page, including information
		 * about filtered data if that action is being performed.
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.info
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "info": false
		 *      } );
		 *    } );
		 */
		"bInfo": true,
	
	
		/**
		 * Enable jQuery UI ThemeRoller support (required as ThemeRoller requires some
		 * slightly different and additional mark-up from what DataTables has
		 * traditionally used).
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.jQueryUI
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "jQueryUI": true
		 *      } );
		 *    } );
		 */
		"bJQueryUI": false,
	
	
		/**
		 * Allows the end user to select the size of a formatted page from a select
		 * menu (sizes are 10, 25, 50 and 100). Requires pagination (`paginate`).
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.lengthChange
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "lengthChange": false
		 *      } );
		 *    } );
		 */
		"bLengthChange": true,
	
	
		/**
		 * Enable or disable pagination.
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.paging
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "paging": false
		 *      } );
		 *    } );
		 */
		"bPaginate": true,
	
	
		/**
		 * Enable or disable the display of a 'processing' indicator when the table is
		 * being processed (e.g. a sort). This is particularly useful for tables with
		 * large amounts of data where it can take a noticeable amount of time to sort
		 * the entries.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.processing
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "processing": true
		 *      } );
		 *    } );
		 */
		"bProcessing": false,
	
	
		/**
		 * Retrieve the DataTables object for the given selector. Note that if the
		 * table has already been initialised, this parameter will cause DataTables
		 * to simply return the object that has already been set up - it will not take
		 * account of any changes you might have made to the initialisation object
		 * passed to DataTables (setting this parameter to true is an acknowledgement
		 * that you understand this). `destroy` can be used to reinitialise a table if
		 * you need.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.retrieve
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      initTable();
		 *      tableActions();
		 *    } );
		 *
		 *    function initTable ()
		 *    {
		 *      return $('#example').dataTable( {
		 *        "scrollY": "200px",
		 *        "paginate": false,
		 *        "retrieve": true
		 *      } );
		 *    }
		 *
		 *    function tableActions ()
		 *    {
		 *      var table = initTable();
		 *      // perform API operations with oTable
		 *    }
		 */
		"bRetrieve": false,
	
	
		/**
		 * When vertical (y) scrolling is enabled, DataTables will force the height of
		 * the table's viewport to the given height at all times (useful for layout).
		 * However, this can look odd when filtering data down to a small data set,
		 * and the footer is left "floating" further down. This parameter (when
		 * enabled) will cause DataTables to collapse the table's viewport down when
		 * the result set will fit within the given Y height.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.scrollCollapse
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "scrollY": "200",
		 *        "scrollCollapse": true
		 *      } );
		 *    } );
		 */
		"bScrollCollapse": false,
	
	
		/**
		 * Configure DataTables to use server-side processing. Note that the
		 * `ajax` parameter must also be given in order to give DataTables a
		 * source to obtain the required data for each draw.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Features
		 *  @dtopt Server-side
		 *  @name DataTable.defaults.serverSide
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "serverSide": true,
		 *        "ajax": "xhr.php"
		 *      } );
		 *    } );
		 */
		"bServerSide": false,
	
	
		/**
		 * Enable or disable sorting of columns. Sorting of individual columns can be
		 * disabled by the `sortable` option for each column.
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.ordering
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "ordering": false
		 *      } );
		 *    } );
		 */
		"bSort": true,
	
	
		/**
		 * Enable or display DataTables' ability to sort multiple columns at the
		 * same time (activated by shift-click by the user).
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.orderMulti
		 *
		 *  @example
		 *    // Disable multiple column sorting ability
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "orderMulti": false
		 *      } );
		 *    } );
		 */
		"bSortMulti": true,
	
	
		/**
		 * Allows control over whether DataTables should use the top (true) unique
		 * cell that is found for a single column, or the bottom (false - default).
		 * This is useful when using complex headers.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.orderCellsTop
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "orderCellsTop": true
		 *      } );
		 *    } );
		 */
		"bSortCellsTop": false,
	
	
		/**
		 * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and
		 * `sorting\_3` to the columns which are currently being sorted on. This is
		 * presented as a feature switch as it can increase processing time (while
		 * classes are removed and added) so for large data sets you might want to
		 * turn this off.
		 *  @type boolean
		 *  @default true
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.orderClasses
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "orderClasses": false
		 *      } );
		 *    } );
		 */
		"bSortClasses": true,
	
	
		/**
		 * Enable or disable state saving. When enabled HTML5 `localStorage` will be
		 * used to save table display information such as pagination information,
		 * display length, filtering and sorting. As such when the end user reloads
		 * the page the display display will match what thy had previously set up.
		 *
		 * Due to the use of `localStorage` the default state saving is not supported
		 * in IE6 or 7. If state saving is required in those browsers, use
		 * `stateSaveCallback` to provide a storage solution such as cookies.
		 *  @type boolean
		 *  @default false
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.stateSave
		 *
		 *  @example
		 *    $(document).ready( function () {
		 *      $('#example').dataTable( {
		 *        "stateSave": true
		 *      } );
		 *    } );
		 */
		"bStateSave": false,
	
	
		/**
		 * This function is called when a TR element is created (and all TD child
		 * elements have been inserted), or registered if using a DOM source, allowing
		 * manipulation of the TR element (adding classes etc).
		 *  @type function
		 *  @param {node} row "TR" element for the current row
		 *  @param {array} data Raw data array for this row
		 *  @param {int} dataIndex The index of this row in the internal aoData array
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.createdRow
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "createdRow": function( row, data, dataIndex ) {
		 *          // Bold the grade for all 'A' grade browsers
		 *          if ( data[4] == "A" )
		 *          {
		 *            $('td:eq(4)', row).html( '<b>A</b>' );
		 *          }
		 *        }
		 *      } );
		 *    } );
		 */
		"fnCreatedRow": null,
	
	
		/**
		 * This function is called on every 'draw' event, and allows you to
		 * dynamically modify any aspect you want about the created DOM.
		 *  @type function
		 *  @param {object} settings DataTables settings object
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.drawCallback
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "drawCallback": function( settings ) {
		 *          alert( 'DataTables has redrawn the table' );
		 *        }
		 *      } );
		 *    } );
		 */
		"fnDrawCallback": null,
	
	
		/**
		 * Identical to fnHeaderCallback() but for the table footer this function
		 * allows you to modify the table footer on every 'draw' event.
		 *  @type function
		 *  @param {node} foot "TR" element for the footer
		 *  @param {array} data Full table data (as derived from the original HTML)
		 *  @param {int} start Index for the current display starting point in the
		 *    display array
		 *  @param {int} end Index for the current display ending point in the
		 *    display array
		 *  @param {array int} display Index array to translate the visual position
		 *    to the full data array
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.footerCallback
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "footerCallback": function( tfoot, data, start, end, display ) {
		 *          tfoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+start;
		 *        }
		 *      } );
		 *    } )
		 */
		"fnFooterCallback": null,
	
	
		/**
		 * When rendering large numbers in the information element for the table
		 * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers
		 * to have a comma separator for the 'thousands' units (e.g. 1 million is
		 * rendered as "1,000,000") to help readability for the end user. This
		 * function will override the default method DataTables uses.
		 *  @type function
		 *  @member
		 *  @param {int} toFormat number to be formatted
		 *  @returns {string} formatted string for DataTables to show the number
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.formatNumber
		 *
		 *  @example
		 *    // Format a number using a single quote for the separator (note that
		 *    // this can also be done with the language.thousands option)
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "formatNumber": function ( toFormat ) {
		 *          return toFormat.toString().replace(
		 *            /\B(?=(\d{3})+(?!\d))/g, "'"
		 *          );
		 *        };
		 *      } );
		 *    } );
		 */
		"fnFormatNumber": function ( toFormat ) {
			return toFormat.toString().replace(
				/\B(?=(\d{3})+(?!\d))/g,
				this.oLanguage.sThousands
			);
		},
	
	
		/**
		 * This function is called on every 'draw' event, and allows you to
		 * dynamically modify the header row. This can be used to calculate and
		 * display useful information about the table.
		 *  @type function
		 *  @param {node} head "TR" element for the header
		 *  @param {array} data Full table data (as derived from the original HTML)
		 *  @param {int} start Index for the current display starting point in the
		 *    display array
		 *  @param {int} end Index for the current display ending point in the
		 *    display array
		 *  @param {array int} display Index array to translate the visual position
		 *    to the full data array
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.headerCallback
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "fheaderCallback": function( head, data, start, end, display ) {
		 *          head.getElementsByTagName('th')[0].innerHTML = "Displaying "+(end-start)+" records";
		 *        }
		 *      } );
		 *    } )
		 */
		"fnHeaderCallback": null,
	
	
		/**
		 * The information element can be used to convey information about the current
		 * state of the table. Although the internationalisation options presented by
		 * DataTables are quite capable of dealing with most customisations, there may
		 * be times where you wish to customise the string further. This callback
		 * allows you to do exactly that.
		 *  @type function
		 *  @param {object} oSettings DataTables settings object
		 *  @param {int} start Starting position in data for the draw
		 *  @param {int} end End position in data for the draw
		 *  @param {int} max Total number of rows in the table (regardless of
		 *    filtering)
		 *  @param {int} total Total number of rows in the data set, after filtering
		 *  @param {string} pre The string that DataTables has formatted using it's
		 *    own rules
		 *  @returns {string} The string to be displayed in the information element.
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.infoCallback
		 *
		 *  @example
		 *    $('#example').dataTable( {
		 *      "infoCallback": function( settings, start, end, max, total, pre ) {
		 *        return start +" to "+ end;
		 *      }
		 *    } );
		 */
		"fnInfoCallback": null,
	
	
		/**
		 * Called when the table has been initialised. Normally DataTables will
		 * initialise sequentially and there will be no need for this function,
		 * however, this does not hold true when using external language information
		 * since that is obtained using an async XHR call.
		 *  @type function
		 *  @param {object} settings DataTables settings object
		 *  @param {object} json The JSON object request from the server - only
		 *    present if client-side Ajax sourced data is used
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.initComplete
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "initComplete": function(settings, json) {
		 *          alert( 'DataTables has finished its initialisation.' );
		 *        }
		 *      } );
		 *    } )
		 */
		"fnInitComplete": null,
	
	
		/**
		 * Called at the very start of each table draw and can be used to cancel the
		 * draw by returning false, any other return (including undefined) results in
		 * the full draw occurring).
		 *  @type function
		 *  @param {object} settings DataTables settings object
		 *  @returns {boolean} False will cancel the draw, anything else (including no
		 *    return) will allow it to complete.
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.preDrawCallback
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "preDrawCallback": function( settings ) {
		 *          if ( $('#test').val() == 1 ) {
		 *            return false;
		 *          }
		 *        }
		 *      } );
		 *    } );
		 */
		"fnPreDrawCallback": null,
	
	
		/**
		 * This function allows you to 'post process' each row after it have been
		 * generated for each table draw, but before it is rendered on screen. This
		 * function might be used for setting the row class name etc.
		 *  @type function
		 *  @param {node} row "TR" element for the current row
		 *  @param {array} data Raw data array for this row
		 *  @param {int} displayIndex The display index for the current table draw
		 *  @param {int} displayIndexFull The index of the data in the full list of
		 *    rows (after filtering)
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.rowCallback
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "rowCallback": function( row, data, displayIndex, displayIndexFull ) {
		 *          // Bold the grade for all 'A' grade browsers
		 *          if ( data[4] == "A" ) {
		 *            $('td:eq(4)', row).html( '<b>A</b>' );
		 *          }
		 *        }
		 *      } );
		 *    } );
		 */
		"fnRowCallback": null,
	
	
		/**
		 * __Deprecated__ The functionality provided by this parameter has now been
		 * superseded by that provided through `ajax`, which should be used instead.
		 *
		 * This parameter allows you to override the default function which obtains
		 * the data from the server so something more suitable for your application.
		 * For example you could use POST data, or pull information from a Gears or
		 * AIR database.
		 *  @type function
		 *  @member
		 *  @param {string} source HTTP source to obtain the data from (`ajax`)
		 *  @param {array} data A key/value pair object containing the data to send
		 *    to the server
		 *  @param {function} callback to be called on completion of the data get
		 *    process that will draw the data on the page.
		 *  @param {object} settings DataTables settings object
		 *
		 *  @dtopt Callbacks
		 *  @dtopt Server-side
		 *  @name DataTable.defaults.serverData
		 *
		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
		 */
		"fnServerData": null,
	
	
		/**
		 * __Deprecated__ The functionality provided by this parameter has now been
		 * superseded by that provided through `ajax`, which should be used instead.
		 *
		 *  It is often useful to send extra data to the server when making an Ajax
		 * request - for example custom filtering information, and this callback
		 * function makes it trivial to send extra information to the server. The
		 * passed in parameter is the data set that has been constructed by
		 * DataTables, and you can add to this or modify it as you require.
		 *  @type function
		 *  @param {array} data Data array (array of objects which are name/value
		 *    pairs) that has been constructed by DataTables and will be sent to the
		 *    server. In the case of Ajax sourced data with server-side processing
		 *    this will be an empty array, for server-side processing there will be a
		 *    significant number of parameters!
		 *  @returns {undefined} Ensure that you modify the data array passed in,
		 *    as this is passed by reference.
		 *
		 *  @dtopt Callbacks
		 *  @dtopt Server-side
		 *  @name DataTable.defaults.serverParams
		 *
		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
		 */
		"fnServerParams": null,
	
	
		/**
		 * Load the table state. With this function you can define from where, and how, the
		 * state of a table is loaded. By default DataTables will load from `localStorage`
		 * but you might wish to use a server-side database or cookies.
		 *  @type function
		 *  @member
		 *  @param {object} settings DataTables settings object
		 *  @return {object} The DataTables state object to be loaded
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.stateLoadCallback
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stateSave": true,
		 *        "stateLoadCallback": function (settings) {
		 *          var o;
		 *
		 *          // Send an Ajax request to the server to get the data. Note that
		 *          // this is a synchronous request.
		 *          $.ajax( {
		 *            "url": "/state_load",
		 *            "async": false,
		 *            "dataType": "json",
		 *            "success": function (json) {
		 *              o = json;
		 *            }
		 *          } );
		 *
		 *          return o;
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateLoadCallback": function ( settings ) {
			try {
				return JSON.parse(
					(settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem(
						'DataTables_'+settings.sInstance+'_'+location.pathname
					)
				);
			} catch (e) {}
		},
	
	
		/**
		 * Callback which allows modification of the saved state prior to loading that state.
		 * This callback is called when the table is loading state from the stored data, but
		 * prior to the settings object being modified by the saved state. Note that for
		 * plug-in authors, you should use the `stateLoadParams` event to load parameters for
		 * a plug-in.
		 *  @type function
		 *  @param {object} settings DataTables settings object
		 *  @param {object} data The state object that is to be loaded
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.stateLoadParams
		 *
		 *  @example
		 *    // Remove a saved filter, so filtering is never loaded
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stateSave": true,
		 *        "stateLoadParams": function (settings, data) {
		 *          data.oSearch.sSearch = "";
		 *        }
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Disallow state loading by returning false
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stateSave": true,
		 *        "stateLoadParams": function (settings, data) {
		 *          return false;
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateLoadParams": null,
	
	
		/**
		 * Callback that is called when the state has been loaded from the state saving method
		 * and the DataTables settings object has been modified as a result of the loaded state.
		 *  @type function
		 *  @param {object} settings DataTables settings object
		 *  @param {object} data The state object that was loaded
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.stateLoaded
		 *
		 *  @example
		 *    // Show an alert with the filtering value that was saved
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stateSave": true,
		 *        "stateLoaded": function (settings, data) {
		 *          alert( 'Saved filter was: '+data.oSearch.sSearch );
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateLoaded": null,
	
	
		/**
		 * Save the table state. This function allows you to define where and how the state
		 * information for the table is stored By default DataTables will use `localStorage`
		 * but you might wish to use a server-side database or cookies.
		 *  @type function
		 *  @member
		 *  @param {object} settings DataTables settings object
		 *  @param {object} data The state object to be saved
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.stateSaveCallback
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stateSave": true,
		 *        "stateSaveCallback": function (settings, data) {
		 *          // Send an Ajax request to the server with the state object
		 *          $.ajax( {
		 *            "url": "/state_save",
		 *            "data": data,
		 *            "dataType": "json",
		 *            "method": "POST"
		 *            "success": function () {}
		 *          } );
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateSaveCallback": function ( settings, data ) {
			try {
				(settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem(
					'DataTables_'+settings.sInstance+'_'+location.pathname,
					JSON.stringify( data )
				);
			} catch (e) {}
		},
	
	
		/**
		 * Callback which allows modification of the state to be saved. Called when the table
		 * has changed state a new state save is required. This method allows modification of
		 * the state saving object prior to actually doing the save, including addition or
		 * other state properties or modification. Note that for plug-in authors, you should
		 * use the `stateSaveParams` event to save parameters for a plug-in.
		 *  @type function
		 *  @param {object} settings DataTables settings object
		 *  @param {object} data The state object to be saved
		 *
		 *  @dtopt Callbacks
		 *  @name DataTable.defaults.stateSaveParams
		 *
		 *  @example
		 *    // Remove a saved filter, so filtering is never saved
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stateSave": true,
		 *        "stateSaveParams": function (settings, data) {
		 *          data.oSearch.sSearch = "";
		 *        }
		 *      } );
		 *    } );
		 */
		"fnStateSaveParams": null,
	
	
		/**
		 * Duration for which the saved state information is considered valid. After this period
		 * has elapsed the state will be returned to the default.
		 * Value is given in seconds.
		 *  @type int
		 *  @default 7200 <i>(2 hours)</i>
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.stateDuration
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "stateDuration": 60*60*24; // 1 day
		 *      } );
		 *    } )
		 */
		"iStateDuration": 7200,
	
	
		/**
		 * When enabled DataTables will not make a request to the server for the first
		 * page draw - rather it will use the data already on the page (no sorting etc
		 * will be applied to it), thus saving on an XHR at load time. `deferLoading`
		 * is used to indicate that deferred loading is required, but it is also used
		 * to tell DataTables how many records there are in the full table (allowing
		 * the information element and pagination to be displayed correctly). In the case
		 * where a filtering is applied to the table on initial load, this can be
		 * indicated by giving the parameter as an array, where the first element is
		 * the number of records available after filtering and the second element is the
		 * number of records without filtering (allowing the table information element
		 * to be shown correctly).
		 *  @type int | array
		 *  @default null
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.deferLoading
		 *
		 *  @example
		 *    // 57 records available in the table, no filtering applied
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "serverSide": true,
		 *        "ajax": "scripts/server_processing.php",
		 *        "deferLoading": 57
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // 57 records after filtering, 100 without filtering (an initial filter applied)
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "serverSide": true,
		 *        "ajax": "scripts/server_processing.php",
		 *        "deferLoading": [ 57, 100 ],
		 *        "search": {
		 *          "search": "my_filter"
		 *        }
		 *      } );
		 *    } );
		 */
		"iDeferLoading": null,
	
	
		/**
		 * Number of rows to display on a single page when using pagination. If
		 * feature enabled (`lengthChange`) then the end user will be able to override
		 * this to a custom setting using a pop-up menu.
		 *  @type int
		 *  @default 10
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.pageLength
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "pageLength": 50
		 *      } );
		 *    } )
		 */
		"iDisplayLength": 10,
	
	
		/**
		 * Define the starting point for data display when using DataTables with
		 * pagination. Note that this parameter is the number of records, rather than
		 * the page number, so if you have 10 records per page and want to start on
		 * the third page, it should be "20".
		 *  @type int
		 *  @default 0
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.displayStart
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "displayStart": 20
		 *      } );
		 *    } )
		 */
		"iDisplayStart": 0,
	
	
		/**
		 * By default DataTables allows keyboard navigation of the table (sorting, paging,
		 * and filtering) by adding a `tabindex` attribute to the required elements. This
		 * allows you to tab through the controls and press the enter key to activate them.
		 * The tabindex is default 0, meaning that the tab follows the flow of the document.
		 * You can overrule this using this parameter if you wish. Use a value of -1 to
		 * disable built-in keyboard navigation.
		 *  @type int
		 *  @default 0
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.tabIndex
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "tabIndex": 1
		 *      } );
		 *    } );
		 */
		"iTabIndex": 0,
	
	
		/**
		 * Classes that DataTables assigns to the various components and features
		 * that it adds to the HTML table. This allows classes to be configured
		 * during initialisation in addition to through the static
		 * {@link DataTable.ext.oStdClasses} object).
		 *  @namespace
		 *  @name DataTable.defaults.classes
		 */
		"oClasses": {},
	
	
		/**
		 * All strings that DataTables uses in the user interface that it creates
		 * are defined in this object, allowing you to modified them individually or
		 * completely replace them all as required.
		 *  @namespace
		 *  @name DataTable.defaults.language
		 */
		"oLanguage": {
			/**
			 * Strings that are used for WAI-ARIA labels and controls only (these are not
			 * actually visible on the page, but will be read by screenreaders, and thus
			 * must be internationalised as well).
			 *  @namespace
			 *  @name DataTable.defaults.language.aria
			 */
			"oAria": {
				/**
				 * ARIA label that is added to the table headers when the column may be
				 * sorted ascending by activing the column (click or return when focused).
				 * Note that the column header is prefixed to this string.
				 *  @type string
				 *  @default : activate to sort column ascending
				 *
				 *  @dtopt Language
				 *  @name DataTable.defaults.language.aria.sortAscending
				 *
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "language": {
				 *          "aria": {
				 *            "sortAscending": " - click/return to sort ascending"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sSortAscending": ": activate to sort column ascending",
	
				/**
				 * ARIA label that is added to the table headers when the column may be
				 * sorted descending by activing the column (click or return when focused).
				 * Note that the column header is prefixed to this string.
				 *  @type string
				 *  @default : activate to sort column ascending
				 *
				 *  @dtopt Language
				 *  @name DataTable.defaults.language.aria.sortDescending
				 *
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "language": {
				 *          "aria": {
				 *            "sortDescending": " - click/return to sort descending"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sSortDescending": ": activate to sort column descending"
			},
	
			/**
			 * Pagination string used by DataTables for the built-in pagination
			 * control types.
			 *  @namespace
			 *  @name DataTable.defaults.language.paginate
			 */
			"oPaginate": {
				/**
				 * Text to use when using the 'full_numbers' type of pagination for the
				 * button to take the user to the first page.
				 *  @type string
				 *  @default First
				 *
				 *  @dtopt Language
				 *  @name DataTable.defaults.language.paginate.first
				 *
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "language": {
				 *          "paginate": {
				 *            "first": "First page"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sFirst": "First",
	
	
				/**
				 * Text to use when using the 'full_numbers' type of pagination for the
				 * button to take the user to the last page.
				 *  @type string
				 *  @default Last
				 *
				 *  @dtopt Language
				 *  @name DataTable.defaults.language.paginate.last
				 *
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "language": {
				 *          "paginate": {
				 *            "last": "Last page"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sLast": "Last",
	
	
				/**
				 * Text to use for the 'next' pagination button (to take the user to the
				 * next page).
				 *  @type string
				 *  @default Next
				 *
				 *  @dtopt Language
				 *  @name DataTable.defaults.language.paginate.next
				 *
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "language": {
				 *          "paginate": {
				 *            "next": "Next page"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sNext": "Next",
	
	
				/**
				 * Text to use for the 'previous' pagination button (to take the user to
				 * the previous page).
				 *  @type string
				 *  @default Previous
				 *
				 *  @dtopt Language
				 *  @name DataTable.defaults.language.paginate.previous
				 *
				 *  @example
				 *    $(document).ready( function() {
				 *      $('#example').dataTable( {
				 *        "language": {
				 *          "paginate": {
				 *            "previous": "Previous page"
				 *          }
				 *        }
				 *      } );
				 *    } );
				 */
				"sPrevious": "Previous"
			},
	
			/**
			 * This string is shown in preference to `zeroRecords` when the table is
			 * empty of data (regardless of filtering). Note that this is an optional
			 * parameter - if it is not given, the value of `zeroRecords` will be used
			 * instead (either the default or given value).
			 *  @type string
			 *  @default No data available in table
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.emptyTable
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "emptyTable": "No data available in table"
			 *        }
			 *      } );
			 *    } );
			 */
			"sEmptyTable": "No data available in table",
	
	
			/**
			 * This string gives information to the end user about the information
			 * that is current on display on the page. The following tokens can be
			 * used in the string and will be dynamically replaced as the table
			 * display updates. This tokens can be placed anywhere in the string, or
			 * removed as needed by the language requires:
			 *
			 * * `\_START\_` - Display index of the first record on the current page
			 * * `\_END\_` - Display index of the last record on the current page
			 * * `\_TOTAL\_` - Number of records in the table after filtering
			 * * `\_MAX\_` - Number of records in the table without filtering
			 * * `\_PAGE\_` - Current page number
			 * * `\_PAGES\_` - Total number of pages of data in the table
			 *
			 *  @type string
			 *  @default Showing _START_ to _END_ of _TOTAL_ entries
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.info
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "info": "Showing page _PAGE_ of _PAGES_"
			 *        }
			 *      } );
			 *    } );
			 */
			"sInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
	
	
			/**
			 * Display information string for when the table is empty. Typically the
			 * format of this string should match `info`.
			 *  @type string
			 *  @default Showing 0 to 0 of 0 entries
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.infoEmpty
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "infoEmpty": "No entries to show"
			 *        }
			 *      } );
			 *    } );
			 */
			"sInfoEmpty": "Showing 0 to 0 of 0 entries",
	
	
			/**
			 * When a user filters the information in a table, this string is appended
			 * to the information (`info`) to give an idea of how strong the filtering
			 * is. The variable _MAX_ is dynamically updated.
			 *  @type string
			 *  @default (filtered from _MAX_ total entries)
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.infoFiltered
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "infoFiltered": " - filtering from _MAX_ records"
			 *        }
			 *      } );
			 *    } );
			 */
			"sInfoFiltered": "(filtered from _MAX_ total entries)",
	
	
			/**
			 * If can be useful to append extra information to the info string at times,
			 * and this variable does exactly that. This information will be appended to
			 * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are
			 * being used) at all times.
			 *  @type string
			 *  @default <i>Empty string</i>
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.infoPostFix
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "infoPostFix": "All records shown are derived from real information."
			 *        }
			 *      } );
			 *    } );
			 */
			"sInfoPostFix": "",
	
	
			/**
			 * This decimal place operator is a little different from the other
			 * language options since DataTables doesn't output floating point
			 * numbers, so it won't ever use this for display of a number. Rather,
			 * what this parameter does is modify the sort methods of the table so
			 * that numbers which are in a format which has a character other than
			 * a period (`.`) as a decimal place will be sorted numerically.
			 *
			 * Note that numbers with different decimal places cannot be shown in
			 * the same table and still be sortable, the table must be consistent.
			 * However, multiple different tables on the page can use different
			 * decimal place characters.
			 *  @type string
			 *  @default 
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.decimal
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "decimal": ","
			 *          "thousands": "."
			 *        }
			 *      } );
			 *    } );
			 */
			"sDecimal": "",
	
	
			/**
			 * DataTables has a build in number formatter (`formatNumber`) which is
			 * used to format large numbers that are used in the table information.
			 * By default a comma is used, but this can be trivially changed to any
			 * character you wish with this parameter.
			 *  @type string
			 *  @default ,
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.thousands
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "thousands": "'"
			 *        }
			 *      } );
			 *    } );
			 */
			"sThousands": ",",
	
	
			/**
			 * Detail the action that will be taken when the drop down menu for the
			 * pagination length option is changed. The '_MENU_' variable is replaced
			 * with a default select list of 10, 25, 50 and 100, and can be replaced
			 * with a custom select box if required.
			 *  @type string
			 *  @default Show _MENU_ entries
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.lengthMenu
			 *
			 *  @example
			 *    // Language change only
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "lengthMenu": "Display _MENU_ records"
			 *        }
			 *      } );
			 *    } );
			 *
			 *  @example
			 *    // Language and options change
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "lengthMenu": 'Display <select>'+
			 *            '<option value="10">10</option>'+
			 *            '<option value="20">20</option>'+
			 *            '<option value="30">30</option>'+
			 *            '<option value="40">40</option>'+
			 *            '<option value="50">50</option>'+
			 *            '<option value="-1">All</option>'+
			 *            '</select> records'
			 *        }
			 *      } );
			 *    } );
			 */
			"sLengthMenu": "Show _MENU_ entries",
	
	
			/**
			 * When using Ajax sourced data and during the first draw when DataTables is
			 * gathering the data, this message is shown in an empty row in the table to
			 * indicate to the end user the the data is being loaded. Note that this
			 * parameter is not used when loading data by server-side processing, just
			 * Ajax sourced data with client-side processing.
			 *  @type string
			 *  @default Loading...
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.loadingRecords
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "loadingRecords": "Please wait - loading..."
			 *        }
			 *      } );
			 *    } );
			 */
			"sLoadingRecords": "Loading...",
	
	
			/**
			 * Text which is displayed when the table is processing a user action
			 * (usually a sort command or similar).
			 *  @type string
			 *  @default Processing...
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.processing
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "processing": "DataTables is currently busy"
			 *        }
			 *      } );
			 *    } );
			 */
			"sProcessing": "Processing...",
	
	
			/**
			 * Details the actions that will be taken when the user types into the
			 * filtering input text box. The variable "_INPUT_", if used in the string,
			 * is replaced with the HTML text box for the filtering input allowing
			 * control over where it appears in the string. If "_INPUT_" is not given
			 * then the input box is appended to the string automatically.
			 *  @type string
			 *  @default Search:
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.search
			 *
			 *  @example
			 *    // Input text box will be appended at the end automatically
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "search": "Filter records:"
			 *        }
			 *      } );
			 *    } );
			 *
			 *  @example
			 *    // Specify where the filter should appear
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "search": "Apply filter _INPUT_ to table"
			 *        }
			 *      } );
			 *    } );
			 */
			"sSearch": "Search:",
	
	
			/**
			 * Assign a `placeholder` attribute to the search `input` element
			 *  @type string
			 *  @default 
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.searchPlaceholder
			 */
			"sSearchPlaceholder": "",
	
	
			/**
			 * All of the language information can be stored in a file on the
			 * server-side, which DataTables will look up if this parameter is passed.
			 * It must store the URL of the language file, which is in a JSON format,
			 * and the object has the same properties as the oLanguage object in the
			 * initialiser object (i.e. the above parameters). Please refer to one of
			 * the example language files to see how this works in action.
			 *  @type string
			 *  @default <i>Empty string - i.e. disabled</i>
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.url
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "url": "http://www.sprymedia.co.uk/dataTables/lang.txt"
			 *        }
			 *      } );
			 *    } );
			 */
			"sUrl": "",
	
	
			/**
			 * Text shown inside the table records when the is no information to be
			 * displayed after filtering. `emptyTable` is shown when there is simply no
			 * information in the table at all (regardless of filtering).
			 *  @type string
			 *  @default No matching records found
			 *
			 *  @dtopt Language
			 *  @name DataTable.defaults.language.zeroRecords
			 *
			 *  @example
			 *    $(document).ready( function() {
			 *      $('#example').dataTable( {
			 *        "language": {
			 *          "zeroRecords": "No records to display"
			 *        }
			 *      } );
			 *    } );
			 */
			"sZeroRecords": "No matching records found"
		},
	
	
		/**
		 * This parameter allows you to have define the global filtering state at
		 * initialisation time. As an object the `search` parameter must be
		 * defined, but all other parameters are optional. When `regex` is true,
		 * the search string will be treated as a regular expression, when false
		 * (default) it will be treated as a straight string. When `smart`
		 * DataTables will use it's smart filtering methods (to word match at
		 * any point in the data), when false this will not be done.
		 *  @namespace
		 *  @extends DataTable.models.oSearch
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.search
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "search": {"search": "Initial search"}
		 *      } );
		 *    } )
		 */
		"oSearch": $.extend( {}, DataTable.models.oSearch ),
	
	
		/**
		 * __Deprecated__ The functionality provided by this parameter has now been
		 * superseded by that provided through `ajax`, which should be used instead.
		 *
		 * By default DataTables will look for the property `data` (or `aaData` for
		 * compatibility with DataTables 1.9-) when obtaining data from an Ajax
		 * source or for server-side processing - this parameter allows that
		 * property to be changed. You can use Javascript dotted object notation to
		 * get a data source for multiple levels of nesting.
		 *  @type string
		 *  @default data
		 *
		 *  @dtopt Options
		 *  @dtopt Server-side
		 *  @name DataTable.defaults.ajaxDataProp
		 *
		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
		 */
		"sAjaxDataProp": "data",
	
	
		/**
		 * __Deprecated__ The functionality provided by this parameter has now been
		 * superseded by that provided through `ajax`, which should be used instead.
		 *
		 * You can instruct DataTables to load data from an external
		 * source using this parameter (use aData if you want to pass data in you
		 * already have). Simply provide a url a JSON object can be obtained from.
		 *  @type string
		 *  @default null
		 *
		 *  @dtopt Options
		 *  @dtopt Server-side
		 *  @name DataTable.defaults.ajaxSource
		 *
		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
		 */
		"sAjaxSource": null,
	
	
		/**
		 * This initialisation variable allows you to specify exactly where in the
		 * DOM you want DataTables to inject the various controls it adds to the page
		 * (for example you might want the pagination controls at the top of the
		 * table). DIV elements (with or without a custom class) can also be added to
		 * aid styling. The follow syntax is used:
		 *   <ul>
		 *     <li>The following options are allowed:
		 *       <ul>
		 *         <li>'l' - Length changing</li>
		 *         <li>'f' - Filtering input</li>
		 *         <li>'t' - The table!</li>
		 *         <li>'i' - Information</li>
		 *         <li>'p' - Pagination</li>
		 *         <li>'r' - pRocessing</li>
		 *       </ul>
		 *     </li>
		 *     <li>The following constants are allowed:
		 *       <ul>
		 *         <li>'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')</li>
		 *         <li>'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')</li>
		 *       </ul>
		 *     </li>
		 *     <li>The following syntax is expected:
		 *       <ul>
		 *         <li>'&lt;' and '&gt;' - div elements</li>
		 *         <li>'&lt;"class" and '&gt;' - div with a class</li>
		 *         <li>'&lt;"#id" and '&gt;' - div with an ID</li>
		 *       </ul>
		 *     </li>
		 *     <li>Examples:
		 *       <ul>
		 *         <li>'&lt;"wrapper"flipt&gt;'</li>
		 *         <li>'&lt;lf&lt;t&gt;ip&gt;'</li>
		 *       </ul>
		 *     </li>
		 *   </ul>
		 *  @type string
		 *  @default lfrtip <i>(when `jQueryUI` is false)</i> <b>or</b>
		 *    <"H"lfr>t<"F"ip> <i>(when `jQueryUI` is true)</i>
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.dom
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "dom": '&lt;"top"i&gt;rt&lt;"bottom"flp&gt;&lt;"clear"&gt;'
		 *      } );
		 *    } );
		 */
		"sDom": "lfrtip",
	
	
		/**
		 * Search delay option. This will throttle full table searches that use the
		 * DataTables provided search input element (it does not effect calls to
		 * `dt-api search()`, providing a delay before the search is made.
		 *  @type integer
		 *  @default 0
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.searchDelay
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "searchDelay": 200
		 *      } );
		 *    } )
		 */
		"searchDelay": null,
	
	
		/**
		 * DataTables features four different built-in options for the buttons to
		 * display for pagination control:
		 *
		 * * `simple` - 'Previous' and 'Next' buttons only
		 * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers
		 * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons
		 * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus
		 *   page numbers
		 *  
		 * Further methods can be added using {@link DataTable.ext.oPagination}.
		 *  @type string
		 *  @default simple_numbers
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.pagingType
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "pagingType": "full_numbers"
		 *      } );
		 *    } )
		 */
		"sPaginationType": "simple_numbers",
	
	
		/**
		 * Enable horizontal scrolling. When a table is too wide to fit into a
		 * certain layout, or you have a large number of columns in the table, you
		 * can enable x-scrolling to show the table in a viewport, which can be
		 * scrolled. This property can be `true` which will allow the table to
		 * scroll horizontally when needed, or any CSS unit, or a number (in which
		 * case it will be treated as a pixel measurement). Setting as simply `true`
		 * is recommended.
		 *  @type boolean|string
		 *  @default <i>blank string - i.e. disabled</i>
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.scrollX
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "scrollX": true,
		 *        "scrollCollapse": true
		 *      } );
		 *    } );
		 */
		"sScrollX": "",
	
	
		/**
		 * This property can be used to force a DataTable to use more width than it
		 * might otherwise do when x-scrolling is enabled. For example if you have a
		 * table which requires to be well spaced, this parameter is useful for
		 * "over-sizing" the table, and thus forcing scrolling. This property can by
		 * any CSS unit, or a number (in which case it will be treated as a pixel
		 * measurement).
		 *  @type string
		 *  @default <i>blank string - i.e. disabled</i>
		 *
		 *  @dtopt Options
		 *  @name DataTable.defaults.scrollXInner
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "scrollX": "100%",
		 *        "scrollXInner": "110%"
		 *      } );
		 *    } );
		 */
		"sScrollXInner": "",
	
	
		/**
		 * Enable vertical scrolling. Vertical scrolling will constrain the DataTable
		 * to the given height, and enable scrolling for any data which overflows the
		 * current viewport. This can be used as an alternative to paging to display
		 * a lot of data in a small area (although paging and scrolling can both be
		 * enabled at the same time). This property can be any CSS unit, or a number
		 * (in which case it will be treated as a pixel measurement).
		 *  @type string
		 *  @default <i>blank string - i.e. disabled</i>
		 *
		 *  @dtopt Features
		 *  @name DataTable.defaults.scrollY
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "scrollY": "200px",
		 *        "paginate": false
		 *      } );
		 *    } );
		 */
		"sScrollY": "",
	
	
		/**
		 * __Deprecated__ The functionality provided by this parameter has now been
		 * superseded by that provided through `ajax`, which should be used instead.
		 *
		 * Set the HTTP method that is used to make the Ajax call for server-side
		 * processing or Ajax sourced data.
		 *  @type string
		 *  @default GET
		 *
		 *  @dtopt Options
		 *  @dtopt Server-side
		 *  @name DataTable.defaults.serverMethod
		 *
		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
		 */
		"sServerMethod": "GET",
	
	
		/**
		 * DataTables makes use of renderers when displaying HTML elements for
		 * a table. These renderers can be added or modified by plug-ins to
		 * generate suitable mark-up for a site. For example the Bootstrap
		 * integration plug-in for DataTables uses a paging button renderer to
		 * display pagination buttons in the mark-up required by Bootstrap.
		 *
		 * For further information about the renderers available see
		 * DataTable.ext.renderer
		 *  @type string|object
		 *  @default null
		 *
		 *  @name DataTable.defaults.renderer
		 *
		 */
		"renderer": null,
	
	
		/**
		 * Set the data property name that DataTables should use to get a row's id
		 * to set as the `id` property in the node.
		 *  @type string
		 *  @default DT_RowId
		 *
		 *  @name DataTable.defaults.rowId
		 */
		"rowId": "DT_RowId"
	};
	
	_fnHungarianMap( DataTable.defaults );
	
	
	
	/*
	 * Developer note - See note in model.defaults.js about the use of Hungarian
	 * notation and camel case.
	 */
	
	/**
	 * Column options that can be given to DataTables at initialisation time.
	 *  @namespace
	 */
	DataTable.defaults.column = {
		/**
		 * Define which column(s) an order will occur on for this column. This
		 * allows a column's ordering to take multiple columns into account when
		 * doing a sort or use the data from a different column. For example first
		 * name / last name columns make sense to do a multi-column sort over the
		 * two columns.
		 *  @type array|int
		 *  @default null <i>Takes the value of the column index automatically</i>
		 *
		 *  @name DataTable.defaults.column.orderData
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "orderData": [ 0, 1 ], "targets": [ 0 ] },
		 *          { "orderData": [ 1, 0 ], "targets": [ 1 ] },
		 *          { "orderData": 2, "targets": [ 2 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "orderData": [ 0, 1 ] },
		 *          { "orderData": [ 1, 0 ] },
		 *          { "orderData": 2 },
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"aDataSort": null,
		"iDataSort": -1,
	
	
		/**
		 * You can control the default ordering direction, and even alter the
		 * behaviour of the sort handler (i.e. only allow ascending ordering etc)
		 * using this parameter.
		 *  @type array
		 *  @default [ 'asc', 'desc' ]
		 *
		 *  @name DataTable.defaults.column.orderSequence
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "orderSequence": [ "asc" ], "targets": [ 1 ] },
		 *          { "orderSequence": [ "desc", "asc", "asc" ], "targets": [ 2 ] },
		 *          { "orderSequence": [ "desc" ], "targets": [ 3 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          null,
		 *          { "orderSequence": [ "asc" ] },
		 *          { "orderSequence": [ "desc", "asc", "asc" ] },
		 *          { "orderSequence": [ "desc" ] },
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"asSorting": [ 'asc', 'desc' ],
	
	
		/**
		 * Enable or disable filtering on the data in this column.
		 *  @type boolean
		 *  @default true
		 *
		 *  @name DataTable.defaults.column.searchable
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "searchable": false, "targets": [ 0 ] }
		 *        ] } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "searchable": false },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ] } );
		 *    } );
		 */
		"bSearchable": true,
	
	
		/**
		 * Enable or disable ordering on this column.
		 *  @type boolean
		 *  @default true
		 *
		 *  @name DataTable.defaults.column.orderable
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "orderable": false, "targets": [ 0 ] }
		 *        ] } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "orderable": false },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ] } );
		 *    } );
		 */
		"bSortable": true,
	
	
		/**
		 * Enable or disable the display of this column.
		 *  @type boolean
		 *  @default true
		 *
		 *  @name DataTable.defaults.column.visible
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "visible": false, "targets": [ 0 ] }
		 *        ] } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "visible": false },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ] } );
		 *    } );
		 */
		"bVisible": true,
	
	
		/**
		 * Developer definable function that is called whenever a cell is created (Ajax source,
		 * etc) or processed for input (DOM source). This can be used as a compliment to mRender
		 * allowing you to modify the DOM element (add background colour for example) when the
		 * element is available.
		 *  @type function
		 *  @param {element} td The TD node that has been created
		 *  @param {*} cellData The Data for the cell
		 *  @param {array|object} rowData The data for the whole row
		 *  @param {int} row The row index for the aoData data store
		 *  @param {int} col The column index for aoColumns
		 *
		 *  @name DataTable.defaults.column.createdCell
		 *  @dtopt Columns
		 *
		 *  @example
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [3],
		 *          "createdCell": function (td, cellData, rowData, row, col) {
		 *            if ( cellData == "1.7" ) {
		 *              $(td).css('color', 'blue')
		 *            }
		 *          }
		 *        } ]
		 *      });
		 *    } );
		 */
		"fnCreatedCell": null,
	
	
		/**
		 * This parameter has been replaced by `data` in DataTables to ensure naming
		 * consistency. `dataProp` can still be used, as there is backwards
		 * compatibility in DataTables for this option, but it is strongly
		 * recommended that you use `data` in preference to `dataProp`.
		 *  @name DataTable.defaults.column.dataProp
		 */
	
	
		/**
		 * This property can be used to read data from any data source property,
		 * including deeply nested objects / properties. `data` can be given in a
		 * number of different ways which effect its behaviour:
		 *
		 * * `integer` - treated as an array index for the data source. This is the
		 *   default that DataTables uses (incrementally increased for each column).
		 * * `string` - read an object property from the data source. There are
		 *   three 'special' options that can be used in the string to alter how
		 *   DataTables reads the data from the source object:
		 *    * `.` - Dotted Javascript notation. Just as you use a `.` in
		 *      Javascript to read from nested objects, so to can the options
		 *      specified in `data`. For example: `browser.version` or
		 *      `browser.name`. If your object parameter name contains a period, use
		 *      `\\` to escape it - i.e. `first\\.name`.
		 *    * `[]` - Array notation. DataTables can automatically combine data
		 *      from and array source, joining the data with the characters provided
		 *      between the two brackets. For example: `name[, ]` would provide a
		 *      comma-space separated list from the source array. If no characters
		 *      are provided between the brackets, the original array source is
		 *      returned.
		 *    * `()` - Function notation. Adding `()` to the end of a parameter will
		 *      execute a function of the name given. For example: `browser()` for a
		 *      simple function on the data source, `browser.version()` for a
		 *      function in a nested property or even `browser().version` to get an
		 *      object property if the function called returns an object. Note that
		 *      function notation is recommended for use in `render` rather than
		 *      `data` as it is much simpler to use as a renderer.
		 * * `null` - use the original data source for the row rather than plucking
		 *   data directly from it. This action has effects on two other
		 *   initialisation options:
		 *    * `defaultContent` - When null is given as the `data` option and
		 *      `defaultContent` is specified for the column, the value defined by
		 *      `defaultContent` will be used for the cell.
		 *    * `render` - When null is used for the `data` option and the `render`
		 *      option is specified for the column, the whole data source for the
		 *      row is used for the renderer.
		 * * `function` - the function given will be executed whenever DataTables
		 *   needs to set or get the data for a cell in the column. The function
		 *   takes three parameters:
		 *    * Parameters:
		 *      * `{array|object}` The data source for the row
		 *      * `{string}` The type call data requested - this will be 'set' when
		 *        setting data or 'filter', 'display', 'type', 'sort' or undefined
		 *        when gathering data. Note that when `undefined` is given for the
		 *        type DataTables expects to get the raw data for the object back<
		 *      * `{*}` Data to set when the second parameter is 'set'.
		 *    * Return:
		 *      * The return value from the function is not required when 'set' is
		 *        the type of call, but otherwise the return is what will be used
		 *        for the data requested.
		 *
		 * Note that `data` is a getter and setter option. If you just require
		 * formatting of data for output, you will likely want to use `render` which
		 * is simply a getter and thus simpler to use.
		 *
		 * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The
		 * name change reflects the flexibility of this property and is consistent
		 * with the naming of mRender. If 'mDataProp' is given, then it will still
		 * be used by DataTables, as it automatically maps the old name to the new
		 * if required.
		 *
		 *  @type string|int|function|null
		 *  @default null <i>Use automatically calculated column index</i>
		 *
		 *  @name DataTable.defaults.column.data
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Read table data from objects
		 *    // JSON structure for each row:
		 *    //   {
		 *    //      "engine": {value},
		 *    //      "browser": {value},
		 *    //      "platform": {value},
		 *    //      "version": {value},
		 *    //      "grade": {value}
		 *    //   }
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "ajaxSource": "sources/objects.txt",
		 *        "columns": [
		 *          { "data": "engine" },
		 *          { "data": "browser" },
		 *          { "data": "platform" },
		 *          { "data": "version" },
		 *          { "data": "grade" }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Read information from deeply nested objects
		 *    // JSON structure for each row:
		 *    //   {
		 *    //      "engine": {value},
		 *    //      "browser": {value},
		 *    //      "platform": {
		 *    //         "inner": {value}
		 *    //      },
		 *    //      "details": [
		 *    //         {value}, {value}
		 *    //      ]
		 *    //   }
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "ajaxSource": "sources/deep.txt",
		 *        "columns": [
		 *          { "data": "engine" },
		 *          { "data": "browser" },
		 *          { "data": "platform.inner" },
		 *          { "data": "platform.details.0" },
		 *          { "data": "platform.details.1" }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `data` as a function to provide different information for
		 *    // sorting, filtering and display. In this case, currency (price)
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [ 0 ],
		 *          "data": function ( source, type, val ) {
		 *            if (type === 'set') {
		 *              source.price = val;
		 *              // Store the computed dislay and filter values for efficiency
		 *              source.price_display = val=="" ? "" : "$"+numberFormat(val);
		 *              source.price_filter  = val=="" ? "" : "$"+numberFormat(val)+" "+val;
		 *              return;
		 *            }
		 *            else if (type === 'display') {
		 *              return source.price_display;
		 *            }
		 *            else if (type === 'filter') {
		 *              return source.price_filter;
		 *            }
		 *            // 'sort', 'type' and undefined all just use the integer
		 *            return source.price;
		 *          }
		 *        } ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using default content
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [ 0 ],
		 *          "data": null,
		 *          "defaultContent": "Click to edit"
		 *        } ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using array notation - outputting a list from an array
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [ 0 ],
		 *          "data": "name[, ]"
		 *        } ]
		 *      } );
		 *    } );
		 *
		 */
		"mData": null,
	
	
		/**
		 * This property is the rendering partner to `data` and it is suggested that
		 * when you want to manipulate data for display (including filtering,
		 * sorting etc) without altering the underlying data for the table, use this
		 * property. `render` can be considered to be the the read only companion to
		 * `data` which is read / write (then as such more complex). Like `data`
		 * this option can be given in a number of different ways to effect its
		 * behaviour:
		 *
		 * * `integer` - treated as an array index for the data source. This is the
		 *   default that DataTables uses (incrementally increased for each column).
		 * * `string` - read an object property from the data source. There are
		 *   three 'special' options that can be used in the string to alter how
		 *   DataTables reads the data from the source object:
		 *    * `.` - Dotted Javascript notation. Just as you use a `.` in
		 *      Javascript to read from nested objects, so to can the options
		 *      specified in `data`. For example: `browser.version` or
		 *      `browser.name`. If your object parameter name contains a period, use
		 *      `\\` to escape it - i.e. `first\\.name`.
		 *    * `[]` - Array notation. DataTables can automatically combine data
		 *      from and array source, joining the data with the characters provided
		 *      between the two brackets. For example: `name[, ]` would provide a
		 *      comma-space separated list from the source array. If no characters
		 *      are provided between the brackets, the original array source is
		 *      returned.
		 *    * `()` - Function notation. Adding `()` to the end of a parameter will
		 *      execute a function of the name given. For example: `browser()` for a
		 *      simple function on the data source, `browser.version()` for a
		 *      function in a nested property or even `browser().version` to get an
		 *      object property if the function called returns an object.
		 * * `object` - use different data for the different data types requested by
		 *   DataTables ('filter', 'display', 'type' or 'sort'). The property names
		 *   of the object is the data type the property refers to and the value can
		 *   defined using an integer, string or function using the same rules as
		 *   `render` normally does. Note that an `_` option _must_ be specified.
		 *   This is the default value to use if you haven't specified a value for
		 *   the data type requested by DataTables.
		 * * `function` - the function given will be executed whenever DataTables
		 *   needs to set or get the data for a cell in the column. The function
		 *   takes three parameters:
		 *    * Parameters:
		 *      * {array|object} The data source for the row (based on `data`)
		 *      * {string} The type call data requested - this will be 'filter',
		 *        'display', 'type' or 'sort'.
		 *      * {array|object} The full data source for the row (not based on
		 *        `data`)
		 *    * Return:
		 *      * The return value from the function is what will be used for the
		 *        data requested.
		 *
		 *  @type string|int|function|object|null
		 *  @default null Use the data source value.
		 *
		 *  @name DataTable.defaults.column.render
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Create a comma separated list from an array of objects
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "ajaxSource": "sources/deep.txt",
		 *        "columns": [
		 *          { "data": "engine" },
		 *          { "data": "browser" },
		 *          {
		 *            "data": "platform",
		 *            "render": "[, ].name"
		 *          }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Execute a function to obtain data
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [ 0 ],
		 *          "data": null, // Use the full data source object for the renderer's source
		 *          "render": "browserName()"
		 *        } ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // As an object, extracting different data for the different types
		 *    // This would be used with a data source such as:
		 *    //   { "phone": 5552368, "phone_filter": "5552368 555-2368", "phone_display": "555-2368" }
		 *    // Here the `phone` integer is used for sorting and type detection, while `phone_filter`
		 *    // (which has both forms) is used for filtering for if a user inputs either format, while
		 *    // the formatted phone number is the one that is shown in the table.
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [ 0 ],
		 *          "data": null, // Use the full data source object for the renderer's source
		 *          "render": {
		 *            "_": "phone",
		 *            "filter": "phone_filter",
		 *            "display": "phone_display"
		 *          }
		 *        } ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Use as a function to create a link from the data source
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [ 0 ],
		 *          "data": "download_link",
		 *          "render": function ( data, type, full ) {
		 *            return '<a href="'+data+'">Download</a>';
		 *          }
		 *        } ]
		 *      } );
		 *    } );
		 */
		"mRender": null,
	
	
		/**
		 * Change the cell type created for the column - either TD cells or TH cells. This
		 * can be useful as TH cells have semantic meaning in the table body, allowing them
		 * to act as a header for a row (you may wish to add scope='row' to the TH elements).
		 *  @type string
		 *  @default td
		 *
		 *  @name DataTable.defaults.column.cellType
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Make the first column use TH cells
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [ {
		 *          "targets": [ 0 ],
		 *          "cellType": "th"
		 *        } ]
		 *      } );
		 *    } );
		 */
		"sCellType": "td",
	
	
		/**
		 * Class to give to each cell in this column.
		 *  @type string
		 *  @default <i>Empty string</i>
		 *
		 *  @name DataTable.defaults.column.class
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "class": "my_class", "targets": [ 0 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "class": "my_class" },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"sClass": "",
	
		/**
		 * When DataTables calculates the column widths to assign to each column,
		 * it finds the longest string in each column and then constructs a
		 * temporary table and reads the widths from that. The problem with this
		 * is that "mmm" is much wider then "iiii", but the latter is a longer
		 * string - thus the calculation can go wrong (doing it properly and putting
		 * it into an DOM object and measuring that is horribly(!) slow). Thus as
		 * a "work around" we provide this option. It will append its value to the
		 * text that is found to be the longest string for the column - i.e. padding.
		 * Generally you shouldn't need this!
		 *  @type string
		 *  @default <i>Empty string<i>
		 *
		 *  @name DataTable.defaults.column.contentPadding
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          null,
		 *          null,
		 *          null,
		 *          {
		 *            "contentPadding": "mmm"
		 *          }
		 *        ]
		 *      } );
		 *    } );
		 */
		"sContentPadding": "",
	
	
		/**
		 * Allows a default value to be given for a column's data, and will be used
		 * whenever a null data source is encountered (this can be because `data`
		 * is set to null, or because the data source itself is null).
		 *  @type string
		 *  @default null
		 *
		 *  @name DataTable.defaults.column.defaultContent
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          {
		 *            "data": null,
		 *            "defaultContent": "Edit",
		 *            "targets": [ -1 ]
		 *          }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          null,
		 *          null,
		 *          null,
		 *          {
		 *            "data": null,
		 *            "defaultContent": "Edit"
		 *          }
		 *        ]
		 *      } );
		 *    } );
		 */
		"sDefaultContent": null,
	
	
		/**
		 * This parameter is only used in DataTables' server-side processing. It can
		 * be exceptionally useful to know what columns are being displayed on the
		 * client side, and to map these to database fields. When defined, the names
		 * also allow DataTables to reorder information from the server if it comes
		 * back in an unexpected order (i.e. if you switch your columns around on the
		 * client-side, your server-side code does not also need updating).
		 *  @type string
		 *  @default <i>Empty string</i>
		 *
		 *  @name DataTable.defaults.column.name
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "name": "engine", "targets": [ 0 ] },
		 *          { "name": "browser", "targets": [ 1 ] },
		 *          { "name": "platform", "targets": [ 2 ] },
		 *          { "name": "version", "targets": [ 3 ] },
		 *          { "name": "grade", "targets": [ 4 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "name": "engine" },
		 *          { "name": "browser" },
		 *          { "name": "platform" },
		 *          { "name": "version" },
		 *          { "name": "grade" }
		 *        ]
		 *      } );
		 *    } );
		 */
		"sName": "",
	
	
		/**
		 * Defines a data source type for the ordering which can be used to read
		 * real-time information from the table (updating the internally cached
		 * version) prior to ordering. This allows ordering to occur on user
		 * editable elements such as form inputs.
		 *  @type string
		 *  @default std
		 *
		 *  @name DataTable.defaults.column.orderDataType
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "orderDataType": "dom-text", "targets": [ 2, 3 ] },
		 *          { "type": "numeric", "targets": [ 3 ] },
		 *          { "orderDataType": "dom-select", "targets": [ 4 ] },
		 *          { "orderDataType": "dom-checkbox", "targets": [ 5 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          null,
		 *          null,
		 *          { "orderDataType": "dom-text" },
		 *          { "orderDataType": "dom-text", "type": "numeric" },
		 *          { "orderDataType": "dom-select" },
		 *          { "orderDataType": "dom-checkbox" }
		 *        ]
		 *      } );
		 *    } );
		 */
		"sSortDataType": "std",
	
	
		/**
		 * The title of this column.
		 *  @type string
		 *  @default null <i>Derived from the 'TH' value for this column in the
		 *    original HTML table.</i>
		 *
		 *  @name DataTable.defaults.column.title
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "title": "My column title", "targets": [ 0 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "title": "My column title" },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"sTitle": null,
	
	
		/**
		 * The type allows you to specify how the data for this column will be
		 * ordered. Four types (string, numeric, date and html (which will strip
		 * HTML tags before ordering)) are currently available. Note that only date
		 * formats understood by Javascript's Date() object will be accepted as type
		 * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string',
		 * 'numeric', 'date' or 'html' (by default). Further types can be adding
		 * through plug-ins.
		 *  @type string
		 *  @default null <i>Auto-detected from raw data</i>
		 *
		 *  @name DataTable.defaults.column.type
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "type": "html", "targets": [ 0 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "type": "html" },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"sType": null,
	
	
		/**
		 * Defining the width of the column, this parameter may take any CSS value
		 * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not
		 * been given a specific width through this interface ensuring that the table
		 * remains readable.
		 *  @type string
		 *  @default null <i>Automatic</i>
		 *
		 *  @name DataTable.defaults.column.width
		 *  @dtopt Columns
		 *
		 *  @example
		 *    // Using `columnDefs`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columnDefs": [
		 *          { "width": "20%", "targets": [ 0 ] }
		 *        ]
		 *      } );
		 *    } );
		 *
		 *  @example
		 *    // Using `columns`
		 *    $(document).ready( function() {
		 *      $('#example').dataTable( {
		 *        "columns": [
		 *          { "width": "20%" },
		 *          null,
		 *          null,
		 *          null,
		 *          null
		 *        ]
		 *      } );
		 *    } );
		 */
		"sWidth": null
	};
	
	_fnHungarianMap( DataTable.defaults.column );
	
	
	
	/**
	 * DataTables settings object - this holds all the information needed for a
	 * given table, including configuration, data and current application of the
	 * table options. DataTables does not have a single instance for each DataTable
	 * with the settings attached to that instance, but rather instances of the
	 * DataTable "class" are created on-the-fly as needed (typically by a
	 * $().dataTable() call) and the settings object is then applied to that
	 * instance.
	 *
	 * Note that this object is related to {@link DataTable.defaults} but this
	 * one is the internal data store for DataTables's cache of columns. It should
	 * NOT be manipulated outside of DataTables. Any configuration should be done
	 * through the initialisation options.
	 *  @namespace
	 *  @todo Really should attach the settings object to individual instances so we
	 *    don't need to create new instances on each $().dataTable() call (if the
	 *    table already exists). It would also save passing oSettings around and
	 *    into every single function. However, this is a very significant
	 *    architecture change for DataTables and will almost certainly break
	 *    backwards compatibility with older installations. This is something that
	 *    will be done in 2.0.
	 */
	DataTable.models.oSettings = {
		/**
		 * Primary features of DataTables and their enablement state.
		 *  @namespace
		 */
		"oFeatures": {
	
			/**
			 * Flag to say if DataTables should automatically try to calculate the
			 * optimum table and columns widths (true) or not (false).
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bAutoWidth": null,
	
			/**
			 * Delay the creation of TR and TD elements until they are actually
			 * needed by a driven page draw. This can give a significant speed
			 * increase for Ajax source and Javascript source data, but makes no
			 * difference at all fro DOM and server-side processing tables.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bDeferRender": null,
	
			/**
			 * Enable filtering on the table or not. Note that if this is disabled
			 * then there is no filtering at all on the table, including fnFilter.
			 * To just remove the filtering input use sDom and remove the 'f' option.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bFilter": null,
	
			/**
			 * Table information element (the 'Showing x of y records' div) enable
			 * flag.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bInfo": null,
	
			/**
			 * Present a user control allowing the end user to change the page size
			 * when pagination is enabled.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bLengthChange": null,
	
			/**
			 * Pagination enabled or not. Note that if this is disabled then length
			 * changing must also be disabled.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bPaginate": null,
	
			/**
			 * Processing indicator enable flag whenever DataTables is enacting a
			 * user request - typically an Ajax request for server-side processing.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bProcessing": null,
	
			/**
			 * Server-side processing enabled flag - when enabled DataTables will
			 * get all data from the server for every draw - there is no filtering,
			 * sorting or paging done on the client-side.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bServerSide": null,
	
			/**
			 * Sorting enablement flag.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bSort": null,
	
			/**
			 * Multi-column sorting
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bSortMulti": null,
	
			/**
			 * Apply a class to the columns which are being sorted to provide a
			 * visual highlight or not. This can slow things down when enabled since
			 * there is a lot of DOM interaction.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bSortClasses": null,
	
			/**
			 * State saving enablement flag.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bStateSave": null
		},
	
	
		/**
		 * Scrolling settings for a table.
		 *  @namespace
		 */
		"oScroll": {
			/**
			 * When the table is shorter in height than sScrollY, collapse the
			 * table container down to the height of the table (when true).
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type boolean
			 */
			"bCollapse": null,
	
			/**
			 * Width of the scrollbar for the web-browser's platform. Calculated
			 * during table initialisation.
			 *  @type int
			 *  @default 0
			 */
			"iBarWidth": 0,
	
			/**
			 * Viewport width for horizontal scrolling. Horizontal scrolling is
			 * disabled if an empty string.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type string
			 */
			"sX": null,
	
			/**
			 * Width to expand the table to when using x-scrolling. Typically you
			 * should not need to use this.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type string
			 *  @deprecated
			 */
			"sXInner": null,
	
			/**
			 * Viewport height for vertical scrolling. Vertical scrolling is disabled
			 * if an empty string.
			 * Note that this parameter will be set by the initialisation routine. To
			 * set a default use {@link DataTable.defaults}.
			 *  @type string
			 */
			"sY": null
		},
	
		/**
		 * Language information for the table.
		 *  @namespace
		 *  @extends DataTable.defaults.oLanguage
		 */
		"oLanguage": {
			/**
			 * Information callback function. See
			 * {@link DataTable.defaults.fnInfoCallback}
			 *  @type function
			 *  @default null
			 */
			"fnInfoCallback": null
		},
	
		/**
		 * Browser support parameters
		 *  @namespace
		 */
		"oBrowser": {
			/**
			 * Indicate if the browser incorrectly calculates width:100% inside a
			 * scrolling element (IE6/7)
			 *  @type boolean
			 *  @default false
			 */
			"bScrollOversize": false,
	
			/**
			 * Determine if the vertical scrollbar is on the right or left of the
			 * scrolling container - needed for rtl language layout, although not
			 * all browsers move the scrollbar (Safari).
			 *  @type boolean
			 *  @default false
			 */
			"bScrollbarLeft": false,
	
			/**
			 * Flag for if `getBoundingClientRect` is fully supported or not
			 *  @type boolean
			 *  @default false
			 */
			"bBounding": false,
	
			/**
			 * Browser scrollbar width
			 *  @type integer
			 *  @default 0
			 */
			"barWidth": 0
		},
	
	
		"ajax": null,
	
	
		/**
		 * Array referencing the nodes which are used for the features. The
		 * parameters of this object match what is allowed by sDom - i.e.
		 *   <ul>
		 *     <li>'l' - Length changing</li>
		 *     <li>'f' - Filtering input</li>
		 *     <li>'t' - The table!</li>
		 *     <li>'i' - Information</li>
		 *     <li>'p' - Pagination</li>
		 *     <li>'r' - pRocessing</li>
		 *   </ul>
		 *  @type array
		 *  @default []
		 */
		"aanFeatures": [],
	
		/**
		 * Store data information - see {@link DataTable.models.oRow} for detailed
		 * information.
		 *  @type array
		 *  @default []
		 */
		"aoData": [],
	
		/**
		 * Array of indexes which are in the current display (after filtering etc)
		 *  @type array
		 *  @default []
		 */
		"aiDisplay": [],
	
		/**
		 * Array of indexes for display - no filtering
		 *  @type array
		 *  @default []
		 */
		"aiDisplayMaster": [],
	
		/**
		 * Map of row ids to data indexes
		 *  @type object
		 *  @default {}
		 */
		"aIds": {},
	
		/**
		 * Store information about each column that is in use
		 *  @type array
		 *  @default []
		 */
		"aoColumns": [],
	
		/**
		 * Store information about the table's header
		 *  @type array
		 *  @default []
		 */
		"aoHeader": [],
	
		/**
		 * Store information about the table's footer
		 *  @type array
		 *  @default []
		 */
		"aoFooter": [],
	
		/**
		 * Store the applied global search information in case we want to force a
		 * research or compare the old search to a new one.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @namespace
		 *  @extends DataTable.models.oSearch
		 */
		"oPreviousSearch": {},
	
		/**
		 * Store the applied search for each column - see
		 * {@link DataTable.models.oSearch} for the format that is used for the
		 * filtering information for each column.
		 *  @type array
		 *  @default []
		 */
		"aoPreSearchCols": [],
	
		/**
		 * Sorting that is applied to the table. Note that the inner arrays are
		 * used in the following manner:
		 * <ul>
		 *   <li>Index 0 - column number</li>
		 *   <li>Index 1 - current sorting direction</li>
		 * </ul>
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type array
		 *  @todo These inner arrays should really be objects
		 */
		"aaSorting": null,
	
		/**
		 * Sorting that is always applied to the table (i.e. prefixed in front of
		 * aaSorting).
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type array
		 *  @default []
		 */
		"aaSortingFixed": [],
	
		/**
		 * Classes to use for the striping of a table.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type array
		 *  @default []
		 */
		"asStripeClasses": null,
	
		/**
		 * If restoring a table - we should restore its striping classes as well
		 *  @type array
		 *  @default []
		 */
		"asDestroyStripes": [],
	
		/**
		 * If restoring a table - we should restore its width
		 *  @type int
		 *  @default 0
		 */
		"sDestroyWidth": 0,
	
		/**
		 * Callback functions array for every time a row is inserted (i.e. on a draw).
		 *  @type array
		 *  @default []
		 */
		"aoRowCallback": [],
	
		/**
		 * Callback functions for the header on each draw.
		 *  @type array
		 *  @default []
		 */
		"aoHeaderCallback": [],
	
		/**
		 * Callback function for the footer on each draw.
		 *  @type array
		 *  @default []
		 */
		"aoFooterCallback": [],
	
		/**
		 * Array of callback functions for draw callback functions
		 *  @type array
		 *  @default []
		 */
		"aoDrawCallback": [],
	
		/**
		 * Array of callback functions for row created function
		 *  @type array
		 *  @default []
		 */
		"aoRowCreatedCallback": [],
	
		/**
		 * Callback functions for just before the table is redrawn. A return of
		 * false will be used to cancel the draw.
		 *  @type array
		 *  @default []
		 */
		"aoPreDrawCallback": [],
	
		/**
		 * Callback functions for when the table has been initialised.
		 *  @type array
		 *  @default []
		 */
		"aoInitComplete": [],
	
	
		/**
		 * Callbacks for modifying the settings to be stored for state saving, prior to
		 * saving state.
		 *  @type array
		 *  @default []
		 */
		"aoStateSaveParams": [],
	
		/**
		 * Callbacks for modifying the settings that have been stored for state saving
		 * prior to using the stored values to restore the state.
		 *  @type array
		 *  @default []
		 */
		"aoStateLoadParams": [],
	
		/**
		 * Callbacks for operating on the settings object once the saved state has been
		 * loaded
		 *  @type array
		 *  @default []
		 */
		"aoStateLoaded": [],
	
		/**
		 * Cache the table ID for quick access
		 *  @type string
		 *  @default <i>Empty string</i>
		 */
		"sTableId": "",
	
		/**
		 * The TABLE node for the main table
		 *  @type node
		 *  @default null
		 */
		"nTable": null,
	
		/**
		 * Permanent ref to the thead element
		 *  @type node
		 *  @default null
		 */
		"nTHead": null,
	
		/**
		 * Permanent ref to the tfoot element - if it exists
		 *  @type node
		 *  @default null
		 */
		"nTFoot": null,
	
		/**
		 * Permanent ref to the tbody element
		 *  @type node
		 *  @default null
		 */
		"nTBody": null,
	
		/**
		 * Cache the wrapper node (contains all DataTables controlled elements)
		 *  @type node
		 *  @default null
		 */
		"nTableWrapper": null,
	
		/**
		 * Indicate if when using server-side processing the loading of data
		 * should be deferred until the second draw.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type boolean
		 *  @default false
		 */
		"bDeferLoading": false,
	
		/**
		 * Indicate if all required information has been read in
		 *  @type boolean
		 *  @default false
		 */
		"bInitialised": false,
	
		/**
		 * Information about open rows. Each object in the array has the parameters
		 * 'nTr' and 'nParent'
		 *  @type array
		 *  @default []
		 */
		"aoOpenRows": [],
	
		/**
		 * Dictate the positioning of DataTables' control elements - see
		 * {@link DataTable.model.oInit.sDom}.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 *  @default null
		 */
		"sDom": null,
	
		/**
		 * Search delay (in mS)
		 *  @type integer
		 *  @default null
		 */
		"searchDelay": null,
	
		/**
		 * Which type of pagination should be used.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 *  @default two_button
		 */
		"sPaginationType": "two_button",
	
		/**
		 * The state duration (for `stateSave`) in seconds.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type int
		 *  @default 0
		 */
		"iStateDuration": 0,
	
		/**
		 * Array of callback functions for state saving. Each array element is an
		 * object with the following parameters:
		 *   <ul>
		 *     <li>function:fn - function to call. Takes two parameters, oSettings
		 *       and the JSON string to save that has been thus far created. Returns
		 *       a JSON string to be inserted into a json object
		 *       (i.e. '"param": [ 0, 1, 2]')</li>
		 *     <li>string:sName - name of callback</li>
		 *   </ul>
		 *  @type array
		 *  @default []
		 */
		"aoStateSave": [],
	
		/**
		 * Array of callback functions for state loading. Each array element is an
		 * object with the following parameters:
		 *   <ul>
		 *     <li>function:fn - function to call. Takes two parameters, oSettings
		 *       and the object stored. May return false to cancel state loading</li>
		 *     <li>string:sName - name of callback</li>
		 *   </ul>
		 *  @type array
		 *  @default []
		 */
		"aoStateLoad": [],
	
		/**
		 * State that was saved. Useful for back reference
		 *  @type object
		 *  @default null
		 */
		"oSavedState": null,
	
		/**
		 * State that was loaded. Useful for back reference
		 *  @type object
		 *  @default null
		 */
		"oLoadedState": null,
	
		/**
		 * Source url for AJAX data for the table.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 *  @default null
		 */
		"sAjaxSource": null,
	
		/**
		 * Property from a given object from which to read the table data from. This
		 * can be an empty string (when not server-side processing), in which case
		 * it is  assumed an an array is given directly.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 */
		"sAjaxDataProp": null,
	
		/**
		 * Note if draw should be blocked while getting data
		 *  @type boolean
		 *  @default true
		 */
		"bAjaxDataGet": true,
	
		/**
		 * The last jQuery XHR object that was used for server-side data gathering.
		 * This can be used for working with the XHR information in one of the
		 * callbacks
		 *  @type object
		 *  @default null
		 */
		"jqXHR": null,
	
		/**
		 * JSON returned from the server in the last Ajax request
		 *  @type object
		 *  @default undefined
		 */
		"json": undefined,
	
		/**
		 * Data submitted as part of the last Ajax request
		 *  @type object
		 *  @default undefined
		 */
		"oAjaxData": undefined,
	
		/**
		 * Function to get the server-side data.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type function
		 */
		"fnServerData": null,
	
		/**
		 * Functions which are called prior to sending an Ajax request so extra
		 * parameters can easily be sent to the server
		 *  @type array
		 *  @default []
		 */
		"aoServerParams": [],
	
		/**
		 * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if
		 * required).
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type string
		 */
		"sServerMethod": null,
	
		/**
		 * Format numbers for display.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type function
		 */
		"fnFormatNumber": null,
	
		/**
		 * List of options that can be used for the user selectable length menu.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type array
		 *  @default []
		 */
		"aLengthMenu": null,
	
		/**
		 * Counter for the draws that the table does. Also used as a tracker for
		 * server-side processing
		 *  @type int
		 *  @default 0
		 */
		"iDraw": 0,
	
		/**
		 * Indicate if a redraw is being done - useful for Ajax
		 *  @type boolean
		 *  @default false
		 */
		"bDrawing": false,
	
		/**
		 * Draw index (iDraw) of the last error when parsing the returned data
		 *  @type int
		 *  @default -1
		 */
		"iDrawError": -1,
	
		/**
		 * Paging display length
		 *  @type int
		 *  @default 10
		 */
		"_iDisplayLength": 10,
	
		/**
		 * Paging start point - aiDisplay index
		 *  @type int
		 *  @default 0
		 */
		"_iDisplayStart": 0,
	
		/**
		 * Server-side processing - number of records in the result set
		 * (i.e. before filtering), Use fnRecordsTotal rather than
		 * this property to get the value of the number of records, regardless of
		 * the server-side processing setting.
		 *  @type int
		 *  @default 0
		 *  @private
		 */
		"_iRecordsTotal": 0,
	
		/**
		 * Server-side processing - number of records in the current display set
		 * (i.e. after filtering). Use fnRecordsDisplay rather than
		 * this property to get the value of the number of records, regardless of
		 * the server-side processing setting.
		 *  @type boolean
		 *  @default 0
		 *  @private
		 */
		"_iRecordsDisplay": 0,
	
		/**
		 * Flag to indicate if jQuery UI marking and classes should be used.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type boolean
		 */
		"bJUI": null,
	
		/**
		 * The classes to use for the table
		 *  @type object
		 *  @default {}
		 */
		"oClasses": {},
	
		/**
		 * Flag attached to the settings object so you can check in the draw
		 * callback if filtering has been done in the draw. Deprecated in favour of
		 * events.
		 *  @type boolean
		 *  @default false
		 *  @deprecated
		 */
		"bFiltered": false,
	
		/**
		 * Flag attached to the settings object so you can check in the draw
		 * callback if sorting has been done in the draw. Deprecated in favour of
		 * events.
		 *  @type boolean
		 *  @default false
		 *  @deprecated
		 */
		"bSorted": false,
	
		/**
		 * Indicate that if multiple rows are in the header and there is more than
		 * one unique cell per column, if the top one (true) or bottom one (false)
		 * should be used for sorting / title by DataTables.
		 * Note that this parameter will be set by the initialisation routine. To
		 * set a default use {@link DataTable.defaults}.
		 *  @type boolean
		 */
		"bSortCellsTop": null,
	
		/**
		 * Initialisation object that is used for the table
		 *  @type object
		 *  @default null
		 */
		"oInit": null,
	
		/**
		 * Destroy callback functions - for plug-ins to attach themselves to the
		 * destroy so they can clean up markup and events.
		 *  @type array
		 *  @default []
		 */
		"aoDestroyCallback": [],
	
	
		/**
		 * Get the number of records in the current record set, before filtering
		 *  @type function
		 */
		"fnRecordsTotal": function ()
		{
			return _fnDataSource( this ) == 'ssp' ?
				this._iRecordsTotal * 1 :
				this.aiDisplayMaster.length;
		},
	
		/**
		 * Get the number of records in the current record set, after filtering
		 *  @type function
		 */
		"fnRecordsDisplay": function ()
		{
			return _fnDataSource( this ) == 'ssp' ?
				this._iRecordsDisplay * 1 :
				this.aiDisplay.length;
		},
	
		/**
		 * Get the display end point - aiDisplay index
		 *  @type function
		 */
		"fnDisplayEnd": function ()
		{
			var
				len      = this._iDisplayLength,
				start    = this._iDisplayStart,
				calc     = start + len,
				records  = this.aiDisplay.length,
				features = this.oFeatures,
				paginate = features.bPaginate;
	
			if ( features.bServerSide ) {
				return paginate === false || len === -1 ?
					start + records :
					Math.min( start+len, this._iRecordsDisplay );
			}
			else {
				return ! paginate || calc>records || len===-1 ?
					records :
					calc;
			}
		},
	
		/**
		 * The DataTables object for this table
		 *  @type object
		 *  @default null
		 */
		"oInstance": null,
	
		/**
		 * Unique identifier for each instance of the DataTables object. If there
		 * is an ID on the table node, then it takes that value, otherwise an
		 * incrementing internal counter is used.
		 *  @type string
		 *  @default null
		 */
		"sInstance": null,
	
		/**
		 * tabindex attribute value that is added to DataTables control elements, allowing
		 * keyboard navigation of the table and its controls.
		 */
		"iTabIndex": 0,
	
		/**
		 * DIV container for the footer scrolling table if scrolling
		 */
		"nScrollHead": null,
	
		/**
		 * DIV container for the footer scrolling table if scrolling
		 */
		"nScrollFoot": null,
	
		/**
		 * Last applied sort
		 *  @type array
		 *  @default []
		 */
		"aLastSort": [],
	
		/**
		 * Stored plug-in instances
		 *  @type object
		 *  @default {}
		 */
		"oPlugins": {},
	
		/**
		 * Function used to get a row's id from the row's data
		 *  @type function
		 *  @default null
		 */
		"rowIdFn": null,
	
		/**
		 * Data location where to store a row's id
		 *  @type string
		 *  @default null
		 */
		"rowId": null
	};

	/**
	 * Extension object for DataTables that is used to provide all extension
	 * options.
	 *
	 * Note that the `DataTable.ext` object is available through
	 * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is
	 * also aliased to `jQuery.fn.dataTableExt` for historic reasons.
	 *  @namespace
	 *  @extends DataTable.models.ext
	 */
	
	
	/**
	 * DataTables extensions
	 * 
	 * This namespace acts as a collection area for plug-ins that can be used to
	 * extend DataTables capabilities. Indeed many of the build in methods
	 * use this method to provide their own capabilities (sorting methods for
	 * example).
	 *
	 * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy
	 * reasons
	 *
	 *  @namespace
	 */
	DataTable.ext = _ext = {
		/**
		 * Buttons. For use with the Buttons extension for DataTables. This is
		 * defined here so other extensions can define buttons regardless of load
		 * order. It is _not_ used by DataTables core.
		 *
		 *  @type object
		 *  @default {}
		 */
		buttons: {},
	
	
		/**
		 * Element class names
		 *
		 *  @type object
		 *  @default {}
		 */
		classes: {},
	
	
		/**
		 * DataTables build type (expanded by the download builder)
		 *
		 *  @type string
		 */
		builder: "-source-",
	
	
		/**
		 * Error reporting.
		 * 
		 * How should DataTables report an error. Can take the value 'alert',
		 * 'throw', 'none' or a function.
		 *
		 *  @type string|function
		 *  @default alert
		 */
		errMode: "alert",
	
	
		/**
		 * Feature plug-ins.
		 * 
		 * This is an array of objects which describe the feature plug-ins that are
		 * available to DataTables. These feature plug-ins are then available for
		 * use through the `dom` initialisation option.
		 * 
		 * Each feature plug-in is described by an object which must have the
		 * following properties:
		 * 
		 * * `fnInit` - function that is used to initialise the plug-in,
		 * * `cFeature` - a character so the feature can be enabled by the `dom`
		 *   instillation option. This is case sensitive.
		 *
		 * The `fnInit` function has the following input parameters:
		 *
		 * 1. `{object}` DataTables settings object: see
		 *    {@link DataTable.models.oSettings}
		 *
		 * And the following return is expected:
		 * 
		 * * {node|null} The element which contains your feature. Note that the
		 *   return may also be void if your plug-in does not require to inject any
		 *   DOM elements into DataTables control (`dom`) - for example this might
		 *   be useful when developing a plug-in which allows table control via
		 *   keyboard entry
		 *
		 *  @type array
		 *
		 *  @example
		 *    $.fn.dataTable.ext.features.push( {
		 *      "fnInit": function( oSettings ) {
		 *        return new TableTools( { "oDTSettings": oSettings } );
		 *      },
		 *      "cFeature": "T"
		 *    } );
		 */
		feature: [],
	
	
		/**
		 * Row searching.
		 * 
		 * This method of searching is complimentary to the default type based
		 * searching, and a lot more comprehensive as it allows you complete control
		 * over the searching logic. Each element in this array is a function
		 * (parameters described below) that is called for every row in the table,
		 * and your logic decides if it should be included in the searching data set
		 * or not.
		 *
		 * Searching functions have the following input parameters:
		 *
		 * 1. `{object}` DataTables settings object: see
		 *    {@link DataTable.models.oSettings}
		 * 2. `{array|object}` Data for the row to be processed (same as the
		 *    original format that was passed in as the data source, or an array
		 *    from a DOM data source
		 * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which
		 *    can be useful to retrieve the `TR` element if you need DOM interaction.
		 *
		 * And the following return is expected:
		 *
		 * * {boolean} Include the row in the searched result set (true) or not
		 *   (false)
		 *
		 * Note that as with the main search ability in DataTables, technically this
		 * is "filtering", since it is subtractive. However, for consistency in
		 * naming we call it searching here.
		 *
		 *  @type array
		 *  @default []
		 *
		 *  @example
		 *    // The following example shows custom search being applied to the
		 *    // fourth column (i.e. the data[3] index) based on two input values
		 *    // from the end-user, matching the data in a certain range.
		 *    $.fn.dataTable.ext.search.push(
		 *      function( settings, data, dataIndex ) {
		 *        var min = document.getElementById('min').value * 1;
		 *        var max = document.getElementById('max').value * 1;
		 *        var version = data[3] == "-" ? 0 : data[3]*1;
		 *
		 *        if ( min == "" && max == "" ) {
		 *          return true;
		 *        }
		 *        else if ( min == "" && version < max ) {
		 *          return true;
		 *        }
		 *        else if ( min < version && "" == max ) {
		 *          return true;
		 *        }
		 *        else if ( min < version && version < max ) {
		 *          return true;
		 *        }
		 *        return false;
		 *      }
		 *    );
		 */
		search: [],
	
	
		/**
		 * Selector extensions
		 *
		 * The `selector` option can be used to extend the options available for the
		 * selector modifier options (`selector-modifier` object data type) that
		 * each of the three built in selector types offer (row, column and cell +
		 * their plural counterparts). For example the Select extension uses this
		 * mechanism to provide an option to select only rows, columns and cells
		 * that have been marked as selected by the end user (`{selected: true}`),
		 * which can be used in conjunction with the existing built in selector
		 * options.
		 *
		 * Each property is an array to which functions can be pushed. The functions
		 * take three attributes:
		 *
		 * * Settings object for the host table
		 * * Options object (`selector-modifier` object type)
		 * * Array of selected item indexes
		 *
		 * The return is an array of the resulting item indexes after the custom
		 * selector has been applied.
		 *
		 *  @type object
		 */
		selector: {
			cell: [],
			column: [],
			row: []
		},
	
	
		/**
		 * Internal functions, exposed for used in plug-ins.
		 * 
		 * Please note that you should not need to use the internal methods for
		 * anything other than a plug-in (and even then, try to avoid if possible).
		 * The internal function may change between releases.
		 *
		 *  @type object
		 *  @default {}
		 */
		internal: {},
	
	
		/**
		 * Legacy configuration options. Enable and disable legacy options that
		 * are available in DataTables.
		 *
		 *  @type object
		 */
		legacy: {
			/**
			 * Enable / disable DataTables 1.9 compatible server-side processing
			 * requests
			 *
			 *  @type boolean
			 *  @default null
			 */
			ajax: null
		},
	
	
		/**
		 * Pagination plug-in methods.
		 * 
		 * Each entry in this object is a function and defines which buttons should
		 * be shown by the pagination rendering method that is used for the table:
		 * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the
		 * buttons are displayed in the document, while the functions here tell it
		 * what buttons to display. This is done by returning an array of button
		 * descriptions (what each button will do).
		 *
		 * Pagination types (the four built in options and any additional plug-in
		 * options defined here) can be used through the `paginationType`
		 * initialisation parameter.
		 *
		 * The functions defined take two parameters:
		 *
		 * 1. `{int} page` The current page index
		 * 2. `{int} pages` The number of pages in the table
		 *
		 * Each function is expected to return an array where each element of the
		 * array can be one of:
		 *
		 * * `first` - Jump to first page when activated
		 * * `last` - Jump to last page when activated
		 * * `previous` - Show previous page when activated
		 * * `next` - Show next page when activated
		 * * `{int}` - Show page of the index given
		 * * `{array}` - A nested array containing the above elements to add a
		 *   containing 'DIV' element (might be useful for styling).
		 *
		 * Note that DataTables v1.9- used this object slightly differently whereby
		 * an object with two functions would be defined for each plug-in. That
		 * ability is still supported by DataTables 1.10+ to provide backwards
		 * compatibility, but this option of use is now decremented and no longer
		 * documented in DataTables 1.10+.
		 *
		 *  @type object
		 *  @default {}
		 *
		 *  @example
		 *    // Show previous, next and current page buttons only
		 *    $.fn.dataTableExt.oPagination.current = function ( page, pages ) {
		 *      return [ 'previous', page, 'next' ];
		 *    };
		 */
		pager: {},
	
	
		renderer: {
			pageButton: {},
			header: {}
		},
	
	
		/**
		 * Ordering plug-ins - custom data source
		 * 
		 * The extension options for ordering of data available here is complimentary
		 * to the default type based ordering that DataTables typically uses. It
		 * allows much greater control over the the data that is being used to
		 * order a column, but is necessarily therefore more complex.
		 * 
		 * This type of ordering is useful if you want to do ordering based on data
		 * live from the DOM (for example the contents of an 'input' element) rather
		 * than just the static string that DataTables knows of.
		 * 
		 * The way these plug-ins work is that you create an array of the values you
		 * wish to be ordering for the column in question and then return that
		 * array. The data in the array much be in the index order of the rows in
		 * the table (not the currently ordering order!). Which order data gathering
		 * function is run here depends on the `dt-init columns.orderDataType`
		 * parameter that is used for the column (if any).
		 *
		 * The functions defined take two parameters:
		 *
		 * 1. `{object}` DataTables settings object: see
		 *    {@link DataTable.models.oSettings}
		 * 2. `{int}` Target column index
		 *
		 * Each function is expected to return an array:
		 *
		 * * `{array}` Data for the column to be ordering upon
		 *
		 *  @type array
		 *
		 *  @example
		 *    // Ordering using `input` node values
		 *    $.fn.dataTable.ext.order['dom-text'] = function  ( settings, col )
		 *    {
		 *      return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) {
		 *        return $('input', td).val();
		 *      } );
		 *    }
		 */
		order: {},
	
	
		/**
		 * Type based plug-ins.
		 *
		 * Each column in DataTables has a type assigned to it, either by automatic
		 * detection or by direct assignment using the `type` option for the column.
		 * The type of a column will effect how it is ordering and search (plug-ins
		 * can also make use of the column type if required).
		 *
		 * @namespace
		 */
		type: {
			/**
			 * Type detection functions.
			 *
			 * The functions defined in this object are used to automatically detect
			 * a column's type, making initialisation of DataTables super easy, even
			 * when complex data is in the table.
			 *
			 * The functions defined take two parameters:
			 *
		     *  1. `{*}` Data from the column cell to be analysed
		     *  2. `{settings}` DataTables settings object. This can be used to
		     *     perform context specific type detection - for example detection
		     *     based on language settings such as using a comma for a decimal
		     *     place. Generally speaking the options from the settings will not
		     *     be required
			 *
			 * Each function is expected to return:
			 *
			 * * `{string|null}` Data type detected, or null if unknown (and thus
			 *   pass it on to the other type detection functions.
			 *
			 *  @type array
			 *
			 *  @example
			 *    // Currency type detection plug-in:
			 *    $.fn.dataTable.ext.type.detect.push(
			 *      function ( data, settings ) {
			 *        // Check the numeric part
			 *        if ( ! $.isNumeric( data.substring(1) ) ) {
			 *          return null;
			 *        }
			 *
			 *        // Check prefixed by currency
			 *        if ( data.charAt(0) == '$' || data.charAt(0) == '&pound;' ) {
			 *          return 'currency';
			 *        }
			 *        return null;
			 *      }
			 *    );
			 */
			detect: [],
	
	
			/**
			 * Type based search formatting.
			 *
			 * The type based searching functions can be used to pre-format the
			 * data to be search on. For example, it can be used to strip HTML
			 * tags or to de-format telephone numbers for numeric only searching.
			 *
			 * Note that is a search is not defined for a column of a given type,
			 * no search formatting will be performed.
			 * 
			 * Pre-processing of searching data plug-ins - When you assign the sType
			 * for a column (or have it automatically detected for you by DataTables
			 * or a type detection plug-in), you will typically be using this for
			 * custom sorting, but it can also be used to provide custom searching
			 * by allowing you to pre-processing the data and returning the data in
			 * the format that should be searched upon. This is done by adding
			 * functions this object with a parameter name which matches the sType
			 * for that target column. This is the corollary of <i>afnSortData</i>
			 * for searching data.
			 *
			 * The functions defined take a single parameter:
			 *
		     *  1. `{*}` Data from the column cell to be prepared for searching
			 *
			 * Each function is expected to return:
			 *
			 * * `{string|null}` Formatted string that will be used for the searching.
			 *
			 *  @type object
			 *  @default {}
			 *
			 *  @example
			 *    $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) {
			 *      return d.replace(/\n/g," ").replace( /<.*?>/g, "" );
			 *    }
			 */
			search: {},
	
	
			/**
			 * Type based ordering.
			 *
			 * The column type tells DataTables what ordering to apply to the table
			 * when a column is sorted upon. The order for each type that is defined,
			 * is defined by the functions available in this object.
			 *
			 * Each ordering option can be described by three properties added to
			 * this object:
			 *
			 * * `{type}-pre` - Pre-formatting function
			 * * `{type}-asc` - Ascending order function
			 * * `{type}-desc` - Descending order function
			 *
			 * All three can be used together, only `{type}-pre` or only
			 * `{type}-asc` and `{type}-desc` together. It is generally recommended
			 * that only `{type}-pre` is used, as this provides the optimal
			 * implementation in terms of speed, although the others are provided
			 * for compatibility with existing Javascript sort functions.
			 *
			 * `{type}-pre`: Functions defined take a single parameter:
			 *
		     *  1. `{*}` Data from the column cell to be prepared for ordering
			 *
			 * And return:
			 *
			 * * `{*}` Data to be sorted upon
			 *
			 * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort
			 * functions, taking two parameters:
			 *
		     *  1. `{*}` Data to compare to the second parameter
		     *  2. `{*}` Data to compare to the first parameter
			 *
			 * And returning:
			 *
			 * * `{*}` Ordering match: <0 if first parameter should be sorted lower
			 *   than the second parameter, ===0 if the two parameters are equal and
			 *   >0 if the first parameter should be sorted height than the second
			 *   parameter.
			 * 
			 *  @type object
			 *  @default {}
			 *
			 *  @example
			 *    // Numeric ordering of formatted numbers with a pre-formatter
			 *    $.extend( $.fn.dataTable.ext.type.order, {
			 *      "string-pre": function(x) {
			 *        a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" );
			 *        return parseFloat( a );
			 *      }
			 *    } );
			 *
			 *  @example
			 *    // Case-sensitive string ordering, with no pre-formatting method
			 *    $.extend( $.fn.dataTable.ext.order, {
			 *      "string-case-asc": function(x,y) {
			 *        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
			 *      },
			 *      "string-case-desc": function(x,y) {
			 *        return ((x < y) ? 1 : ((x > y) ? -1 : 0));
			 *      }
			 *    } );
			 */
			order: {}
		},
	
		/**
		 * Unique DataTables instance counter
		 *
		 * @type int
		 * @private
		 */
		_unique: 0,
	
	
		//
		// Depreciated
		// The following properties are retained for backwards compatiblity only.
		// The should not be used in new projects and will be removed in a future
		// version
		//
	
		/**
		 * Version check function.
		 *  @type function
		 *  @depreciated Since 1.10
		 */
		fnVersionCheck: DataTable.fnVersionCheck,
	
	
		/**
		 * Index for what 'this' index API functions should use
		 *  @type int
		 *  @deprecated Since v1.10
		 */
		iApiIndex: 0,
	
	
		/**
		 * jQuery UI class container
		 *  @type object
		 *  @deprecated Since v1.10
		 */
		oJUIClasses: {},
	
	
		/**
		 * Software version
		 *  @type string
		 *  @deprecated Since v1.10
		 */
		sVersion: DataTable.version
	};
	
	
	//
	// Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts
	//
	$.extend( _ext, {
		afnFiltering: _ext.search,
		aTypes:       _ext.type.detect,
		ofnSearch:    _ext.type.search,
		oSort:        _ext.type.order,
		afnSortData:  _ext.order,
		aoFeatures:   _ext.feature,
		oApi:         _ext.internal,
		oStdClasses:  _ext.classes,
		oPagination:  _ext.pager
	} );
	
	
	$.extend( DataTable.ext.classes, {
		"sTable": "dataTable",
		"sNoFooter": "no-footer",
	
		/* Paging buttons */
		"sPageButton": "paginate_button",
		"sPageButtonActive": "current",
		"sPageButtonDisabled": "disabled",
	
		/* Striping classes */
		"sStripeOdd": "odd",
		"sStripeEven": "even",
	
		/* Empty row */
		"sRowEmpty": "dataTables_empty",
	
		/* Features */
		"sWrapper": "dataTables_wrapper",
		"sFilter": "dataTables_filter",
		"sInfo": "dataTables_info",
		"sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */
		"sLength": "dataTables_length",
		"sProcessing": "dataTables_processing",
	
		/* Sorting */
		"sSortAsc": "sorting_asc",
		"sSortDesc": "sorting_desc",
		"sSortable": "sorting", /* Sortable in both directions */
		"sSortableAsc": "sorting_asc_disabled",
		"sSortableDesc": "sorting_desc_disabled",
		"sSortableNone": "sorting_disabled",
		"sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
	
		/* Filtering */
		"sFilterInput": "",
	
		/* Page length */
		"sLengthSelect": "",
	
		/* Scrolling */
		"sScrollWrapper": "dataTables_scroll",
		"sScrollHead": "dataTables_scrollHead",
		"sScrollHeadInner": "dataTables_scrollHeadInner",
		"sScrollBody": "dataTables_scrollBody",
		"sScrollFoot": "dataTables_scrollFoot",
		"sScrollFootInner": "dataTables_scrollFootInner",
	
		/* Misc */
		"sHeaderTH": "",
		"sFooterTH": "",
	
		// Deprecated
		"sSortJUIAsc": "",
		"sSortJUIDesc": "",
		"sSortJUI": "",
		"sSortJUIAscAllowed": "",
		"sSortJUIDescAllowed": "",
		"sSortJUIWrapper": "",
		"sSortIcon": "",
		"sJUIHeader": "",
		"sJUIFooter": ""
	} );
	
	
	(function() {
	
	// Reused strings for better compression. Closure compiler appears to have a
	// weird edge case where it is trying to expand strings rather than use the
	// variable version. This results in about 200 bytes being added, for very
	// little preference benefit since it this run on script load only.
	var _empty = '';
	_empty = '';
	
	var _stateDefault = _empty + 'ui-state-default';
	var _sortIcon     = _empty + 'css_right ui-icon ui-icon-';
	var _headerFooter = _empty + 'fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix';
	
	$.extend( DataTable.ext.oJUIClasses, DataTable.ext.classes, {
		/* Full numbers paging buttons */
		"sPageButton":         "fg-button ui-button "+_stateDefault,
		"sPageButtonActive":   "ui-state-disabled",
		"sPageButtonDisabled": "ui-state-disabled",
	
		/* Features */
		"sPaging": "dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi "+
			"ui-buttonset-multi paging_", /* Note that the type is postfixed */
	
		/* Sorting */
		"sSortAsc":            _stateDefault+" sorting_asc",
		"sSortDesc":           _stateDefault+" sorting_desc",
		"sSortable":           _stateDefault+" sorting",
		"sSortableAsc":        _stateDefault+" sorting_asc_disabled",
		"sSortableDesc":       _stateDefault+" sorting_desc_disabled",
		"sSortableNone":       _stateDefault+" sorting_disabled",
		"sSortJUIAsc":         _sortIcon+"triangle-1-n",
		"sSortJUIDesc":        _sortIcon+"triangle-1-s",
		"sSortJUI":            _sortIcon+"carat-2-n-s",
		"sSortJUIAscAllowed":  _sortIcon+"carat-1-n",
		"sSortJUIDescAllowed": _sortIcon+"carat-1-s",
		"sSortJUIWrapper":     "DataTables_sort_wrapper",
		"sSortIcon":           "DataTables_sort_icon",
	
		/* Scrolling */
		"sScrollHead": "dataTables_scrollHead "+_stateDefault,
		"sScrollFoot": "dataTables_scrollFoot "+_stateDefault,
	
		/* Misc */
		"sHeaderTH":  _stateDefault,
		"sFooterTH":  _stateDefault,
		"sJUIHeader": _headerFooter+" ui-corner-tl ui-corner-tr",
		"sJUIFooter": _headerFooter+" ui-corner-bl ui-corner-br"
	} );
	
	}());
	
	
	
	var extPagination = DataTable.ext.pager;
	
	function _numbers ( page, pages ) {
		var
			numbers = [],
			buttons = extPagination.numbers_length,
			half = Math.floor( buttons / 2 ),
			i = 1;
	
		if ( pages <= buttons ) {
			numbers = _range( 0, pages );
		}
		else if ( page <= half ) {
			numbers = _range( 0, buttons-2 );
			numbers.push( 'ellipsis' );
			numbers.push( pages-1 );
		}
		else if ( page >= pages - 1 - half ) {
			numbers = _range( pages-(buttons-2), pages );
			numbers.splice( 0, 0, 'ellipsis' ); // no unshift in ie6
			numbers.splice( 0, 0, 0 );
		}
		else {
			numbers = _range( page-half+2, page+half-1 );
			numbers.push( 'ellipsis' );
			numbers.push( pages-1 );
			numbers.splice( 0, 0, 'ellipsis' );
			numbers.splice( 0, 0, 0 );
		}
	
		numbers.DT_el = 'span';
		return numbers;
	}
	
	
	$.extend( extPagination, {
		simple: function ( page, pages ) {
			return [ 'previous', 'next' ];
		},
	
		full: function ( page, pages ) {
			return [  'first', 'previous', 'next', 'last' ];
		},
	
		numbers: function ( page, pages ) {
			return [ _numbers(page, pages) ];
		},
	
		simple_numbers: function ( page, pages ) {
			return [ 'previous', _numbers(page, pages), 'next' ];
		},
	
		full_numbers: function ( page, pages ) {
			return [ 'first', 'previous', _numbers(page, pages), 'next', 'last' ];
		},
	
		// For testing and plug-ins to use
		_numbers: _numbers,
	
		// Number of number buttons (including ellipsis) to show. _Must be odd!_
		numbers_length: 7
	} );
	
	
	$.extend( true, DataTable.ext.renderer, {
		pageButton: {
			_: function ( settings, host, idx, buttons, page, pages ) {
				var classes = settings.oClasses;
				var lang = settings.oLanguage.oPaginate;
				var aria = settings.oLanguage.oAria.paginate || {};
				var btnDisplay, btnClass, counter=0;
	
				var attach = function( container, buttons ) {
					var i, ien, node, button;
					var clickHandler = function ( e ) {
						_fnPageChange( settings, e.data.action, true );
					};
	
					for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
						button = buttons[i];
	
						if ( $.isArray( button ) ) {
							var inner = $( '<'+(button.DT_el || 'div')+'/>' )
								.appendTo( container );
							attach( inner, button );
						}
						else {
							btnDisplay = null;
							btnClass = '';
	
							switch ( button ) {
								case 'ellipsis':
									container.append('<span class="ellipsis">&#x2026;</span>');
									break;
	
								case 'first':
									btnDisplay = lang.sFirst;
									btnClass = button + (page > 0 ?
										'' : ' '+classes.sPageButtonDisabled);
									break;
	
								case 'previous':
									btnDisplay = lang.sPrevious;
									btnClass = button + (page > 0 ?
										'' : ' '+classes.sPageButtonDisabled);
									break;
	
								case 'next':
									btnDisplay = lang.sNext;
									btnClass = button + (page < pages-1 ?
										'' : ' '+classes.sPageButtonDisabled);
									break;
	
								case 'last':
									btnDisplay = lang.sLast;
									btnClass = button + (page < pages-1 ?
										'' : ' '+classes.sPageButtonDisabled);
									break;
	
								default:
									btnDisplay = button + 1;
									btnClass = page === button ?
										classes.sPageButtonActive : '';
									break;
							}
	
							if ( btnDisplay !== null ) {
								node = $('<a>', {
										'class': classes.sPageButton+' '+btnClass,
										'aria-controls': settings.sTableId,
										'aria-label': aria[ button ],
										'data-dt-idx': counter,
										'tabindex': settings.iTabIndex,
										'id': idx === 0 && typeof button === 'string' ?
											settings.sTableId +'_'+ button :
											null
									} )
									.html( btnDisplay )
									.appendTo( container );
	
								_fnBindAction(
									node, {action: button}, clickHandler
								);
	
								counter++;
							}
						}
					}
				};
	
				// IE9 throws an 'unknown error' if document.activeElement is used
				// inside an iframe or frame. Try / catch the error. Not good for
				// accessibility, but neither are frames.
				var activeEl;
	
				try {
					// Because this approach is destroying and recreating the paging
					// elements, focus is lost on the select button which is bad for
					// accessibility. So we want to restore focus once the draw has
					// completed
					activeEl = $(host).find(document.activeElement).data('dt-idx');
				}
				catch (e) {}
	
				attach( $(host).empty(), buttons );
	
				if ( activeEl ) {
					$(host).find( '[data-dt-idx='+activeEl+']' ).focus();
				}
			}
		}
	} );
	
	
	
	// Built in type detection. See model.ext.aTypes for information about
	// what is required from this methods.
	$.extend( DataTable.ext.type.detect, [
		// Plain numbers - first since V8 detects some plain numbers as dates
		// e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...).
		function ( d, settings )
		{
			var decimal = settings.oLanguage.sDecimal;
			return _isNumber( d, decimal ) ? 'num'+decimal : null;
		},
	
		// Dates (only those recognised by the browser's Date.parse)
		function ( d, settings )
		{
			// V8 will remove any unknown characters at the start and end of the
			// expression, leading to false matches such as `$245.12` or `10%` being
			// a valid date. See forum thread 18941 for detail.
			if ( d && !(d instanceof Date) && ( ! _re_date_start.test(d) || ! _re_date_end.test(d) ) ) {
				return null;
			}
			var parsed = Date.parse(d);
			return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null;
		},
	
		// Formatted numbers
		function ( d, settings )
		{
			var decimal = settings.oLanguage.sDecimal;
			return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null;
		},
	
		// HTML numeric
		function ( d, settings )
		{
			var decimal = settings.oLanguage.sDecimal;
			return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null;
		},
	
		// HTML numeric, formatted
		function ( d, settings )
		{
			var decimal = settings.oLanguage.sDecimal;
			return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null;
		},
	
		// HTML (this is strict checking - there must be html)
		function ( d, settings )
		{
			return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ?
				'html' : null;
		}
	] );
	
	
	
	// Filter formatting functions. See model.ext.ofnSearch for information about
	// what is required from these methods.
	// 
	// Note that additional search methods are added for the html numbers and
	// html formatted numbers by `_addNumericSort()` when we know what the decimal
	// place is
	
	
	$.extend( DataTable.ext.type.search, {
		html: function ( data ) {
			return _empty(data) ?
				data :
				typeof data === 'string' ?
					data
						.replace( _re_new_lines, " " )
						.replace( _re_html, "" ) :
					'';
		},
	
		string: function ( data ) {
			return _empty(data) ?
				data :
				typeof data === 'string' ?
					data.replace( _re_new_lines, " " ) :
					data;
		}
	} );
	
	
	
	var __numericReplace = function ( d, decimalPlace, re1, re2 ) {
		if ( d !== 0 && (!d || d === '-') ) {
			return -Infinity;
		}
	
		// If a decimal place other than `.` is used, it needs to be given to the
		// function so we can detect it and replace with a `.` which is the only
		// decimal place Javascript recognises - it is not locale aware.
		if ( decimalPlace ) {
			d = _numToDecimal( d, decimalPlace );
		}
	
		if ( d.replace ) {
			if ( re1 ) {
				d = d.replace( re1, '' );
			}
	
			if ( re2 ) {
				d = d.replace( re2, '' );
			}
		}
	
		return d * 1;
	};
	
	
	// Add the numeric 'deformatting' functions for sorting and search. This is done
	// in a function to provide an easy ability for the language options to add
	// additional methods if a non-period decimal place is used.
	function _addNumericSort ( decimalPlace ) {
		$.each(
			{
				// Plain numbers
				"num": function ( d ) {
					return __numericReplace( d, decimalPlace );
				},
	
				// Formatted numbers
				"num-fmt": function ( d ) {
					return __numericReplace( d, decimalPlace, _re_formatted_numeric );
				},
	
				// HTML numeric
				"html-num": function ( d ) {
					return __numericReplace( d, decimalPlace, _re_html );
				},
	
				// HTML numeric, formatted
				"html-num-fmt": function ( d ) {
					return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric );
				}
			},
			function ( key, fn ) {
				// Add the ordering method
				_ext.type.order[ key+decimalPlace+'-pre' ] = fn;
	
				// For HTML types add a search formatter that will strip the HTML
				if ( key.match(/^html\-/) ) {
					_ext.type.search[ key+decimalPlace ] = _ext.type.search.html;
				}
			}
		);
	}
	
	
	// Default sort methods
	$.extend( _ext.type.order, {
		// Dates
		"date-pre": function ( d ) {
			return Date.parse( d ) || 0;
		},
	
		// html
		"html-pre": function ( a ) {
			return _empty(a) ?
				'' :
				a.replace ?
					a.replace( /<.*?>/g, "" ).toLowerCase() :
					a+'';
		},
	
		// string
		"string-pre": function ( a ) {
			// This is a little complex, but faster than always calling toString,
			// http://jsperf.com/tostring-v-check
			return _empty(a) ?
				'' :
				typeof a === 'string' ?
					a.toLowerCase() :
					! a.toString ?
						'' :
						a.toString();
		},
	
		// string-asc and -desc are retained only for compatibility with the old
		// sort methods
		"string-asc": function ( x, y ) {
			return ((x < y) ? -1 : ((x > y) ? 1 : 0));
		},
	
		"string-desc": function ( x, y ) {
			return ((x < y) ? 1 : ((x > y) ? -1 : 0));
		}
	} );
	
	
	// Numeric sorting types - order doesn't matter here
	_addNumericSort( '' );
	
	
	$.extend( true, DataTable.ext.renderer, {
		header: {
			_: function ( settings, cell, column, classes ) {
				// No additional mark-up required
				// Attach a sort listener to update on sort - note that using the
				// `DT` namespace will allow the event to be removed automatically
				// on destroy, while the `dt` namespaced event is the one we are
				// listening for
				$(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) {
					if ( settings !== ctx ) { // need to check this this is the host
						return;               // table, not a nested one
					}
	
					var colIdx = column.idx;
	
					cell
						.removeClass(
							column.sSortingClass +' '+
							classes.sSortAsc +' '+
							classes.sSortDesc
						)
						.addClass( columns[ colIdx ] == 'asc' ?
							classes.sSortAsc : columns[ colIdx ] == 'desc' ?
								classes.sSortDesc :
								column.sSortingClass
						);
				} );
			},
	
			jqueryui: function ( settings, cell, column, classes ) {
				$('<div/>')
					.addClass( classes.sSortJUIWrapper )
					.append( cell.contents() )
					.append( $('<span/>')
						.addClass( classes.sSortIcon+' '+column.sSortingClassJUI )
					)
					.appendTo( cell );
	
				// Attach a sort listener to update on sort
				$(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) {
					if ( settings !== ctx ) {
						return;
					}
	
					var colIdx = column.idx;
	
					cell
						.removeClass( classes.sSortAsc +" "+classes.sSortDesc )
						.addClass( columns[ colIdx ] == 'asc' ?
							classes.sSortAsc : columns[ colIdx ] == 'desc' ?
								classes.sSortDesc :
								column.sSortingClass
						);
	
					cell
						.find( 'span.'+classes.sSortIcon )
						.removeClass(
							classes.sSortJUIAsc +" "+
							classes.sSortJUIDesc +" "+
							classes.sSortJUI +" "+
							classes.sSortJUIAscAllowed +" "+
							classes.sSortJUIDescAllowed
						)
						.addClass( columns[ colIdx ] == 'asc' ?
							classes.sSortJUIAsc : columns[ colIdx ] == 'desc' ?
								classes.sSortJUIDesc :
								column.sSortingClassJUI
						);
				} );
			}
		}
	} );
	
	/*
	 * Public helper functions. These aren't used internally by DataTables, or
	 * called by any of the options passed into DataTables, but they can be used
	 * externally by developers working with DataTables. They are helper functions
	 * to make working with DataTables a little bit easier.
	 */
	
	/**
	 * Helpers for `columns.render`.
	 *
	 * The options defined here can be used with the `columns.render` initialisation
	 * option to provide a display renderer. The following functions are defined:
	 *
	 * * `number` - Will format numeric data (defined by `columns.data`) for
	 *   display, retaining the original unformatted data for sorting and filtering.
	 *   It takes 5 parameters:
	 *   * `string` - Thousands grouping separator
	 *   * `string` - Decimal point indicator
	 *   * `integer` - Number of decimal points to show
	 *   * `string` (optional) - Prefix.
	 *   * `string` (optional) - Postfix (/suffix).
	 * * `text` - Escape HTML to help prevent XSS attacks. It has no optional
	 *   parameters.
	 *
	 * @example
	 *   // Column definition using the number renderer
	 *   {
	 *     data: "salary",
	 *     render: $.fn.dataTable.render.number( '\'', '.', 0, '$' )
	 *   }
	 *
	 * @namespace
	 */
	DataTable.render = {
		number: function ( thousands, decimal, precision, prefix, postfix ) {
			return {
				display: function ( d ) {
					if ( typeof d !== 'number' && typeof d !== 'string' ) {
						return d;
					}
	
					var negative = d < 0 ? '-' : '';
					var flo = parseFloat( d );
	
					// If NaN then there isn't much formatting that we can do - just
					// return immediately
					if ( isNaN( flo ) ) {
						return d;
					}
	
					d = Math.abs( flo );
	
					var intPart = parseInt( d, 10 );
					var floatPart = precision ?
						decimal+(d - intPart).toFixed( precision ).substring( 2 ):
						'';
	
					return negative + (prefix||'') +
						intPart.toString().replace(
							/\B(?=(\d{3})+(?!\d))/g, thousands
						) +
						floatPart +
						(postfix||'');
				}
			};
		},
	
		text: function () {
			return {
				display: function ( d ) {
					return typeof d === 'string' ?
						d.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;') :
						d;
				}
			};
		}
	};
	
	
	/*
	 * This is really a good bit rubbish this method of exposing the internal methods
	 * publicly... - To be fixed in 2.0 using methods on the prototype
	 */
	
	
	/**
	 * Create a wrapper function for exporting an internal functions to an external API.
	 *  @param {string} fn API function name
	 *  @returns {function} wrapped function
	 *  @memberof DataTable#internal
	 */
	function _fnExternApiFunc (fn)
	{
		return function() {
			var args = [_fnSettingsFromNode( this[DataTable.ext.iApiIndex] )].concat(
				Array.prototype.slice.call(arguments)
			);
			return DataTable.ext.internal[fn].apply( this, args );
		};
	}
	
	
	/**
	 * Reference to internal functions for use by plug-in developers. Note that
	 * these methods are references to internal functions and are considered to be
	 * private. If you use these methods, be aware that they are liable to change
	 * between versions.
	 *  @namespace
	 */
	$.extend( DataTable.ext.internal, {
		_fnExternApiFunc: _fnExternApiFunc,
		_fnBuildAjax: _fnBuildAjax,
		_fnAjaxUpdate: _fnAjaxUpdate,
		_fnAjaxParameters: _fnAjaxParameters,
		_fnAjaxUpdateDraw: _fnAjaxUpdateDraw,
		_fnAjaxDataSrc: _fnAjaxDataSrc,
		_fnAddColumn: _fnAddColumn,
		_fnColumnOptions: _fnColumnOptions,
		_fnAdjustColumnSizing: _fnAdjustColumnSizing,
		_fnVisibleToColumnIndex: _fnVisibleToColumnIndex,
		_fnColumnIndexToVisible: _fnColumnIndexToVisible,
		_fnVisbleColumns: _fnVisbleColumns,
		_fnGetColumns: _fnGetColumns,
		_fnColumnTypes: _fnColumnTypes,
		_fnApplyColumnDefs: _fnApplyColumnDefs,
		_fnHungarianMap: _fnHungarianMap,
		_fnCamelToHungarian: _fnCamelToHungarian,
		_fnLanguageCompat: _fnLanguageCompat,
		_fnBrowserDetect: _fnBrowserDetect,
		_fnAddData: _fnAddData,
		_fnAddTr: _fnAddTr,
		_fnNodeToDataIndex: _fnNodeToDataIndex,
		_fnNodeToColumnIndex: _fnNodeToColumnIndex,
		_fnGetCellData: _fnGetCellData,
		_fnSetCellData: _fnSetCellData,
		_fnSplitObjNotation: _fnSplitObjNotation,
		_fnGetObjectDataFn: _fnGetObjectDataFn,
		_fnSetObjectDataFn: _fnSetObjectDataFn,
		_fnGetDataMaster: _fnGetDataMaster,
		_fnClearTable: _fnClearTable,
		_fnDeleteIndex: _fnDeleteIndex,
		_fnInvalidate: _fnInvalidate,
		_fnGetRowElements: _fnGetRowElements,
		_fnCreateTr: _fnCreateTr,
		_fnBuildHead: _fnBuildHead,
		_fnDrawHead: _fnDrawHead,
		_fnDraw: _fnDraw,
		_fnReDraw: _fnReDraw,
		_fnAddOptionsHtml: _fnAddOptionsHtml,
		_fnDetectHeader: _fnDetectHeader,
		_fnGetUniqueThs: _fnGetUniqueThs,
		_fnFeatureHtmlFilter: _fnFeatureHtmlFilter,
		_fnFilterComplete: _fnFilterComplete,
		_fnFilterCustom: _fnFilterCustom,
		_fnFilterColumn: _fnFilterColumn,
		_fnFilter: _fnFilter,
		_fnFilterCreateSearch: _fnFilterCreateSearch,
		_fnEscapeRegex: _fnEscapeRegex,
		_fnFilterData: _fnFilterData,
		_fnFeatureHtmlInfo: _fnFeatureHtmlInfo,
		_fnUpdateInfo: _fnUpdateInfo,
		_fnInfoMacros: _fnInfoMacros,
		_fnInitialise: _fnInitialise,
		_fnInitComplete: _fnInitComplete,
		_fnLengthChange: _fnLengthChange,
		_fnFeatureHtmlLength: _fnFeatureHtmlLength,
		_fnFeatureHtmlPaginate: _fnFeatureHtmlPaginate,
		_fnPageChange: _fnPageChange,
		_fnFeatureHtmlProcessing: _fnFeatureHtmlProcessing,
		_fnProcessingDisplay: _fnProcessingDisplay,
		_fnFeatureHtmlTable: _fnFeatureHtmlTable,
		_fnScrollDraw: _fnScrollDraw,
		_fnApplyToChildren: _fnApplyToChildren,
		_fnCalculateColumnWidths: _fnCalculateColumnWidths,
		_fnThrottle: _fnThrottle,
		_fnConvertToWidth: _fnConvertToWidth,
		_fnGetWidestNode: _fnGetWidestNode,
		_fnGetMaxLenString: _fnGetMaxLenString,
		_fnStringToCss: _fnStringToCss,
		_fnSortFlatten: _fnSortFlatten,
		_fnSort: _fnSort,
		_fnSortAria: _fnSortAria,
		_fnSortListener: _fnSortListener,
		_fnSortAttachListener: _fnSortAttachListener,
		_fnSortingClasses: _fnSortingClasses,
		_fnSortData: _fnSortData,
		_fnSaveState: _fnSaveState,
		_fnLoadState: _fnLoadState,
		_fnSettingsFromNode: _fnSettingsFromNode,
		_fnLog: _fnLog,
		_fnMap: _fnMap,
		_fnBindAction: _fnBindAction,
		_fnCallbackReg: _fnCallbackReg,
		_fnCallbackFire: _fnCallbackFire,
		_fnLengthOverflow: _fnLengthOverflow,
		_fnRenderer: _fnRenderer,
		_fnDataSource: _fnDataSource,
		_fnRowAttributes: _fnRowAttributes,
		_fnCalculateEnd: function () {} // Used by a lot of plug-ins, but redundant
		                                // in 1.10, so this dead-end function is
		                                // added to prevent errors
	} );
	

	// jQuery access
	$.fn.dataTable = DataTable;

	// Provide access to the host jQuery object (circular reference)
	DataTable.$ = $;

	// Legacy aliases
	$.fn.dataTableSettings = DataTable.settings;
	$.fn.dataTableExt = DataTable.ext;

	// With a capital `D` we return a DataTables API instance rather than a
	// jQuery object
	$.fn.DataTable = function ( opts ) {
		return $(this).dataTable( opts ).api();
	};

	// All properties that are available to $.fn.dataTable should also be
	// available on $.fn.DataTable
	$.each( DataTable, function ( prop, val ) {
		$.fn.DataTable[ prop ] = val;
	} );


	// Information about events fired by DataTables - for documentation.
	/**
	 * Draw event, fired whenever the table is redrawn on the page, at the same
	 * point as fnDrawCallback. This may be useful for binding events or
	 * performing calculations when the table is altered at all.
	 *  @name DataTable#draw.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * Search event, fired when the searching applied to the table (using the
	 * built-in global search, or column filters) is altered.
	 *  @name DataTable#search.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * Page change event, fired when the paging of the table is altered.
	 *  @name DataTable#page.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * Order event, fired when the ordering applied to the table is altered.
	 *  @name DataTable#order.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * DataTables initialisation complete event, fired when the table is fully
	 * drawn, including Ajax data loaded, if Ajax data is required.
	 *  @name DataTable#init.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {object} json The JSON object request from the server - only
	 *    present if client-side Ajax sourced data is used</li></ol>
	 */

	/**
	 * State save event, fired when the table has changed state a new state save
	 * is required. This event allows modification of the state saving object
	 * prior to actually doing the save, including addition or other state
	 * properties (for plug-ins) or modification of a DataTables core property.
	 *  @name DataTable#stateSaveParams.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {object} json The state information to be saved
	 */

	/**
	 * State load event, fired when the table is loading state from the stored
	 * data, but prior to the settings object being modified by the saved state
	 * - allowing modification of the saved state is required or loading of
	 * state for a plug-in.
	 *  @name DataTable#stateLoadParams.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {object} json The saved state information
	 */

	/**
	 * State loaded event, fired when state has been loaded from stored data and
	 * the settings object has been modified by the loaded data.
	 *  @name DataTable#stateLoaded.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {object} json The saved state information
	 */

	/**
	 * Processing event, fired when DataTables is doing some kind of processing
	 * (be it, order, searcg or anything else). It can be used to indicate to
	 * the end user that there is something happening, or that something has
	 * finished.
	 *  @name DataTable#processing.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} oSettings DataTables settings object
	 *  @param {boolean} bShow Flag for if DataTables is doing processing or not
	 */

	/**
	 * Ajax (XHR) event, fired whenever an Ajax request is completed from a
	 * request to made to the server for new data. This event is called before
	 * DataTables processed the returned data, so it can also be used to pre-
	 * process the data returned from the server, if needed.
	 *
	 * Note that this trigger is called in `fnServerData`, if you override
	 * `fnServerData` and which to use this event, you need to trigger it in you
	 * success function.
	 *  @name DataTable#xhr.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 *  @param {object} json JSON returned from the server
	 *
	 *  @example
	 *     // Use a custom property returned from the server in another DOM element
	 *     $('#table').dataTable().on('xhr.dt', function (e, settings, json) {
	 *       $('#status').html( json.status );
	 *     } );
	 *
	 *  @example
	 *     // Pre-process the data returned from the server
	 *     $('#table').dataTable().on('xhr.dt', function (e, settings, json) {
	 *       for ( var i=0, ien=json.aaData.length ; i<ien ; i++ ) {
	 *         json.aaData[i].sum = json.aaData[i].one + json.aaData[i].two;
	 *       }
	 *       // Note no return - manipulate the data directly in the JSON object.
	 *     } );
	 */

	/**
	 * Destroy event, fired when the DataTable is destroyed by calling fnDestroy
	 * or passing the bDestroy:true parameter in the initialisation object. This
	 * can be used to remove bound events, added DOM nodes, etc.
	 *  @name DataTable#destroy.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * Page length change event, fired when number of records to show on each
	 * page (the length) is changed.
	 *  @name DataTable#length.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 *  @param {integer} len New length
	 */

	/**
	 * Column sizing has changed.
	 *  @name DataTable#column-sizing.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 */

	/**
	 * Column visibility has changed.
	 *  @name DataTable#column-visibility.dt
	 *  @event
	 *  @param {event} e jQuery event object
	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
	 *  @param {int} column Column index
	 *  @param {bool} vis `false` if column now hidden, or `true` if visible
	 */

	return $.fn.dataTable;
}));
if(!document.createElement("canvas").getContext){(function(){var ab=Math;var n=ab.round;var l=ab.sin;var A=ab.cos;var H=ab.abs;var N=ab.sqrt;var d=10;var f=d/2;var z=+navigator.userAgent.match(/MSIE ([\d.]+)?/)[1];function y(){return this.context_||(this.context_=new D(this))}var t=Array.prototype.slice;function g(j,m,p){var i=t.call(arguments,2);return function(){return j.apply(m,i.concat(t.call(arguments)))}}function af(i){return String(i).replace(/&/g,"&amp;").replace(/"/g,"&quot;")}function Y(m,j,i){if(!m.namespaces[j]){m.namespaces.add(j,i,"#default#VML")}}function R(j){Y(j,"g_vml_","urn:schemas-microsoft-com:vml");Y(j,"g_o_","urn:schemas-microsoft-com:office:office");if(!j.styleSheets.ex_canvas_){var i=j.createStyleSheet();i.owningElement.id="ex_canvas_";i.cssText="canvas{display:inline-block;overflow:hidden;text-align:left;width:300px;height:150px}"}}R(document);var e={init:function(i){var j=i||document;j.createElement("canvas");j.attachEvent("onreadystatechange",g(this.init_,this,j))},init_:function(p){var m=p.getElementsByTagName("canvas");for(var j=0;j<m.length;j++){this.initElement(m[j])}},initElement:function(j){if(!j.getContext){j.getContext=y;R(j.ownerDocument);j.innerHTML="";j.attachEvent("onpropertychange",x);j.attachEvent("onresize",W);var i=j.attributes;if(i.width&&i.width.specified){j.style.width=i.width.nodeValue+"px"}else{j.width=j.clientWidth}if(i.height&&i.height.specified){j.style.height=i.height.nodeValue+"px"}else{j.height=j.clientHeight}}return j}};function x(j){var i=j.srcElement;switch(j.propertyName){case"width":i.getContext().clearRect();i.style.width=i.attributes.width.nodeValue+"px";i.firstChild.style.width=i.clientWidth+"px";break;case"height":i.getContext().clearRect();i.style.height=i.attributes.height.nodeValue+"px";i.firstChild.style.height=i.clientHeight+"px";break}}function W(j){var i=j.srcElement;if(i.firstChild){i.firstChild.style.width=i.clientWidth+"px";i.firstChild.style.height=i.clientHeight+"px"}}e.init();var k=[];for(var ae=0;ae<16;ae++){for(var ad=0;ad<16;ad++){k[ae*16+ad]=ae.toString(16)+ad.toString(16)}}function B(){return[[1,0,0],[0,1,0],[0,0,1]]}function J(p,m){var j=B();for(var i=0;i<3;i++){for(var ah=0;ah<3;ah++){var Z=0;for(var ag=0;ag<3;ag++){Z+=p[i][ag]*m[ag][ah]}j[i][ah]=Z}}return j}function v(j,i){i.fillStyle=j.fillStyle;i.lineCap=j.lineCap;i.lineJoin=j.lineJoin;i.lineWidth=j.lineWidth;i.miterLimit=j.miterLimit;i.shadowBlur=j.shadowBlur;i.shadowColor=j.shadowColor;i.shadowOffsetX=j.shadowOffsetX;i.shadowOffsetY=j.shadowOffsetY;i.strokeStyle=j.strokeStyle;i.globalAlpha=j.globalAlpha;i.font=j.font;i.textAlign=j.textAlign;i.textBaseline=j.textBaseline;i.arcScaleX_=j.arcScaleX_;i.arcScaleY_=j.arcScaleY_;i.lineScale_=j.lineScale_}var b={aliceblue:"#F0F8FF",antiquewhite:"#FAEBD7",aquamarine:"#7FFFD4",azure:"#F0FFFF",beige:"#F5F5DC",bisque:"#FFE4C4",black:"#000000",blanchedalmond:"#FFEBCD",blueviolet:"#8A2BE2",brown:"#A52A2A",burlywood:"#DEB887",cadetblue:"#5F9EA0",chartreuse:"#7FFF00",chocolate:"#D2691E",coral:"#FF7F50",cornflowerblue:"#6495ED",cornsilk:"#FFF8DC",crimson:"#DC143C",cyan:"#00FFFF",darkblue:"#00008B",darkcyan:"#008B8B",darkgoldenrod:"#B8860B",darkgray:"#A9A9A9",darkgreen:"#006400",darkgrey:"#A9A9A9",darkkhaki:"#BDB76B",darkmagenta:"#8B008B",darkolivegreen:"#556B2F",darkorange:"#FF8C00",darkorchid:"#9932CC",darkred:"#8B0000",darksalmon:"#E9967A",darkseagreen:"#8FBC8F",darkslateblue:"#483D8B",darkslategray:"#2F4F4F",darkslategrey:"#2F4F4F",darkturquoise:"#00CED1",darkviolet:"#9400D3",deeppink:"#FF1493",deepskyblue:"#00BFFF",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1E90FF",firebrick:"#B22222",floralwhite:"#FFFAF0",forestgreen:"#228B22",gainsboro:"#DCDCDC",ghostwhite:"#F8F8FF",gold:"#FFD700",goldenrod:"#DAA520",grey:"#808080",greenyellow:"#ADFF2F",honeydew:"#F0FFF0",hotpink:"#FF69B4",indianred:"#CD5C5C",indigo:"#4B0082",ivory:"#FFFFF0",khaki:"#F0E68C",lavender:"#E6E6FA",lavenderblush:"#FFF0F5",lawngreen:"#7CFC00",lemonchiffon:"#FFFACD",lightblue:"#ADD8E6",lightcoral:"#F08080",lightcyan:"#E0FFFF",lightgoldenrodyellow:"#FAFAD2",lightgreen:"#90EE90",lightgrey:"#D3D3D3",lightpink:"#FFB6C1",lightsalmon:"#FFA07A",lightseagreen:"#20B2AA",lightskyblue:"#87CEFA",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#B0C4DE",lightyellow:"#FFFFE0",limegreen:"#32CD32",linen:"#FAF0E6",magenta:"#FF00FF",mediumaquamarine:"#66CDAA",mediumblue:"#0000CD",mediumorchid:"#BA55D3",mediumpurple:"#9370DB",mediumseagreen:"#3CB371",mediumslateblue:"#7B68EE",mediumspringgreen:"#00FA9A",mediumturquoise:"#48D1CC",mediumvioletred:"#C71585",midnightblue:"#191970",mintcream:"#F5FFFA",mistyrose:"#FFE4E1",moccasin:"#FFE4B5",navajowhite:"#FFDEAD",oldlace:"#FDF5E6",olivedrab:"#6B8E23",orange:"#FFA500",orangered:"#FF4500",orchid:"#DA70D6",palegoldenrod:"#EEE8AA",palegreen:"#98FB98",paleturquoise:"#AFEEEE",palevioletred:"#DB7093",papayawhip:"#FFEFD5",peachpuff:"#FFDAB9",peru:"#CD853F",pink:"#FFC0CB",plum:"#DDA0DD",powderblue:"#B0E0E6",rosybrown:"#BC8F8F",royalblue:"#4169E1",saddlebrown:"#8B4513",salmon:"#FA8072",sandybrown:"#F4A460",seagreen:"#2E8B57",seashell:"#FFF5EE",sienna:"#A0522D",skyblue:"#87CEEB",slateblue:"#6A5ACD",slategray:"#708090",slategrey:"#708090",snow:"#FFFAFA",springgreen:"#00FF7F",steelblue:"#4682B4",tan:"#D2B48C",thistle:"#D8BFD8",tomato:"#FF6347",turquoise:"#40E0D0",violet:"#EE82EE",wheat:"#F5DEB3",whitesmoke:"#F5F5F5",yellowgreen:"#9ACD32"};function M(j){var p=j.indexOf("(",3);var i=j.indexOf(")",p+1);var m=j.substring(p+1,i).split(",");if(m.length!=4||j.charAt(3)!="a"){m[3]=1}return m}function c(i){return parseFloat(i)/100}function r(j,m,i){return Math.min(i,Math.max(m,j))}function I(ag){var i,ai,aj,ah,ak,Z;ah=parseFloat(ag[0])/360%360;if(ah<0){ah++}ak=r(c(ag[1]),0,1);Z=r(c(ag[2]),0,1);if(ak==0){i=ai=aj=Z}else{var j=Z<0.5?Z*(1+ak):Z+ak-Z*ak;var m=2*Z-j;i=a(m,j,ah+1/3);ai=a(m,j,ah);aj=a(m,j,ah-1/3)}return"#"+k[Math.floor(i*255)]+k[Math.floor(ai*255)]+k[Math.floor(aj*255)]}function a(j,i,m){if(m<0){m++}if(m>1){m--}if(6*m<1){return j+(i-j)*6*m}else{if(2*m<1){return i}else{if(3*m<2){return j+(i-j)*(2/3-m)*6}else{return j}}}}var C={};function F(j){if(j in C){return C[j]}var ag,Z=1;j=String(j);if(j.charAt(0)=="#"){ag=j}else{if(/^rgb/.test(j)){var p=M(j);var ag="#",ah;for(var m=0;m<3;m++){if(p[m].indexOf("%")!=-1){ah=Math.floor(c(p[m])*255)}else{ah=+p[m]}ag+=k[r(ah,0,255)]}Z=+p[3]}else{if(/^hsl/.test(j)){var p=M(j);ag=I(p);Z=p[3]}else{ag=b[j]||j}}}return C[j]={color:ag,alpha:Z}}var o={style:"normal",variant:"normal",weight:"normal",size:10,family:"sans-serif"};var L={};function E(i){if(L[i]){return L[i]}var p=document.createElement("div");var m=p.style;try{m.font=i}catch(j){}return L[i]={style:m.fontStyle||o.style,variant:m.fontVariant||o.variant,weight:m.fontWeight||o.weight,size:m.fontSize||o.size,family:m.fontFamily||o.family}}function u(m,j){var i={};for(var ah in m){i[ah]=m[ah]}var ag=parseFloat(j.currentStyle.fontSize),Z=parseFloat(m.size);if(typeof m.size=="number"){i.size=m.size}else{if(m.size.indexOf("px")!=-1){i.size=Z}else{if(m.size.indexOf("em")!=-1){i.size=ag*Z}else{if(m.size.indexOf("%")!=-1){i.size=(ag/100)*Z}else{if(m.size.indexOf("pt")!=-1){i.size=Z/0.75}else{i.size=ag}}}}}i.size*=0.981;return i}function ac(i){return i.style+" "+i.variant+" "+i.weight+" "+i.size+"px "+i.family}var s={butt:"flat",round:"round"};function S(i){return s[i]||"square"}function D(i){this.m_=B();this.mStack_=[];this.aStack_=[];this.currentPath_=[];this.strokeStyle="#000";this.fillStyle="#000";this.lineWidth=1;this.lineJoin="miter";this.lineCap="butt";this.miterLimit=d*1;this.globalAlpha=1;this.font="10px sans-serif";this.textAlign="left";this.textBaseline="alphabetic";this.canvas=i;var m="width:"+i.clientWidth+"px;height:"+i.clientHeight+"px;overflow:hidden;position:absolute";var j=i.ownerDocument.createElement("div");j.style.cssText=m;i.appendChild(j);var p=j.cloneNode(false);p.style.backgroundColor="red";p.style.filter="alpha(opacity=0)";i.appendChild(p);this.element_=j;this.arcScaleX_=1;this.arcScaleY_=1;this.lineScale_=1}var q=D.prototype;q.clearRect=function(){if(this.textMeasureEl_){this.textMeasureEl_.removeNode(true);this.textMeasureEl_=null}this.element_.innerHTML=""};q.beginPath=function(){this.currentPath_=[]};q.moveTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"moveTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.lineTo=function(j,i){var m=V(this,j,i);this.currentPath_.push({type:"lineTo",x:m.x,y:m.y});this.currentX_=m.x;this.currentY_=m.y};q.bezierCurveTo=function(m,j,ak,aj,ai,ag){var i=V(this,ai,ag);var ah=V(this,m,j);var Z=V(this,ak,aj);K(this,ah,Z,i)};function K(i,Z,m,j){i.currentPath_.push({type:"bezierCurveTo",cp1x:Z.x,cp1y:Z.y,cp2x:m.x,cp2y:m.y,x:j.x,y:j.y});i.currentX_=j.x;i.currentY_=j.y}q.quadraticCurveTo=function(ai,m,j,i){var ah=V(this,ai,m);var ag=V(this,j,i);var aj={x:this.currentX_+2/3*(ah.x-this.currentX_),y:this.currentY_+2/3*(ah.y-this.currentY_)};var Z={x:aj.x+(ag.x-this.currentX_)/3,y:aj.y+(ag.y-this.currentY_)/3};K(this,aj,Z,ag)};q.arc=function(al,aj,ak,ag,j,m){ak*=d;var ap=m?"at":"wa";var am=al+A(ag)*ak-f;var ao=aj+l(ag)*ak-f;var i=al+A(j)*ak-f;var an=aj+l(j)*ak-f;if(am==i&&!m){am+=0.125}var Z=V(this,al,aj);var ai=V(this,am,ao);var ah=V(this,i,an);this.currentPath_.push({type:ap,x:Z.x,y:Z.y,radius:ak,xStart:ai.x,yStart:ai.y,xEnd:ah.x,yEnd:ah.y})};q.rect=function(m,j,i,p){this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath()};q.strokeRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.stroke();this.currentPath_=Z};q.fillRect=function(m,j,i,p){var Z=this.currentPath_;this.beginPath();this.moveTo(m,j);this.lineTo(m+i,j);this.lineTo(m+i,j+p);this.lineTo(m,j+p);this.closePath();this.fill();this.currentPath_=Z};q.createLinearGradient=function(j,p,i,m){var Z=new U("gradient");Z.x0_=j;Z.y0_=p;Z.x1_=i;Z.y1_=m;return Z};q.createRadialGradient=function(p,ag,m,j,Z,i){var ah=new U("gradientradial");ah.x0_=p;ah.y0_=ag;ah.r0_=m;ah.x1_=j;ah.y1_=Z;ah.r1_=i;return ah};q.drawImage=function(aq,m){var aj,ah,al,ay,ao,am,at,aA;var ak=aq.runtimeStyle.width;var ap=aq.runtimeStyle.height;aq.runtimeStyle.width="auto";aq.runtimeStyle.height="auto";var ai=aq.width;var aw=aq.height;aq.runtimeStyle.width=ak;aq.runtimeStyle.height=ap;if(arguments.length==3){aj=arguments[1];ah=arguments[2];ao=am=0;at=al=ai;aA=ay=aw}else{if(arguments.length==5){aj=arguments[1];ah=arguments[2];al=arguments[3];ay=arguments[4];ao=am=0;at=ai;aA=aw}else{if(arguments.length==9){ao=arguments[1];am=arguments[2];at=arguments[3];aA=arguments[4];aj=arguments[5];ah=arguments[6];al=arguments[7];ay=arguments[8]}else{throw Error("Invalid number of arguments")}}}var az=V(this,aj,ah);var p=at/2;var j=aA/2;var ax=[];var i=10;var ag=10;ax.push(" <g_vml_:group",' coordsize="',d*i,",",d*ag,'"',' coordorigin="0,0"',' style="width:',i,"px;height:",ag,"px;position:absolute;");if(this.m_[0][0]!=1||this.m_[0][1]||this.m_[1][1]!=1||this.m_[1][0]){var Z=[];Z.push("M11=",this.m_[0][0],",","M12=",this.m_[1][0],",","M21=",this.m_[0][1],",","M22=",this.m_[1][1],",","Dx=",n(az.x/d),",","Dy=",n(az.y/d),"");var av=az;var au=V(this,aj+al,ah);var ar=V(this,aj,ah+ay);var an=V(this,aj+al,ah+ay);av.x=ab.max(av.x,au.x,ar.x,an.x);av.y=ab.max(av.y,au.y,ar.y,an.y);ax.push("padding:0 ",n(av.x/d),"px ",n(av.y/d),"px 0;filter:progid:DXImageTransform.Microsoft.Matrix(",Z.join(""),", sizingmethod='clip');")}else{ax.push("top:",n(az.y/d),"px;left:",n(az.x/d),"px;")}ax.push(' ">','<g_vml_:image src="',aq.src,'"',' style="width:',d*al,"px;"," height:",d*ay,'px"',' cropleft="',ao/ai,'"',' croptop="',am/aw,'"',' cropright="',(ai-ao-at)/ai,'"',' cropbottom="',(aw-am-aA)/aw,'"'," />","</g_vml_:group>");this.element_.insertAdjacentHTML("BeforeEnd",ax.join(""))};q.stroke=function(ao){var Z=10;var ap=10;var ag=5000;var ai={x:null,y:null};var an={x:null,y:null};for(var aj=0;aj<this.currentPath_.length;aj+=ag){var am=[];var ah=false;am.push("<g_vml_:shape",' filled="',!!ao,'"',' style="position:absolute;width:',Z,"px;height:",ap,'px;"',' coordorigin="0,0"',' coordsize="',d*Z,",",d*ap,'"',' stroked="',!ao,'"',' path="');var aq=false;for(var ak=aj;ak<Math.min(aj+ag,this.currentPath_.length);ak++){if(ak%ag==0&&ak>0){am.push(" m ",n(this.currentPath_[ak-1].x),",",n(this.currentPath_[ak-1].y))}var m=this.currentPath_[ak];var al;switch(m.type){case"moveTo":al=m;am.push(" m ",n(m.x),",",n(m.y));break;case"lineTo":am.push(" l ",n(m.x),",",n(m.y));break;case"close":am.push(" x ");m=null;break;case"bezierCurveTo":am.push(" c ",n(m.cp1x),",",n(m.cp1y),",",n(m.cp2x),",",n(m.cp2y),",",n(m.x),",",n(m.y));break;case"at":case"wa":am.push(" ",m.type," ",n(m.x-this.arcScaleX_*m.radius),",",n(m.y-this.arcScaleY_*m.radius)," ",n(m.x+this.arcScaleX_*m.radius),",",n(m.y+this.arcScaleY_*m.radius)," ",n(m.xStart),",",n(m.yStart)," ",n(m.xEnd),",",n(m.yEnd));break}if(m){if(ai.x==null||m.x<ai.x){ai.x=m.x}if(an.x==null||m.x>an.x){an.x=m.x}if(ai.y==null||m.y<ai.y){ai.y=m.y}if(an.y==null||m.y>an.y){an.y=m.y}}}am.push(' ">');if(!ao){w(this,am)}else{G(this,am,ai,an)}am.push("</g_vml_:shape>");this.element_.insertAdjacentHTML("beforeEnd",am.join(""))}};function w(m,ag){var j=F(m.strokeStyle);var p=j.color;var Z=j.alpha*m.globalAlpha;var i=m.lineScale_*m.lineWidth;if(i<1){Z*=i}ag.push("<g_vml_:stroke",' opacity="',Z,'"',' joinstyle="',m.lineJoin,'"',' miterlimit="',m.miterLimit,'"',' endcap="',S(m.lineCap),'"',' weight="',i,'px"',' color="',p,'" />')}function G(aq,ai,aK,ar){var aj=aq.fillStyle;var aB=aq.arcScaleX_;var aA=aq.arcScaleY_;var j=ar.x-aK.x;var p=ar.y-aK.y;if(aj instanceof U){var an=0;var aF={x:0,y:0};var ax=0;var am=1;if(aj.type_=="gradient"){var al=aj.x0_/aB;var m=aj.y0_/aA;var ak=aj.x1_/aB;var aM=aj.y1_/aA;var aJ=V(aq,al,m);var aI=V(aq,ak,aM);var ag=aI.x-aJ.x;var Z=aI.y-aJ.y;an=Math.atan2(ag,Z)*180/Math.PI;if(an<0){an+=360}if(an<0.000001){an=0}}else{var aJ=V(aq,aj.x0_,aj.y0_);aF={x:(aJ.x-aK.x)/j,y:(aJ.y-aK.y)/p};j/=aB*d;p/=aA*d;var aD=ab.max(j,p);ax=2*aj.r0_/aD;am=2*aj.r1_/aD-ax}var av=aj.colors_;av.sort(function(aN,i){return aN.offset-i.offset});var ap=av.length;var au=av[0].color;var at=av[ap-1].color;var az=av[0].alpha*aq.globalAlpha;var ay=av[ap-1].alpha*aq.globalAlpha;var aE=[];for(var aH=0;aH<ap;aH++){var ao=av[aH];aE.push(ao.offset*am+ax+" "+ao.color)}ai.push('<g_vml_:fill type="',aj.type_,'"',' method="none" focus="100%"',' color="',au,'"',' color2="',at,'"',' colors="',aE.join(","),'"',' opacity="',ay,'"',' g_o_:opacity2="',az,'"',' angle="',an,'"',' focusposition="',aF.x,",",aF.y,'" />')}else{if(aj instanceof T){if(j&&p){var ah=-aK.x;var aC=-aK.y;ai.push("<g_vml_:fill",' position="',ah/j*aB*aB,",",aC/p*aA*aA,'"',' type="tile"',' src="',aj.src_,'" />')}}else{var aL=F(aq.fillStyle);var aw=aL.color;var aG=aL.alpha*aq.globalAlpha;ai.push('<g_vml_:fill color="',aw,'" opacity="',aG,'" />')}}}q.fill=function(){this.stroke(true)};q.closePath=function(){this.currentPath_.push({type:"close"})};function V(j,Z,p){var i=j.m_;return{x:d*(Z*i[0][0]+p*i[1][0]+i[2][0])-f,y:d*(Z*i[0][1]+p*i[1][1]+i[2][1])-f}}q.save=function(){var i={};v(this,i);this.aStack_.push(i);this.mStack_.push(this.m_);this.m_=J(B(),this.m_)};q.restore=function(){if(this.aStack_.length){v(this.aStack_.pop(),this);this.m_=this.mStack_.pop()}};function h(i){return isFinite(i[0][0])&&isFinite(i[0][1])&&isFinite(i[1][0])&&isFinite(i[1][1])&&isFinite(i[2][0])&&isFinite(i[2][1])}function aa(j,i,p){if(!h(i)){return}j.m_=i;if(p){var Z=i[0][0]*i[1][1]-i[0][1]*i[1][0];j.lineScale_=N(H(Z))}}q.translate=function(m,j){var i=[[1,0,0],[0,1,0],[m,j,1]];aa(this,J(i,this.m_),false)};q.rotate=function(j){var p=A(j);var m=l(j);var i=[[p,m,0],[-m,p,0],[0,0,1]];aa(this,J(i,this.m_),false)};q.scale=function(m,j){this.arcScaleX_*=m;this.arcScaleY_*=j;var i=[[m,0,0],[0,j,0],[0,0,1]];aa(this,J(i,this.m_),true)};q.transform=function(Z,p,ah,ag,j,i){var m=[[Z,p,0],[ah,ag,0],[j,i,1]];aa(this,J(m,this.m_),true)};q.setTransform=function(ag,Z,ai,ah,p,j){var i=[[ag,Z,0],[ai,ah,0],[p,j,1]];aa(this,i,true)};q.drawText_=function(am,ak,aj,ap,ai){var ao=this.m_,at=1000,j=0,ar=at,ah={x:0,y:0},ag=[];var i=u(E(this.font),this.element_);var p=ac(i);var au=this.element_.currentStyle;var Z=this.textAlign.toLowerCase();switch(Z){case"left":case"center":case"right":break;case"end":Z=au.direction=="ltr"?"right":"left";break;case"start":Z=au.direction=="rtl"?"right":"left";break;default:Z="left"}switch(this.textBaseline){case"hanging":case"top":ah.y=i.size/1.75;break;case"middle":break;default:case null:case"alphabetic":case"ideographic":case"bottom":ah.y=-i.size/2.25;break}switch(Z){case"right":j=at;ar=0.05;break;case"center":j=ar=at/2;break}var aq=V(this,ak+ah.x,aj+ah.y);ag.push('<g_vml_:line from="',-j,' 0" to="',ar,' 0.05" ',' coordsize="100 100" coordorigin="0 0"',' filled="',!ai,'" stroked="',!!ai,'" style="position:absolute;width:1px;height:1px;">');if(ai){w(this,ag)}else{G(this,ag,{x:-j,y:0},{x:ar,y:i.size})}var an=ao[0][0].toFixed(3)+","+ao[1][0].toFixed(3)+","+ao[0][1].toFixed(3)+","+ao[1][1].toFixed(3)+",0,0";var al=n(aq.x/d)+","+n(aq.y/d);ag.push('<g_vml_:skew on="t" matrix="',an,'" ',' offset="',al,'" origin="',j,' 0" />','<g_vml_:path textpathok="true" />','<g_vml_:textpath on="true" string="',af(am),'" style="v-text-align:',Z,";font:",af(p),'" /></g_vml_:line>');this.element_.insertAdjacentHTML("beforeEnd",ag.join(""))};q.fillText=function(m,i,p,j){this.drawText_(m,i,p,j,false)};q.strokeText=function(m,i,p,j){this.drawText_(m,i,p,j,true)};q.measureText=function(m){if(!this.textMeasureEl_){var i='<span style="position:absolute;top:-20000px;left:0;padding:0;margin:0;border:none;white-space:pre;"></span>';this.element_.insertAdjacentHTML("beforeEnd",i);this.textMeasureEl_=this.element_.lastChild}var j=this.element_.ownerDocument;this.textMeasureEl_.innerHTML="";this.textMeasureEl_.style.font=this.font;this.textMeasureEl_.appendChild(j.createTextNode(m));return{width:this.textMeasureEl_.offsetWidth}};q.clip=function(){};q.arcTo=function(){};q.createPattern=function(j,i){return new T(j,i)};function U(i){this.type_=i;this.x0_=0;this.y0_=0;this.r0_=0;this.x1_=0;this.y1_=0;this.r1_=0;this.colors_=[]}U.prototype.addColorStop=function(j,i){i=F(i);this.colors_.push({offset:j,color:i.color,alpha:i.alpha})};function T(j,i){Q(j);switch(i){case"repeat":case null:case"":this.repetition_="repeat";break;case"repeat-x":case"repeat-y":case"no-repeat":this.repetition_=i;break;default:O("SYNTAX_ERR")}this.src_=j.src;this.width_=j.width;this.height_=j.height}function O(i){throw new P(i)}function Q(i){if(!i||i.nodeType!=1||i.tagName!="IMG"){O("TYPE_MISMATCH_ERR")}if(i.readyState!="complete"){O("INVALID_STATE_ERR")}}function P(i){this.code=this[i];this.message=i+": DOM Exception "+this.code}var X=P.prototype=new Error;X.INDEX_SIZE_ERR=1;X.DOMSTRING_SIZE_ERR=2;X.HIERARCHY_REQUEST_ERR=3;X.WRONG_DOCUMENT_ERR=4;X.INVALID_CHARACTER_ERR=5;X.NO_DATA_ALLOWED_ERR=6;X.NO_MODIFICATION_ALLOWED_ERR=7;X.NOT_FOUND_ERR=8;X.NOT_SUPPORTED_ERR=9;X.INUSE_ATTRIBUTE_ERR=10;X.INVALID_STATE_ERR=11;X.SYNTAX_ERR=12;X.INVALID_MODIFICATION_ERR=13;X.NAMESPACE_ERR=14;X.INVALID_ACCESS_ERR=15;X.VALIDATION_ERR=16;X.TYPE_MISMATCH_ERR=17;G_vmlCanvasManager=e;CanvasRenderingContext2D=D;CanvasGradient=U;CanvasPattern=T;DOMException=P})()};
/* Javascript plotting library for jQuery, version 0.8.3.

Copyright (c) 2007-2014 IOLA and Ole Laursen.
Licensed under the MIT license.

*/

// first an inline dependency, jquery.colorhelpers.js, we inline it here
// for convenience

/* Plugin for jQuery for working with colors.
 *
 * Version 1.1.
 *
 * Inspiration from jQuery color animation plugin by John Resig.
 *
 * Released under the MIT license by Ole Laursen, October 2009.
 *
 * Examples:
 *
 *   $.color.parse("#fff").scale('rgb', 0.25).add('a', -0.5).toString()
 *   var c = $.color.extract($("#mydiv"), 'background-color');
 *   console.log(c.r, c.g, c.b, c.a);
 *   $.color.make(100, 50, 25, 0.4).toString() // returns "rgba(100,50,25,0.4)"
 *
 * Note that .scale() and .add() return the same modified object
 * instead of making a new one.
 *
 * V. 1.1: Fix error handling so e.g. parsing an empty string does
 * produce a color rather than just crashing.
 */

(function($){$.color={};$.color.make=function(r,g,b,a){var o={};o.r=r||0;o.g=g||0;o.b=b||0;o.a=a!=null?a:1;o.add=function(c,d){for(var i=0;i<c.length;++i)o[c.charAt(i)]+=d;return o.normalize()};o.scale=function(c,f){for(var i=0;i<c.length;++i)o[c.charAt(i)]*=f;return o.normalize()};o.toString=function(){if(o.a>=1){return"rgb("+[o.r,o.g,o.b].join(",")+")"}else{return"rgba("+[o.r,o.g,o.b,o.a].join(",")+")"}};o.normalize=function(){function clamp(min,value,max){return value<min?min:value>max?max:value}o.r=clamp(0,parseInt(o.r),255);o.g=clamp(0,parseInt(o.g),255);o.b=clamp(0,parseInt(o.b),255);o.a=clamp(0,o.a,1);return o};o.clone=function(){return $.color.make(o.r,o.b,o.g,o.a)};return o.normalize()};$.color.extract=function(elem,css){var c;do{c=elem.css(css).toLowerCase();if(c!=""&&c!="transparent")break;elem=elem.parent()}while(elem.length&&!$.nodeName(elem.get(0),"body"));if(c=="rgba(0, 0, 0, 0)")c="transparent";return $.color.parse(c)};$.color.parse=function(str){var res,m=$.color.make;if(res=/rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10));if(res=/rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseInt(res[1],10),parseInt(res[2],10),parseInt(res[3],10),parseFloat(res[4]));if(res=/rgb\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55);if(res=/rgba\(\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\%\s*,\s*([0-9]+(?:\.[0-9]+)?)\s*\)/.exec(str))return m(parseFloat(res[1])*2.55,parseFloat(res[2])*2.55,parseFloat(res[3])*2.55,parseFloat(res[4]));if(res=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(str))return m(parseInt(res[1],16),parseInt(res[2],16),parseInt(res[3],16));if(res=/#([a-fA-F0-9])([a-fA-F0-9])([a-fA-F0-9])/.exec(str))return m(parseInt(res[1]+res[1],16),parseInt(res[2]+res[2],16),parseInt(res[3]+res[3],16));var name=$.trim(str).toLowerCase();if(name=="transparent")return m(255,255,255,0);else{res=lookupColors[name]||[0,0,0];return m(res[0],res[1],res[2])}};var lookupColors={aqua:[0,255,255],azure:[240,255,255],beige:[245,245,220],black:[0,0,0],blue:[0,0,255],brown:[165,42,42],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgrey:[169,169,169],darkgreen:[0,100,0],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkviolet:[148,0,211],fuchsia:[255,0,255],gold:[255,215,0],green:[0,128,0],indigo:[75,0,130],khaki:[240,230,140],lightblue:[173,216,230],lightcyan:[224,255,255],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightyellow:[255,255,224],lime:[0,255,0],magenta:[255,0,255],maroon:[128,0,0],navy:[0,0,128],olive:[128,128,0],orange:[255,165,0],pink:[255,192,203],purple:[128,0,128],violet:[128,0,128],red:[255,0,0],silver:[192,192,192],white:[255,255,255],yellow:[255,255,0]}})(jQuery);

// the actual Flot code
(function($) {

	// Cache the prototype hasOwnProperty for faster access

	var hasOwnProperty = Object.prototype.hasOwnProperty;

    // A shim to provide 'detach' to jQuery versions prior to 1.4.  Using a DOM
    // operation produces the same effect as detach, i.e. removing the element
    // without touching its jQuery data.

    // Do not merge this into Flot 0.9, since it requires jQuery 1.4.4+.

    if (!$.fn.detach) {
        $.fn.detach = function() {
            return this.each(function() {
                if (this.parentNode) {
                    this.parentNode.removeChild( this );
                }
            });
        };
    }

	///////////////////////////////////////////////////////////////////////////
	// The Canvas object is a wrapper around an HTML5 <canvas> tag.
	//
	// @constructor
	// @param {string} cls List of classes to apply to the canvas.
	// @param {element} container Element onto which to append the canvas.
	//
	// Requiring a container is a little iffy, but unfortunately canvas
	// operations don't work unless the canvas is attached to the DOM.

	function Canvas(cls, container) {

		var element = container.children("." + cls)[0];

		if (element == null) {

			element = document.createElement("canvas");
			element.className = cls;

			$(element).css({ direction: "ltr", position: "absolute", left: 0, top: 0 })
				.appendTo(container);

			// If HTML5 Canvas isn't available, fall back to [Ex|Flash]canvas

			if (!element.getContext) {
				if (window.G_vmlCanvasManager) {
					element = window.G_vmlCanvasManager.initElement(element);
				} else {
					throw new Error("Canvas is not available. If you're using IE with a fall-back such as Excanvas, then there's either a mistake in your conditional include, or the page has no DOCTYPE and is rendering in Quirks Mode.");
				}
			}
		}

		this.element = element;

		var context = this.context = element.getContext("2d");

		// Determine the screen's ratio of physical to device-independent
		// pixels.  This is the ratio between the canvas width that the browser
		// advertises and the number of pixels actually present in that space.

		// The iPhone 4, for example, has a device-independent width of 320px,
		// but its screen is actually 640px wide.  It therefore has a pixel
		// ratio of 2, while most normal devices have a ratio of 1.

		var devicePixelRatio = window.devicePixelRatio || 1,
			backingStoreRatio =
				context.webkitBackingStorePixelRatio ||
				context.mozBackingStorePixelRatio ||
				context.msBackingStorePixelRatio ||
				context.oBackingStorePixelRatio ||
				context.backingStorePixelRatio || 1;

		this.pixelRatio = devicePixelRatio / backingStoreRatio;

		// Size the canvas to match the internal dimensions of its container

		this.resize(container.width(), container.height());

		// Collection of HTML div layers for text overlaid onto the canvas

		this.textContainer = null;
		this.text = {};

		// Cache of text fragments and metrics, so we can avoid expensively
		// re-calculating them when the plot is re-rendered in a loop.

		this._textCache = {};
	}

	// Resizes the canvas to the given dimensions.
	//
	// @param {number} width New width of the canvas, in pixels.
	// @param {number} width New height of the canvas, in pixels.

	Canvas.prototype.resize = function(width, height) {

		if (width <= 0 || height <= 0) {
			throw new Error("Invalid dimensions for plot, width = " + width + ", height = " + height);
		}

		var element = this.element,
			context = this.context,
			pixelRatio = this.pixelRatio;

		// Resize the canvas, increasing its density based on the display's
		// pixel ratio; basically giving it more pixels without increasing the
		// size of its element, to take advantage of the fact that retina
		// displays have that many more pixels in the same advertised space.

		// Resizing should reset the state (excanvas seems to be buggy though)

		if (this.width != width) {
			element.width = width * pixelRatio;
			element.style.width = width + "px";
			this.width = width;
		}

		if (this.height != height) {
			element.height = height * pixelRatio;
			element.style.height = height + "px";
			this.height = height;
		}

		// Save the context, so we can reset in case we get replotted.  The
		// restore ensure that we're really back at the initial state, and
		// should be safe even if we haven't saved the initial state yet.

		context.restore();
		context.save();

		// Scale the coordinate space to match the display density; so even though we
		// may have twice as many pixels, we still want lines and other drawing to
		// appear at the same size; the extra pixels will just make them crisper.

		context.scale(pixelRatio, pixelRatio);
	};

	// Clears the entire canvas area, not including any overlaid HTML text

	Canvas.prototype.clear = function() {
		this.context.clearRect(0, 0, this.width, this.height);
	};

	// Finishes rendering the canvas, including managing the text overlay.

	Canvas.prototype.render = function() {

		var cache = this._textCache;

		// For each text layer, add elements marked as active that haven't
		// already been rendered, and remove those that are no longer active.

		for (var layerKey in cache) {
			if (hasOwnProperty.call(cache, layerKey)) {

				var layer = this.getTextLayer(layerKey),
					layerCache = cache[layerKey];

				layer.hide();

				for (var styleKey in layerCache) {
					if (hasOwnProperty.call(layerCache, styleKey)) {
						var styleCache = layerCache[styleKey];
						for (var key in styleCache) {
							if (hasOwnProperty.call(styleCache, key)) {

								var positions = styleCache[key].positions;

								for (var i = 0, position; position = positions[i]; i++) {
									if (position.active) {
										if (!position.rendered) {
											layer.append(position.element);
											position.rendered = true;
										}
									} else {
										positions.splice(i--, 1);
										if (position.rendered) {
											position.element.detach();
										}
									}
								}

								if (positions.length == 0) {
									delete styleCache[key];
								}
							}
						}
					}
				}

				layer.show();
			}
		}
	};

	// Creates (if necessary) and returns the text overlay container.
	//
	// @param {string} classes String of space-separated CSS classes used to
	//     uniquely identify the text layer.
	// @return {object} The jQuery-wrapped text-layer div.

	Canvas.prototype.getTextLayer = function(classes) {

		var layer = this.text[classes];

		// Create the text layer if it doesn't exist

		if (layer == null) {

			// Create the text layer container, if it doesn't exist

			if (this.textContainer == null) {
				this.textContainer = $("<div class='flot-text'></div>")
					.css({
						position: "absolute",
						top: 0,
						left: 0,
						bottom: 0,
						right: 0,
						'font-size': "smaller",
						color: "#545454"
					})
					.insertAfter(this.element);
			}

			layer = this.text[classes] = $("<div></div>")
				.addClass(classes)
				.css({
					position: "absolute",
					top: 0,
					left: 0,
					bottom: 0,
					right: 0
				})
				.appendTo(this.textContainer);
		}

		return layer;
	};

	// Creates (if necessary) and returns a text info object.
	//
	// The object looks like this:
	//
	// {
	//     width: Width of the text's wrapper div.
	//     height: Height of the text's wrapper div.
	//     element: The jQuery-wrapped HTML div containing the text.
	//     positions: Array of positions at which this text is drawn.
	// }
	//
	// The positions array contains objects that look like this:
	//
	// {
	//     active: Flag indicating whether the text should be visible.
	//     rendered: Flag indicating whether the text is currently visible.
	//     element: The jQuery-wrapped HTML div containing the text.
	//     x: X coordinate at which to draw the text.
	//     y: Y coordinate at which to draw the text.
	// }
	//
	// Each position after the first receives a clone of the original element.
	//
	// The idea is that that the width, height, and general 'identity' of the
	// text is constant no matter where it is placed; the placements are a
	// secondary property.
	//
	// Canvas maintains a cache of recently-used text info objects; getTextInfo
	// either returns the cached element or creates a new entry.
	//
	// @param {string} layer A string of space-separated CSS classes uniquely
	//     identifying the layer containing this text.
	// @param {string} text Text string to retrieve info for.
	// @param {(string|object)=} font Either a string of space-separated CSS
	//     classes or a font-spec object, defining the text's font and style.
	// @param {number=} angle Angle at which to rotate the text, in degrees.
	//     Angle is currently unused, it will be implemented in the future.
	// @param {number=} width Maximum width of the text before it wraps.
	// @return {object} a text info object.

	Canvas.prototype.getTextInfo = function(layer, text, font, angle, width) {

		var textStyle, layerCache, styleCache, info;

		// Cast the value to a string, in case we were given a number or such

		text = "" + text;

		// If the font is a font-spec object, generate a CSS font definition

		if (typeof font === "object") {
			textStyle = font.style + " " + font.variant + " " + font.weight + " " + font.size + "px/" + font.lineHeight + "px " + font.family;
		} else {
			textStyle = font;
		}

		// Retrieve (or create) the cache for the text's layer and styles

		layerCache = this._textCache[layer];

		if (layerCache == null) {
			layerCache = this._textCache[layer] = {};
		}

		styleCache = layerCache[textStyle];

		if (styleCache == null) {
			styleCache = layerCache[textStyle] = {};
		}

		info = styleCache[text];

		// If we can't find a matching element in our cache, create a new one

		if (info == null) {

			var element = $("<div></div>").html(text)
				.css({
					position: "absolute",
					'max-width': width,
					top: -9999
				})
				.appendTo(this.getTextLayer(layer));

			if (typeof font === "object") {
				element.css({
					font: textStyle,
					color: font.color
				});
			} else if (typeof font === "string") {
				element.addClass(font);
			}

			info = styleCache[text] = {
				width: element.outerWidth(true),
				height: element.outerHeight(true),
				element: element,
				positions: []
			};

			element.detach();
		}

		return info;
	};

	// Adds a text string to the canvas text overlay.
	//
	// The text isn't drawn immediately; it is marked as rendering, which will
	// result in its addition to the canvas on the next render pass.
	//
	// @param {string} layer A string of space-separated CSS classes uniquely
	//     identifying the layer containing this text.
	// @param {number} x X coordinate at which to draw the text.
	// @param {number} y Y coordinate at which to draw the text.
	// @param {string} text Text string to draw.
	// @param {(string|object)=} font Either a string of space-separated CSS
	//     classes or a font-spec object, defining the text's font and style.
	// @param {number=} angle Angle at which to rotate the text, in degrees.
	//     Angle is currently unused, it will be implemented in the future.
	// @param {number=} width Maximum width of the text before it wraps.
	// @param {string=} halign Horizontal alignment of the text; either "left",
	//     "center" or "right".
	// @param {string=} valign Vertical alignment of the text; either "top",
	//     "middle" or "bottom".

	Canvas.prototype.addText = function(layer, x, y, text, font, angle, width, halign, valign) {

		var info = this.getTextInfo(layer, text, font, angle, width),
			positions = info.positions;

		// Tweak the div's position to match the text's alignment

		if (halign == "center") {
			x -= info.width / 2;
		} else if (halign == "right") {
			x -= info.width;
		}

		if (valign == "middle") {
			y -= info.height / 2;
		} else if (valign == "bottom") {
			y -= info.height;
		}

		// Determine whether this text already exists at this position.
		// If so, mark it for inclusion in the next render pass.

		for (var i = 0, position; position = positions[i]; i++) {
			if (position.x == x && position.y == y) {
				position.active = true;
				return;
			}
		}

		// If the text doesn't exist at this position, create a new entry

		// For the very first position we'll re-use the original element,
		// while for subsequent ones we'll clone it.

		position = {
			active: true,
			rendered: false,
			element: positions.length ? info.element.clone() : info.element,
			x: x,
			y: y
		};

		positions.push(position);

		// Move the element to its final position within the container

		position.element.css({
			top: Math.round(y),
			left: Math.round(x),
			'text-align': halign	// In case the text wraps
		});
	};

	// Removes one or more text strings from the canvas text overlay.
	//
	// If no parameters are given, all text within the layer is removed.
	//
	// Note that the text is not immediately removed; it is simply marked as
	// inactive, which will result in its removal on the next render pass.
	// This avoids the performance penalty for 'clear and redraw' behavior,
	// where we potentially get rid of all text on a layer, but will likely
	// add back most or all of it later, as when redrawing axes, for example.
	//
	// @param {string} layer A string of space-separated CSS classes uniquely
	//     identifying the layer containing this text.
	// @param {number=} x X coordinate of the text.
	// @param {number=} y Y coordinate of the text.
	// @param {string=} text Text string to remove.
	// @param {(string|object)=} font Either a string of space-separated CSS
	//     classes or a font-spec object, defining the text's font and style.
	// @param {number=} angle Angle at which the text is rotated, in degrees.
	//     Angle is currently unused, it will be implemented in the future.

	Canvas.prototype.removeText = function(layer, x, y, text, font, angle) {
		if (text == null) {
			var layerCache = this._textCache[layer];
			if (layerCache != null) {
				for (var styleKey in layerCache) {
					if (hasOwnProperty.call(layerCache, styleKey)) {
						var styleCache = layerCache[styleKey];
						for (var key in styleCache) {
							if (hasOwnProperty.call(styleCache, key)) {
								var positions = styleCache[key].positions;
								for (var i = 0, position; position = positions[i]; i++) {
									position.active = false;
								}
							}
						}
					}
				}
			}
		} else {
			var positions = this.getTextInfo(layer, text, font, angle).positions;
			for (var i = 0, position; position = positions[i]; i++) {
				if (position.x == x && position.y == y) {
					position.active = false;
				}
			}
		}
	};

	///////////////////////////////////////////////////////////////////////////
	// The top-level container for the entire plot.

    function Plot(placeholder, data_, options_, plugins) {
        // data is on the form:
        //   [ series1, series2 ... ]
        // where series is either just the data as [ [x1, y1], [x2, y2], ... ]
        // or { data: [ [x1, y1], [x2, y2], ... ], label: "some label", ... }

        var series = [],
            options = {
                // the color theme used for graphs
                colors: ["#edc240", "#afd8f8", "#cb4b4b", "#4da74d", "#9440ed"],
                legend: {
                    show: true,
                    noColumns: 1, // number of colums in legend table
                    labelFormatter: null, // fn: string -> string
                    labelBoxBorderColor: "#ccc", // border color for the little label boxes
                    container: null, // container (as jQuery object) to put legend in, null means default on top of graph
                    position: "ne", // position of default legend container within plot
                    margin: 5, // distance from grid edge to default legend container within plot
                    backgroundColor: null, // null means auto-detect
                    backgroundOpacity: 0.85, // set to 0 to avoid background
                    sorted: null    // default to no legend sorting
                },
                xaxis: {
                    show: null, // null = auto-detect, true = always, false = never
                    position: "bottom", // or "top"
                    mode: null, // null or "time"
                    font: null, // null (derived from CSS in placeholder) or object like { size: 11, lineHeight: 13, style: "italic", weight: "bold", family: "sans-serif", variant: "small-caps" }
                    color: null, // base color, labels, ticks
                    tickColor: null, // possibly different color of ticks, e.g. "rgba(0,0,0,0.15)"
                    transform: null, // null or f: number -> number to transform axis
                    inverseTransform: null, // if transform is set, this should be the inverse function
                    min: null, // min. value to show, null means set automatically
                    max: null, // max. value to show, null means set automatically
                    autoscaleMargin: null, // margin in % to add if auto-setting min/max
                    ticks: null, // either [1, 3] or [[1, "a"], 3] or (fn: axis info -> ticks) or app. number of ticks for auto-ticks
                    tickFormatter: null, // fn: number -> string
                    labelWidth: null, // size of tick labels in pixels
                    labelHeight: null,
                    reserveSpace: null, // whether to reserve space even if axis isn't shown
                    tickLength: null, // size in pixels of ticks, or "full" for whole line
                    alignTicksWithAxis: null, // axis number or null for no sync
                    tickDecimals: null, // no. of decimals, null means auto
                    tickSize: null, // number or [number, "unit"]
                    minTickSize: null // number or [number, "unit"]
                },
                yaxis: {
                    autoscaleMargin: 0.02,
                    position: "left" // or "right"
                },
                xaxes: [],
                yaxes: [],
                series: {
                    points: {
                        show: false,
                        radius: 3,
                        lineWidth: 2, // in pixels
                        fill: true,
                        fillColor: "#ffffff",
                        symbol: "circle" // or callback
                    },
                    lines: {
                        // we don't put in show: false so we can see
                        // whether lines were actively disabled
                        lineWidth: 2, // in pixels
                        fill: false,
                        fillColor: null,
                        steps: false
                        // Omit 'zero', so we can later default its value to
                        // match that of the 'fill' option.
                    },
                    bars: {
                        show: false,
                        lineWidth: 2, // in pixels
                        barWidth: 1, // in units of the x axis
                        fill: true,
                        fillColor: null,
                        align: "left", // "left", "right", or "center"
                        horizontal: false,
                        zero: true
                    },
                    shadowSize: 3,
                    highlightColor: null
                },
                grid: {
                    show: true,
                    aboveData: false,
                    color: "#545454", // primary color used for outline and labels
                    backgroundColor: null, // null for transparent, else color
                    borderColor: null, // set if different from the grid color
                    tickColor: null, // color for the ticks, e.g. "rgba(0,0,0,0.15)"
                    margin: 0, // distance from the canvas edge to the grid
                    labelMargin: 5, // in pixels
                    axisMargin: 8, // in pixels
                    borderWidth: 2, // in pixels
                    minBorderMargin: null, // in pixels, null means taken from points radius
                    markings: null, // array of ranges or fn: axes -> array of ranges
                    markingsColor: "#f4f4f4",
                    markingsLineWidth: 2,
                    // interactive stuff
                    clickable: false,
                    hoverable: false,
                    autoHighlight: true, // highlight in case mouse is near
                    mouseActiveRadius: 10 // how far the mouse can be away to activate an item
                },
                interaction: {
                    redrawOverlayInterval: 1000/60 // time between updates, -1 means in same flow
                },
                hooks: {}
            },
        surface = null,     // the canvas for the plot itself
        overlay = null,     // canvas for interactive stuff on top of plot
        eventHolder = null, // jQuery object that events should be bound to
        ctx = null, octx = null,
        xaxes = [], yaxes = [],
        plotOffset = { left: 0, right: 0, top: 0, bottom: 0},
        plotWidth = 0, plotHeight = 0,
        hooks = {
            processOptions: [],
            processRawData: [],
            processDatapoints: [],
            processOffset: [],
            drawBackground: [],
            drawSeries: [],
            draw: [],
            bindEvents: [],
            drawOverlay: [],
            shutdown: []
        },
        plot = this;

        // public functions
        plot.setData = setData;
        plot.setupGrid = setupGrid;
        plot.draw = draw;
        plot.getPlaceholder = function() { return placeholder; };
        plot.getCanvas = function() { return surface.element; };
        plot.getPlotOffset = function() { return plotOffset; };
        plot.width = function () { return plotWidth; };
        plot.height = function () { return plotHeight; };
        plot.offset = function () {
            var o = eventHolder.offset();
            o.left += plotOffset.left;
            o.top += plotOffset.top;
            return o;
        };
        plot.getData = function () { return series; };
        plot.getAxes = function () {
            var res = {}, i;
            $.each(xaxes.concat(yaxes), function (_, axis) {
                if (axis)
                    res[axis.direction + (axis.n != 1 ? axis.n : "") + "axis"] = axis;
            });
            return res;
        };
        plot.getXAxes = function () { return xaxes; };
        plot.getYAxes = function () { return yaxes; };
        plot.c2p = canvasToAxisCoords;
        plot.p2c = axisToCanvasCoords;
        plot.getOptions = function () { return options; };
        plot.highlight = highlight;
        plot.unhighlight = unhighlight;
        plot.triggerRedrawOverlay = triggerRedrawOverlay;
        plot.pointOffset = function(point) {
            return {
                left: parseInt(xaxes[axisNumber(point, "x") - 1].p2c(+point.x) + plotOffset.left, 10),
                top: parseInt(yaxes[axisNumber(point, "y") - 1].p2c(+point.y) + plotOffset.top, 10)
            };
        };
        plot.shutdown = shutdown;
        plot.destroy = function () {
            shutdown();
            placeholder.removeData("plot").empty();

            series = [];
            options = null;
            surface = null;
            overlay = null;
            eventHolder = null;
            ctx = null;
            octx = null;
            xaxes = [];
            yaxes = [];
            hooks = null;
            highlights = [];
            plot = null;
        };
        plot.resize = function () {
        	var width = placeholder.width(),
        		height = placeholder.height();
            surface.resize(width, height);
            overlay.resize(width, height);
        };

        // public attributes
        plot.hooks = hooks;

        // initialize
        initPlugins(plot);
        parseOptions(options_);
        setupCanvases();
        setData(data_);
        setupGrid();
        draw();
        bindEvents();


        function executeHooks(hook, args) {
            args = [plot].concat(args);
            for (var i = 0; i < hook.length; ++i)
                hook[i].apply(this, args);
        }

        function initPlugins() {

            // References to key classes, allowing plugins to modify them

            var classes = {
                Canvas: Canvas
            };

            for (var i = 0; i < plugins.length; ++i) {
                var p = plugins[i];
                p.init(plot, classes);
                if (p.options)
                    $.extend(true, options, p.options);
            }
        }

        function parseOptions(opts) {

            $.extend(true, options, opts);

            // $.extend merges arrays, rather than replacing them.  When less
            // colors are provided than the size of the default palette, we
            // end up with those colors plus the remaining defaults, which is
            // not expected behavior; avoid it by replacing them here.

            if (opts && opts.colors) {
            	options.colors = opts.colors;
            }

            if (options.xaxis.color == null)
                options.xaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();
            if (options.yaxis.color == null)
                options.yaxis.color = $.color.parse(options.grid.color).scale('a', 0.22).toString();

            if (options.xaxis.tickColor == null) // grid.tickColor for back-compatibility
                options.xaxis.tickColor = options.grid.tickColor || options.xaxis.color;
            if (options.yaxis.tickColor == null) // grid.tickColor for back-compatibility
                options.yaxis.tickColor = options.grid.tickColor || options.yaxis.color;

            if (options.grid.borderColor == null)
                options.grid.borderColor = options.grid.color;
            if (options.grid.tickColor == null)
                options.grid.tickColor = $.color.parse(options.grid.color).scale('a', 0.22).toString();

            // Fill in defaults for axis options, including any unspecified
            // font-spec fields, if a font-spec was provided.

            // If no x/y axis options were provided, create one of each anyway,
            // since the rest of the code assumes that they exist.

            var i, axisOptions, axisCount,
                fontSize = placeholder.css("font-size"),
                fontSizeDefault = fontSize ? +fontSize.replace("px", "") : 13,
                fontDefaults = {
                    style: placeholder.css("font-style"),
                    size: Math.round(0.8 * fontSizeDefault),
                    variant: placeholder.css("font-variant"),
                    weight: placeholder.css("font-weight"),
                    family: placeholder.css("font-family")
                };

            axisCount = options.xaxes.length || 1;
            for (i = 0; i < axisCount; ++i) {

                axisOptions = options.xaxes[i];
                if (axisOptions && !axisOptions.tickColor) {
                    axisOptions.tickColor = axisOptions.color;
                }

                axisOptions = $.extend(true, {}, options.xaxis, axisOptions);
                options.xaxes[i] = axisOptions;

                if (axisOptions.font) {
                    axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
                    if (!axisOptions.font.color) {
                        axisOptions.font.color = axisOptions.color;
                    }
                    if (!axisOptions.font.lineHeight) {
                        axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
                    }
                }
            }

            axisCount = options.yaxes.length || 1;
            for (i = 0; i < axisCount; ++i) {

                axisOptions = options.yaxes[i];
                if (axisOptions && !axisOptions.tickColor) {
                    axisOptions.tickColor = axisOptions.color;
                }

                axisOptions = $.extend(true, {}, options.yaxis, axisOptions);
                options.yaxes[i] = axisOptions;

                if (axisOptions.font) {
                    axisOptions.font = $.extend({}, fontDefaults, axisOptions.font);
                    if (!axisOptions.font.color) {
                        axisOptions.font.color = axisOptions.color;
                    }
                    if (!axisOptions.font.lineHeight) {
                        axisOptions.font.lineHeight = Math.round(axisOptions.font.size * 1.15);
                    }
                }
            }

            // backwards compatibility, to be removed in future
            if (options.xaxis.noTicks && options.xaxis.ticks == null)
                options.xaxis.ticks = options.xaxis.noTicks;
            if (options.yaxis.noTicks && options.yaxis.ticks == null)
                options.yaxis.ticks = options.yaxis.noTicks;
            if (options.x2axis) {
                options.xaxes[1] = $.extend(true, {}, options.xaxis, options.x2axis);
                options.xaxes[1].position = "top";
                // Override the inherit to allow the axis to auto-scale
                if (options.x2axis.min == null) {
                    options.xaxes[1].min = null;
                }
                if (options.x2axis.max == null) {
                    options.xaxes[1].max = null;
                }
            }
            if (options.y2axis) {
                options.yaxes[1] = $.extend(true, {}, options.yaxis, options.y2axis);
                options.yaxes[1].position = "right";
                // Override the inherit to allow the axis to auto-scale
                if (options.y2axis.min == null) {
                    options.yaxes[1].min = null;
                }
                if (options.y2axis.max == null) {
                    options.yaxes[1].max = null;
                }
            }
            if (options.grid.coloredAreas)
                options.grid.markings = options.grid.coloredAreas;
            if (options.grid.coloredAreasColor)
                options.grid.markingsColor = options.grid.coloredAreasColor;
            if (options.lines)
                $.extend(true, options.series.lines, options.lines);
            if (options.points)
                $.extend(true, options.series.points, options.points);
            if (options.bars)
                $.extend(true, options.series.bars, options.bars);
            if (options.shadowSize != null)
                options.series.shadowSize = options.shadowSize;
            if (options.highlightColor != null)
                options.series.highlightColor = options.highlightColor;

            // save options on axes for future reference
            for (i = 0; i < options.xaxes.length; ++i)
                getOrCreateAxis(xaxes, i + 1).options = options.xaxes[i];
            for (i = 0; i < options.yaxes.length; ++i)
                getOrCreateAxis(yaxes, i + 1).options = options.yaxes[i];

            // add hooks from options
            for (var n in hooks)
                if (options.hooks[n] && options.hooks[n].length)
                    hooks[n] = hooks[n].concat(options.hooks[n]);

            executeHooks(hooks.processOptions, [options]);
        }

        function setData(d) {
            series = parseData(d);
            fillInSeriesOptions();
            processData();
        }

        function parseData(d) {
            var res = [];
            for (var i = 0; i < d.length; ++i) {
                var s = $.extend(true, {}, options.series);

                if (d[i].data != null) {
                    s.data = d[i].data; // move the data instead of deep-copy
                    delete d[i].data;

                    $.extend(true, s, d[i]);

                    d[i].data = s.data;
                }
                else
                    s.data = d[i];
                res.push(s);
            }

            return res;
        }

        function axisNumber(obj, coord) {
            var a = obj[coord + "axis"];
            if (typeof a == "object") // if we got a real axis, extract number
                a = a.n;
            if (typeof a != "number")
                a = 1; // default to first axis
            return a;
        }

        function allAxes() {
            // return flat array without annoying null entries
            return $.grep(xaxes.concat(yaxes), function (a) { return a; });
        }

        function canvasToAxisCoords(pos) {
            // return an object with x/y corresponding to all used axes
            var res = {}, i, axis;
            for (i = 0; i < xaxes.length; ++i) {
                axis = xaxes[i];
                if (axis && axis.used)
                    res["x" + axis.n] = axis.c2p(pos.left);
            }

            for (i = 0; i < yaxes.length; ++i) {
                axis = yaxes[i];
                if (axis && axis.used)
                    res["y" + axis.n] = axis.c2p(pos.top);
            }

            if (res.x1 !== undefined)
                res.x = res.x1;
            if (res.y1 !== undefined)
                res.y = res.y1;

            return res;
        }

        function axisToCanvasCoords(pos) {
            // get canvas coords from the first pair of x/y found in pos
            var res = {}, i, axis, key;

            for (i = 0; i < xaxes.length; ++i) {
                axis = xaxes[i];
                if (axis && axis.used) {
                    key = "x" + axis.n;
                    if (pos[key] == null && axis.n == 1)
                        key = "x";

                    if (pos[key] != null) {
                        res.left = axis.p2c(pos[key]);
                        break;
                    }
                }
            }

            for (i = 0; i < yaxes.length; ++i) {
                axis = yaxes[i];
                if (axis && axis.used) {
                    key = "y" + axis.n;
                    if (pos[key] == null && axis.n == 1)
                        key = "y";

                    if (pos[key] != null) {
                        res.top = axis.p2c(pos[key]);
                        break;
                    }
                }
            }

            return res;
        }

        function getOrCreateAxis(axes, number) {
            if (!axes[number - 1])
                axes[number - 1] = {
                    n: number, // save the number for future reference
                    direction: axes == xaxes ? "x" : "y",
                    options: $.extend(true, {}, axes == xaxes ? options.xaxis : options.yaxis)
                };

            return axes[number - 1];
        }

        function fillInSeriesOptions() {

            var neededColors = series.length, maxIndex = -1, i;

            // Subtract the number of series that already have fixed colors or
            // color indexes from the number that we still need to generate.

            for (i = 0; i < series.length; ++i) {
                var sc = series[i].color;
                if (sc != null) {
                    neededColors--;
                    if (typeof sc == "number" && sc > maxIndex) {
                        maxIndex = sc;
                    }
                }
            }

            // If any of the series have fixed color indexes, then we need to
            // generate at least as many colors as the highest index.

            if (neededColors <= maxIndex) {
                neededColors = maxIndex + 1;
            }

            // Generate all the colors, using first the option colors and then
            // variations on those colors once they're exhausted.

            var c, colors = [], colorPool = options.colors,
                colorPoolSize = colorPool.length, variation = 0;

            for (i = 0; i < neededColors; i++) {

                c = $.color.parse(colorPool[i % colorPoolSize] || "#666");

                // Each time we exhaust the colors in the pool we adjust
                // a scaling factor used to produce more variations on
                // those colors. The factor alternates negative/positive
                // to produce lighter/darker colors.

                // Reset the variation after every few cycles, or else
                // it will end up producing only white or black colors.

                if (i % colorPoolSize == 0 && i) {
                    if (variation >= 0) {
                        if (variation < 0.5) {
                            variation = -variation - 0.2;
                        } else variation = 0;
                    } else variation = -variation;
                }

                colors[i] = c.scale('rgb', 1 + variation);
            }

            // Finalize the series options, filling in their colors

            var colori = 0, s;
            for (i = 0; i < series.length; ++i) {
                s = series[i];

                // assign colors
                if (s.color == null) {
                    s.color = colors[colori].toString();
                    ++colori;
                }
                else if (typeof s.color == "number")
                    s.color = colors[s.color].toString();

                // turn on lines automatically in case nothing is set
                if (s.lines.show == null) {
                    var v, show = true;
                    for (v in s)
                        if (s[v] && s[v].show) {
                            show = false;
                            break;
                        }
                    if (show)
                        s.lines.show = true;
                }

                // If nothing was provided for lines.zero, default it to match
                // lines.fill, since areas by default should extend to zero.

                if (s.lines.zero == null) {
                    s.lines.zero = !!s.lines.fill;
                }

                // setup axes
                s.xaxis = getOrCreateAxis(xaxes, axisNumber(s, "x"));
                s.yaxis = getOrCreateAxis(yaxes, axisNumber(s, "y"));
            }
        }

        function processData() {
            var topSentry = Number.POSITIVE_INFINITY,
                bottomSentry = Number.NEGATIVE_INFINITY,
                fakeInfinity = Number.MAX_VALUE,
                i, j, k, m, length,
                s, points, ps, x, y, axis, val, f, p,
                data, format;

            function updateAxis(axis, min, max) {
                if (min < axis.datamin && min != -fakeInfinity)
                    axis.datamin = min;
                if (max > axis.datamax && max != fakeInfinity)
                    axis.datamax = max;
            }

            $.each(allAxes(), function (_, axis) {
                // init axis
                axis.datamin = topSentry;
                axis.datamax = bottomSentry;
                axis.used = false;
            });

            for (i = 0; i < series.length; ++i) {
                s = series[i];
                s.datapoints = { points: [] };

                executeHooks(hooks.processRawData, [ s, s.data, s.datapoints ]);
            }

            // first pass: clean and copy data
            for (i = 0; i < series.length; ++i) {
                s = series[i];

                data = s.data;
                format = s.datapoints.format;

                if (!format) {
                    format = [];
                    // find out how to copy
                    format.push({ x: true, number: true, required: true });
                    format.push({ y: true, number: true, required: true });

                    if (s.bars.show || (s.lines.show && s.lines.fill)) {
                        var autoscale = !!((s.bars.show && s.bars.zero) || (s.lines.show && s.lines.zero));
                        format.push({ y: true, number: true, required: false, defaultValue: 0, autoscale: autoscale });
                        if (s.bars.horizontal) {
                            delete format[format.length - 1].y;
                            format[format.length - 1].x = true;
                        }
                    }

                    s.datapoints.format = format;
                }

                if (s.datapoints.pointsize != null)
                    continue; // already filled in

                s.datapoints.pointsize = format.length;

                ps = s.datapoints.pointsize;
                points = s.datapoints.points;

                var insertSteps = s.lines.show && s.lines.steps;
                s.xaxis.used = s.yaxis.used = true;

                for (j = k = 0; j < data.length; ++j, k += ps) {
                    p = data[j];

                    var nullify = p == null;
                    if (!nullify) {
                        for (m = 0; m < ps; ++m) {
                            val = p[m];
                            f = format[m];

                            if (f) {
                                if (f.number && val != null) {
                                    val = +val; // convert to number
                                    if (isNaN(val))
                                        val = null;
                                    else if (val == Infinity)
                                        val = fakeInfinity;
                                    else if (val == -Infinity)
                                        val = -fakeInfinity;
                                }

                                if (val == null) {
                                    if (f.required)
                                        nullify = true;

                                    if (f.defaultValue != null)
                                        val = f.defaultValue;
                                }
                            }

                            points[k + m] = val;
                        }
                    }

                    if (nullify) {
                        for (m = 0; m < ps; ++m) {
                            val = points[k + m];
                            if (val != null) {
                                f = format[m];
                                // extract min/max info
                                if (f.autoscale !== false) {
                                    if (f.x) {
                                        updateAxis(s.xaxis, val, val);
                                    }
                                    if (f.y) {
                                        updateAxis(s.yaxis, val, val);
                                    }
                                }
                            }
                            points[k + m] = null;
                        }
                    }
                    else {
                        // a little bit of line specific stuff that
                        // perhaps shouldn't be here, but lacking
                        // better means...
                        if (insertSteps && k > 0
                            && points[k - ps] != null
                            && points[k - ps] != points[k]
                            && points[k - ps + 1] != points[k + 1]) {
                            // copy the point to make room for a middle point
                            for (m = 0; m < ps; ++m)
                                points[k + ps + m] = points[k + m];

                            // middle point has same y
                            points[k + 1] = points[k - ps + 1];

                            // we've added a point, better reflect that
                            k += ps;
                        }
                    }
                }
            }

            // give the hooks a chance to run
            for (i = 0; i < series.length; ++i) {
                s = series[i];

                executeHooks(hooks.processDatapoints, [ s, s.datapoints]);
            }

            // second pass: find datamax/datamin for auto-scaling
            for (i = 0; i < series.length; ++i) {
                s = series[i];
                points = s.datapoints.points;
                ps = s.datapoints.pointsize;
                format = s.datapoints.format;

                var xmin = topSentry, ymin = topSentry,
                    xmax = bottomSentry, ymax = bottomSentry;

                for (j = 0; j < points.length; j += ps) {
                    if (points[j] == null)
                        continue;

                    for (m = 0; m < ps; ++m) {
                        val = points[j + m];
                        f = format[m];
                        if (!f || f.autoscale === false || val == fakeInfinity || val == -fakeInfinity)
                            continue;

                        if (f.x) {
                            if (val < xmin)
                                xmin = val;
                            if (val > xmax)
                                xmax = val;
                        }
                        if (f.y) {
                            if (val < ymin)
                                ymin = val;
                            if (val > ymax)
                                ymax = val;
                        }
                    }
                }

                if (s.bars.show) {
                    // make sure we got room for the bar on the dancing floor
                    var delta;

                    switch (s.bars.align) {
                        case "left":
                            delta = 0;
                            break;
                        case "right":
                            delta = -s.bars.barWidth;
                            break;
                        default:
                            delta = -s.bars.barWidth / 2;
                    }

                    if (s.bars.horizontal) {
                        ymin += delta;
                        ymax += delta + s.bars.barWidth;
                    }
                    else {
                        xmin += delta;
                        xmax += delta + s.bars.barWidth;
                    }
                }

                updateAxis(s.xaxis, xmin, xmax);
                updateAxis(s.yaxis, ymin, ymax);
            }

            $.each(allAxes(), function (_, axis) {
                if (axis.datamin == topSentry)
                    axis.datamin = null;
                if (axis.datamax == bottomSentry)
                    axis.datamax = null;
            });
        }

        function setupCanvases() {

            // Make sure the placeholder is clear of everything except canvases
            // from a previous plot in this container that we'll try to re-use.

            placeholder.css("padding", 0) // padding messes up the positioning
                .children().filter(function(){
                    return !$(this).hasClass("flot-overlay") && !$(this).hasClass('flot-base');
                }).remove();

            if (placeholder.css("position") == 'static')
                placeholder.css("position", "relative"); // for positioning labels and overlay

            surface = new Canvas("flot-base", placeholder);
            overlay = new Canvas("flot-overlay", placeholder); // overlay canvas for interactive features

            ctx = surface.context;
            octx = overlay.context;

            // define which element we're listening for events on
            eventHolder = $(overlay.element).unbind();

            // If we're re-using a plot object, shut down the old one

            var existing = placeholder.data("plot");

            if (existing) {
                existing.shutdown();
                overlay.clear();
            }

            // save in case we get replotted
            placeholder.data("plot", plot);
        }

        function bindEvents() {
            // bind events
            if (options.grid.hoverable) {
                eventHolder.mousemove(onMouseMove);

                // Use bind, rather than .mouseleave, because we officially
                // still support jQuery 1.2.6, which doesn't define a shortcut
                // for mouseenter or mouseleave.  This was a bug/oversight that
                // was fixed somewhere around 1.3.x.  We can return to using
                // .mouseleave when we drop support for 1.2.6.

                eventHolder.bind("mouseleave", onMouseLeave);
            }

            if (options.grid.clickable)
                eventHolder.click(onClick);

            executeHooks(hooks.bindEvents, [eventHolder]);
        }

        function shutdown() {
            if (redrawTimeout)
                clearTimeout(redrawTimeout);

            eventHolder.unbind("mousemove", onMouseMove);
            eventHolder.unbind("mouseleave", onMouseLeave);
            eventHolder.unbind("click", onClick);

            executeHooks(hooks.shutdown, [eventHolder]);
        }

        function setTransformationHelpers(axis) {
            // set helper functions on the axis, assumes plot area
            // has been computed already

            function identity(x) { return x; }

            var s, m, t = axis.options.transform || identity,
                it = axis.options.inverseTransform;

            // precompute how much the axis is scaling a point
            // in canvas space
            if (axis.direction == "x") {
                s = axis.scale = plotWidth / Math.abs(t(axis.max) - t(axis.min));
                m = Math.min(t(axis.max), t(axis.min));
            }
            else {
                s = axis.scale = plotHeight / Math.abs(t(axis.max) - t(axis.min));
                s = -s;
                m = Math.max(t(axis.max), t(axis.min));
            }

            // data point to canvas coordinate
            if (t == identity) // slight optimization
                axis.p2c = function (p) { return (p - m) * s; };
            else
                axis.p2c = function (p) { return (t(p) - m) * s; };
            // canvas coordinate to data point
            if (!it)
                axis.c2p = function (c) { return m + c / s; };
            else
                axis.c2p = function (c) { return it(m + c / s); };
        }

        function measureTickLabels(axis) {

            var opts = axis.options,
                ticks = axis.ticks || [],
                labelWidth = opts.labelWidth || 0,
                labelHeight = opts.labelHeight || 0,
                maxWidth = labelWidth || (axis.direction == "x" ? Math.floor(surface.width / (ticks.length || 1)) : null),
                legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
                layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
                font = opts.font || "flot-tick-label tickLabel";

            for (var i = 0; i < ticks.length; ++i) {

                var t = ticks[i];

                if (!t.label)
                    continue;

                var info = surface.getTextInfo(layer, t.label, font, null, maxWidth);

                labelWidth = Math.max(labelWidth, info.width);
                labelHeight = Math.max(labelHeight, info.height);
            }

            axis.labelWidth = opts.labelWidth || labelWidth;
            axis.labelHeight = opts.labelHeight || labelHeight;
        }

        function allocateAxisBoxFirstPhase(axis) {
            // find the bounding box of the axis by looking at label
            // widths/heights and ticks, make room by diminishing the
            // plotOffset; this first phase only looks at one
            // dimension per axis, the other dimension depends on the
            // other axes so will have to wait

            var lw = axis.labelWidth,
                lh = axis.labelHeight,
                pos = axis.options.position,
                isXAxis = axis.direction === "x",
                tickLength = axis.options.tickLength,
                axisMargin = options.grid.axisMargin,
                padding = options.grid.labelMargin,
                innermost = true,
                outermost = true,
                first = true,
                found = false;

            // Determine the axis's position in its direction and on its side

            $.each(isXAxis ? xaxes : yaxes, function(i, a) {
                if (a && (a.show || a.reserveSpace)) {
                    if (a === axis) {
                        found = true;
                    } else if (a.options.position === pos) {
                        if (found) {
                            outermost = false;
                        } else {
                            innermost = false;
                        }
                    }
                    if (!found) {
                        first = false;
                    }
                }
            });

            // The outermost axis on each side has no margin

            if (outermost) {
                axisMargin = 0;
            }

            // The ticks for the first axis in each direction stretch across

            if (tickLength == null) {
                tickLength = first ? "full" : 5;
            }

            if (!isNaN(+tickLength))
                padding += +tickLength;

            if (isXAxis) {
                lh += padding;

                if (pos == "bottom") {
                    plotOffset.bottom += lh + axisMargin;
                    axis.box = { top: surface.height - plotOffset.bottom, height: lh };
                }
                else {
                    axis.box = { top: plotOffset.top + axisMargin, height: lh };
                    plotOffset.top += lh + axisMargin;
                }
            }
            else {
                lw += padding;

                if (pos == "left") {
                    axis.box = { left: plotOffset.left + axisMargin, width: lw };
                    plotOffset.left += lw + axisMargin;
                }
                else {
                    plotOffset.right += lw + axisMargin;
                    axis.box = { left: surface.width - plotOffset.right, width: lw };
                }
            }

             // save for future reference
            axis.position = pos;
            axis.tickLength = tickLength;
            axis.box.padding = padding;
            axis.innermost = innermost;
        }

        function allocateAxisBoxSecondPhase(axis) {
            // now that all axis boxes have been placed in one
            // dimension, we can set the remaining dimension coordinates
            if (axis.direction == "x") {
                axis.box.left = plotOffset.left - axis.labelWidth / 2;
                axis.box.width = surface.width - plotOffset.left - plotOffset.right + axis.labelWidth;
            }
            else {
                axis.box.top = plotOffset.top - axis.labelHeight / 2;
                axis.box.height = surface.height - plotOffset.bottom - plotOffset.top + axis.labelHeight;
            }
        }

        function adjustLayoutForThingsStickingOut() {
            // possibly adjust plot offset to ensure everything stays
            // inside the canvas and isn't clipped off

            var minMargin = options.grid.minBorderMargin,
                axis, i;

            // check stuff from the plot (FIXME: this should just read
            // a value from the series, otherwise it's impossible to
            // customize)
            if (minMargin == null) {
                minMargin = 0;
                for (i = 0; i < series.length; ++i)
                    minMargin = Math.max(minMargin, 2 * (series[i].points.radius + series[i].points.lineWidth/2));
            }

            var margins = {
                left: minMargin,
                right: minMargin,
                top: minMargin,
                bottom: minMargin
            };

            // check axis labels, note we don't check the actual
            // labels but instead use the overall width/height to not
            // jump as much around with replots
            $.each(allAxes(), function (_, axis) {
                if (axis.reserveSpace && axis.ticks && axis.ticks.length) {
                    if (axis.direction === "x") {
                        margins.left = Math.max(margins.left, axis.labelWidth / 2);
                        margins.right = Math.max(margins.right, axis.labelWidth / 2);
                    } else {
                        margins.bottom = Math.max(margins.bottom, axis.labelHeight / 2);
                        margins.top = Math.max(margins.top, axis.labelHeight / 2);
                    }
                }
            });

            plotOffset.left = Math.ceil(Math.max(margins.left, plotOffset.left));
            plotOffset.right = Math.ceil(Math.max(margins.right, plotOffset.right));
            plotOffset.top = Math.ceil(Math.max(margins.top, plotOffset.top));
            plotOffset.bottom = Math.ceil(Math.max(margins.bottom, plotOffset.bottom));
        }

        function setupGrid() {
            var i, axes = allAxes(), showGrid = options.grid.show;

            // Initialize the plot's offset from the edge of the canvas

            for (var a in plotOffset) {
                var margin = options.grid.margin || 0;
                plotOffset[a] = typeof margin == "number" ? margin : margin[a] || 0;
            }

            executeHooks(hooks.processOffset, [plotOffset]);

            // If the grid is visible, add its border width to the offset

            for (var a in plotOffset) {
                if(typeof(options.grid.borderWidth) == "object") {
                    plotOffset[a] += showGrid ? options.grid.borderWidth[a] : 0;
                }
                else {
                    plotOffset[a] += showGrid ? options.grid.borderWidth : 0;
                }
            }

            $.each(axes, function (_, axis) {
                var axisOpts = axis.options;
                axis.show = axisOpts.show == null ? axis.used : axisOpts.show;
                axis.reserveSpace = axisOpts.reserveSpace == null ? axis.show : axisOpts.reserveSpace;
                setRange(axis);
            });

            if (showGrid) {

                var allocatedAxes = $.grep(axes, function (axis) {
                    return axis.show || axis.reserveSpace;
                });

                $.each(allocatedAxes, function (_, axis) {
                    // make the ticks
                    setupTickGeneration(axis);
                    setTicks(axis);
                    snapRangeToTicks(axis, axis.ticks);
                    // find labelWidth/Height for axis
                    measureTickLabels(axis);
                });

                // with all dimensions calculated, we can compute the
                // axis bounding boxes, start from the outside
                // (reverse order)
                for (i = allocatedAxes.length - 1; i >= 0; --i)
                    allocateAxisBoxFirstPhase(allocatedAxes[i]);

                // make sure we've got enough space for things that
                // might stick out
                adjustLayoutForThingsStickingOut();

                $.each(allocatedAxes, function (_, axis) {
                    allocateAxisBoxSecondPhase(axis);
                });
            }

            plotWidth = surface.width - plotOffset.left - plotOffset.right;
            plotHeight = surface.height - plotOffset.bottom - plotOffset.top;

            // now we got the proper plot dimensions, we can compute the scaling
            $.each(axes, function (_, axis) {
                setTransformationHelpers(axis);
            });

            if (showGrid) {
                drawAxisLabels();
            }

            insertLegend();
        }

        function setRange(axis) {
            var opts = axis.options,
                min = +(opts.min != null ? opts.min : axis.datamin),
                max = +(opts.max != null ? opts.max : axis.datamax),
                delta = max - min;

            if (delta == 0.0) {
                // degenerate case
                var widen = max == 0 ? 1 : 0.01;

                if (opts.min == null)
                    min -= widen;
                // always widen max if we couldn't widen min to ensure we
                // don't fall into min == max which doesn't work
                if (opts.max == null || opts.min != null)
                    max += widen;
            }
            else {
                // consider autoscaling
                var margin = opts.autoscaleMargin;
                if (margin != null) {
                    if (opts.min == null) {
                        min -= delta * margin;
                        // make sure we don't go below zero if all values
                        // are positive
                        if (min < 0 && axis.datamin != null && axis.datamin >= 0)
                            min = 0;
                    }
                    if (opts.max == null) {
                        max += delta * margin;
                        if (max > 0 && axis.datamax != null && axis.datamax <= 0)
                            max = 0;
                    }
                }
            }
            axis.min = min;
            axis.max = max;
        }

        function setupTickGeneration(axis) {
            var opts = axis.options;

            // estimate number of ticks
            var noTicks;
            if (typeof opts.ticks == "number" && opts.ticks > 0)
                noTicks = opts.ticks;
            else
                // heuristic based on the model a*sqrt(x) fitted to
                // some data points that seemed reasonable
                noTicks = 0.3 * Math.sqrt(axis.direction == "x" ? surface.width : surface.height);

            var delta = (axis.max - axis.min) / noTicks,
                dec = -Math.floor(Math.log(delta) / Math.LN10),
                maxDec = opts.tickDecimals;

            if (maxDec != null && dec > maxDec) {
                dec = maxDec;
            }

            var magn = Math.pow(10, -dec),
                norm = delta / magn, // norm is between 1.0 and 10.0
                size;

            if (norm < 1.5) {
                size = 1;
            } else if (norm < 3) {
                size = 2;
                // special case for 2.5, requires an extra decimal
                if (norm > 2.25 && (maxDec == null || dec + 1 <= maxDec)) {
                    size = 2.5;
                    ++dec;
                }
            } else if (norm < 7.5) {
                size = 5;
            } else {
                size = 10;
            }

            size *= magn;

            if (opts.minTickSize != null && size < opts.minTickSize) {
                size = opts.minTickSize;
            }

            axis.delta = delta;
            axis.tickDecimals = Math.max(0, maxDec != null ? maxDec : dec);
            axis.tickSize = opts.tickSize || size;

            // Time mode was moved to a plug-in in 0.8, and since so many people use it
            // we'll add an especially friendly reminder to make sure they included it.

            if (opts.mode == "time" && !axis.tickGenerator) {
                throw new Error("Time mode requires the flot.time plugin.");
            }

            // Flot supports base-10 axes; any other mode else is handled by a plug-in,
            // like flot.time.js.

            if (!axis.tickGenerator) {

                axis.tickGenerator = function (axis) {

                    var ticks = [],
                        start = floorInBase(axis.min, axis.tickSize),
                        i = 0,
                        v = Number.NaN,
                        prev;

                    do {
                        prev = v;
                        v = start + i * axis.tickSize;
                        ticks.push(v);
                        ++i;
                    } while (v < axis.max && v != prev);
                    return ticks;
                };

				axis.tickFormatter = function (value, axis) {

					var factor = axis.tickDecimals ? Math.pow(10, axis.tickDecimals) : 1;
					var formatted = "" + Math.round(value * factor) / factor;

					// If tickDecimals was specified, ensure that we have exactly that
					// much precision; otherwise default to the value's own precision.

					if (axis.tickDecimals != null) {
						var decimal = formatted.indexOf(".");
						var precision = decimal == -1 ? 0 : formatted.length - decimal - 1;
						if (precision < axis.tickDecimals) {
							return (precision ? formatted : formatted + ".") + ("" + factor).substr(1, axis.tickDecimals - precision);
						}
					}

                    return formatted;
                };
            }

            if ($.isFunction(opts.tickFormatter))
                axis.tickFormatter = function (v, axis) { return "" + opts.tickFormatter(v, axis); };

            if (opts.alignTicksWithAxis != null) {
                var otherAxis = (axis.direction == "x" ? xaxes : yaxes)[opts.alignTicksWithAxis - 1];
                if (otherAxis && otherAxis.used && otherAxis != axis) {
                    // consider snapping min/max to outermost nice ticks
                    var niceTicks = axis.tickGenerator(axis);
                    if (niceTicks.length > 0) {
                        if (opts.min == null)
                            axis.min = Math.min(axis.min, niceTicks[0]);
                        if (opts.max == null && niceTicks.length > 1)
                            axis.max = Math.max(axis.max, niceTicks[niceTicks.length - 1]);
                    }

                    axis.tickGenerator = function (axis) {
                        // copy ticks, scaled to this axis
                        var ticks = [], v, i;
                        for (i = 0; i < otherAxis.ticks.length; ++i) {
                            v = (otherAxis.ticks[i].v - otherAxis.min) / (otherAxis.max - otherAxis.min);
                            v = axis.min + v * (axis.max - axis.min);
                            ticks.push(v);
                        }
                        return ticks;
                    };

                    // we might need an extra decimal since forced
                    // ticks don't necessarily fit naturally
                    if (!axis.mode && opts.tickDecimals == null) {
                        var extraDec = Math.max(0, -Math.floor(Math.log(axis.delta) / Math.LN10) + 1),
                            ts = axis.tickGenerator(axis);

                        // only proceed if the tick interval rounded
                        // with an extra decimal doesn't give us a
                        // zero at end
                        if (!(ts.length > 1 && /\..*0$/.test((ts[1] - ts[0]).toFixed(extraDec))))
                            axis.tickDecimals = extraDec;
                    }
                }
            }
        }

        function setTicks(axis) {
            var oticks = axis.options.ticks, ticks = [];
            if (oticks == null || (typeof oticks == "number" && oticks > 0))
                ticks = axis.tickGenerator(axis);
            else if (oticks) {
                if ($.isFunction(oticks))
                    // generate the ticks
                    ticks = oticks(axis);
                else
                    ticks = oticks;
            }

            // clean up/labelify the supplied ticks, copy them over
            var i, v;
            axis.ticks = [];
            for (i = 0; i < ticks.length; ++i) {
                var label = null;
                var t = ticks[i];
                if (typeof t == "object") {
                    v = +t[0];
                    if (t.length > 1)
                        label = t[1];
                }
                else
                    v = +t;
                if (label == null)
                    label = axis.tickFormatter(v, axis);
                if (!isNaN(v))
                    axis.ticks.push({ v: v, label: label });
            }
        }

        function snapRangeToTicks(axis, ticks) {
            if (axis.options.autoscaleMargin && ticks.length > 0) {
                // snap to ticks
                if (axis.options.min == null)
                    axis.min = Math.min(axis.min, ticks[0].v);
                if (axis.options.max == null && ticks.length > 1)
                    axis.max = Math.max(axis.max, ticks[ticks.length - 1].v);
            }
        }

        function draw() {

            surface.clear();

            executeHooks(hooks.drawBackground, [ctx]);

            var grid = options.grid;

            // draw background, if any
            if (grid.show && grid.backgroundColor)
                drawBackground();

            if (grid.show && !grid.aboveData) {
                drawGrid();
            }

            for (var i = 0; i < series.length; ++i) {
                executeHooks(hooks.drawSeries, [ctx, series[i]]);
                drawSeries(series[i]);
            }

            executeHooks(hooks.draw, [ctx]);

            if (grid.show && grid.aboveData) {
                drawGrid();
            }

            surface.render();

            // A draw implies that either the axes or data have changed, so we
            // should probably update the overlay highlights as well.

            triggerRedrawOverlay();
        }

        function extractRange(ranges, coord) {
            var axis, from, to, key, axes = allAxes();

            for (var i = 0; i < axes.length; ++i) {
                axis = axes[i];
                if (axis.direction == coord) {
                    key = coord + axis.n + "axis";
                    if (!ranges[key] && axis.n == 1)
                        key = coord + "axis"; // support x1axis as xaxis
                    if (ranges[key]) {
                        from = ranges[key].from;
                        to = ranges[key].to;
                        break;
                    }
                }
            }

            // backwards-compat stuff - to be removed in future
            if (!ranges[key]) {
                axis = coord == "x" ? xaxes[0] : yaxes[0];
                from = ranges[coord + "1"];
                to = ranges[coord + "2"];
            }

            // auto-reverse as an added bonus
            if (from != null && to != null && from > to) {
                var tmp = from;
                from = to;
                to = tmp;
            }

            return { from: from, to: to, axis: axis };
        }

        function drawBackground() {
            ctx.save();
            ctx.translate(plotOffset.left, plotOffset.top);

            ctx.fillStyle = getColorOrGradient(options.grid.backgroundColor, plotHeight, 0, "rgba(255, 255, 255, 0)");
            ctx.fillRect(0, 0, plotWidth, plotHeight);
            ctx.restore();
        }

        function drawGrid() {
            var i, axes, bw, bc;

            ctx.save();
            ctx.translate(plotOffset.left, plotOffset.top);

            // draw markings
            var markings = options.grid.markings;
            if (markings) {
                if ($.isFunction(markings)) {
                    axes = plot.getAxes();
                    // xmin etc. is backwards compatibility, to be
                    // removed in the future
                    axes.xmin = axes.xaxis.min;
                    axes.xmax = axes.xaxis.max;
                    axes.ymin = axes.yaxis.min;
                    axes.ymax = axes.yaxis.max;

                    markings = markings(axes);
                }

                for (i = 0; i < markings.length; ++i) {
                    var m = markings[i],
                        xrange = extractRange(m, "x"),
                        yrange = extractRange(m, "y");

                    // fill in missing
                    if (xrange.from == null)
                        xrange.from = xrange.axis.min;
                    if (xrange.to == null)
                        xrange.to = xrange.axis.max;
                    if (yrange.from == null)
                        yrange.from = yrange.axis.min;
                    if (yrange.to == null)
                        yrange.to = yrange.axis.max;

                    // clip
                    if (xrange.to < xrange.axis.min || xrange.from > xrange.axis.max ||
                        yrange.to < yrange.axis.min || yrange.from > yrange.axis.max)
                        continue;

                    xrange.from = Math.max(xrange.from, xrange.axis.min);
                    xrange.to = Math.min(xrange.to, xrange.axis.max);
                    yrange.from = Math.max(yrange.from, yrange.axis.min);
                    yrange.to = Math.min(yrange.to, yrange.axis.max);

                    var xequal = xrange.from === xrange.to,
                        yequal = yrange.from === yrange.to;

                    if (xequal && yequal) {
                        continue;
                    }

                    // then draw
                    xrange.from = Math.floor(xrange.axis.p2c(xrange.from));
                    xrange.to = Math.floor(xrange.axis.p2c(xrange.to));
                    yrange.from = Math.floor(yrange.axis.p2c(yrange.from));
                    yrange.to = Math.floor(yrange.axis.p2c(yrange.to));

                    if (xequal || yequal) {
                        var lineWidth = m.lineWidth || options.grid.markingsLineWidth,
                            subPixel = lineWidth % 2 ? 0.5 : 0;
                        ctx.beginPath();
                        ctx.strokeStyle = m.color || options.grid.markingsColor;
                        ctx.lineWidth = lineWidth;
                        if (xequal) {
                            ctx.moveTo(xrange.to + subPixel, yrange.from);
                            ctx.lineTo(xrange.to + subPixel, yrange.to);
                        } else {
                            ctx.moveTo(xrange.from, yrange.to + subPixel);
                            ctx.lineTo(xrange.to, yrange.to + subPixel);                            
                        }
                        ctx.stroke();
                    } else {
                        ctx.fillStyle = m.color || options.grid.markingsColor;
                        ctx.fillRect(xrange.from, yrange.to,
                                     xrange.to - xrange.from,
                                     yrange.from - yrange.to);
                    }
                }
            }

            // draw the ticks
            axes = allAxes();
            bw = options.grid.borderWidth;

            for (var j = 0; j < axes.length; ++j) {
                var axis = axes[j], box = axis.box,
                    t = axis.tickLength, x, y, xoff, yoff;
                if (!axis.show || axis.ticks.length == 0)
                    continue;

                ctx.lineWidth = 1;

                // find the edges
                if (axis.direction == "x") {
                    x = 0;
                    if (t == "full")
                        y = (axis.position == "top" ? 0 : plotHeight);
                    else
                        y = box.top - plotOffset.top + (axis.position == "top" ? box.height : 0);
                }
                else {
                    y = 0;
                    if (t == "full")
                        x = (axis.position == "left" ? 0 : plotWidth);
                    else
                        x = box.left - plotOffset.left + (axis.position == "left" ? box.width : 0);
                }

                // draw tick bar
                if (!axis.innermost) {
                    ctx.strokeStyle = axis.options.color;
                    ctx.beginPath();
                    xoff = yoff = 0;
                    if (axis.direction == "x")
                        xoff = plotWidth + 1;
                    else
                        yoff = plotHeight + 1;

                    if (ctx.lineWidth == 1) {
                        if (axis.direction == "x") {
                            y = Math.floor(y) + 0.5;
                        } else {
                            x = Math.floor(x) + 0.5;
                        }
                    }

                    ctx.moveTo(x, y);
                    ctx.lineTo(x + xoff, y + yoff);
                    ctx.stroke();
                }

                // draw ticks

                ctx.strokeStyle = axis.options.tickColor;

                ctx.beginPath();
                for (i = 0; i < axis.ticks.length; ++i) {
                    var v = axis.ticks[i].v;

                    xoff = yoff = 0;

                    if (isNaN(v) || v < axis.min || v > axis.max
                        // skip those lying on the axes if we got a border
                        || (t == "full"
                            && ((typeof bw == "object" && bw[axis.position] > 0) || bw > 0)
                            && (v == axis.min || v == axis.max)))
                        continue;

                    if (axis.direction == "x") {
                        x = axis.p2c(v);
                        yoff = t == "full" ? -plotHeight : t;

                        if (axis.position == "top")
                            yoff = -yoff;
                    }
                    else {
                        y = axis.p2c(v);
                        xoff = t == "full" ? -plotWidth : t;

                        if (axis.position == "left")
                            xoff = -xoff;
                    }

                    if (ctx.lineWidth == 1) {
                        if (axis.direction == "x")
                            x = Math.floor(x) + 0.5;
                        else
                            y = Math.floor(y) + 0.5;
                    }

                    ctx.moveTo(x, y);
                    ctx.lineTo(x + xoff, y + yoff);
                }

                ctx.stroke();
            }


            // draw border
            if (bw) {
                // If either borderWidth or borderColor is an object, then draw the border
                // line by line instead of as one rectangle
                bc = options.grid.borderColor;
                if(typeof bw == "object" || typeof bc == "object") {
                    if (typeof bw !== "object") {
                        bw = {top: bw, right: bw, bottom: bw, left: bw};
                    }
                    if (typeof bc !== "object") {
                        bc = {top: bc, right: bc, bottom: bc, left: bc};
                    }

                    if (bw.top > 0) {
                        ctx.strokeStyle = bc.top;
                        ctx.lineWidth = bw.top;
                        ctx.beginPath();
                        ctx.moveTo(0 - bw.left, 0 - bw.top/2);
                        ctx.lineTo(plotWidth, 0 - bw.top/2);
                        ctx.stroke();
                    }

                    if (bw.right > 0) {
                        ctx.strokeStyle = bc.right;
                        ctx.lineWidth = bw.right;
                        ctx.beginPath();
                        ctx.moveTo(plotWidth + bw.right / 2, 0 - bw.top);
                        ctx.lineTo(plotWidth + bw.right / 2, plotHeight);
                        ctx.stroke();
                    }

                    if (bw.bottom > 0) {
                        ctx.strokeStyle = bc.bottom;
                        ctx.lineWidth = bw.bottom;
                        ctx.beginPath();
                        ctx.moveTo(plotWidth + bw.right, plotHeight + bw.bottom / 2);
                        ctx.lineTo(0, plotHeight + bw.bottom / 2);
                        ctx.stroke();
                    }

                    if (bw.left > 0) {
                        ctx.strokeStyle = bc.left;
                        ctx.lineWidth = bw.left;
                        ctx.beginPath();
                        ctx.moveTo(0 - bw.left/2, plotHeight + bw.bottom);
                        ctx.lineTo(0- bw.left/2, 0);
                        ctx.stroke();
                    }
                }
                else {
                    ctx.lineWidth = bw;
                    ctx.strokeStyle = options.grid.borderColor;
                    ctx.strokeRect(-bw/2, -bw/2, plotWidth + bw, plotHeight + bw);
                }
            }

            ctx.restore();
        }

        function drawAxisLabels() {

            $.each(allAxes(), function (_, axis) {
                var box = axis.box,
                    legacyStyles = axis.direction + "Axis " + axis.direction + axis.n + "Axis",
                    layer = "flot-" + axis.direction + "-axis flot-" + axis.direction + axis.n + "-axis " + legacyStyles,
                    font = axis.options.font || "flot-tick-label tickLabel",
                    tick, x, y, halign, valign;

                // Remove text before checking for axis.show and ticks.length;
                // otherwise plugins, like flot-tickrotor, that draw their own
                // tick labels will end up with both theirs and the defaults.

                surface.removeText(layer);

                if (!axis.show || axis.ticks.length == 0)
                    return;

                for (var i = 0; i < axis.ticks.length; ++i) {

                    tick = axis.ticks[i];
                    if (!tick.label || tick.v < axis.min || tick.v > axis.max)
                        continue;

                    if (axis.direction == "x") {
                        halign = "center";
                        x = plotOffset.left + axis.p2c(tick.v);
                        if (axis.position == "bottom") {
                            y = box.top + box.padding;
                        } else {
                            y = box.top + box.height - box.padding;
                            valign = "bottom";
                        }
                    } else {
                        valign = "middle";
                        y = plotOffset.top + axis.p2c(tick.v);
                        if (axis.position == "left") {
                            x = box.left + box.width - box.padding;
                            halign = "right";
                        } else {
                            x = box.left + box.padding;
                        }
                    }

                    surface.addText(layer, x, y, tick.label, font, null, null, halign, valign);
                }
            });
        }

        function drawSeries(series) {
            if (series.lines.show)
                drawSeriesLines(series);
            if (series.bars.show)
                drawSeriesBars(series);
            if (series.points.show)
                drawSeriesPoints(series);
        }

        function drawSeriesLines(series) {
            function plotLine(datapoints, xoffset, yoffset, axisx, axisy) {
                var points = datapoints.points,
                    ps = datapoints.pointsize,
                    prevx = null, prevy = null;

                ctx.beginPath();
                for (var i = ps; i < points.length; i += ps) {
                    var x1 = points[i - ps], y1 = points[i - ps + 1],
                        x2 = points[i], y2 = points[i + 1];

                    if (x1 == null || x2 == null)
                        continue;

                    // clip with ymin
                    if (y1 <= y2 && y1 < axisy.min) {
                        if (y2 < axisy.min)
                            continue;   // line segment is outside
                        // compute new intersection point
                        x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y1 = axisy.min;
                    }
                    else if (y2 <= y1 && y2 < axisy.min) {
                        if (y1 < axisy.min)
                            continue;
                        x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y2 = axisy.min;
                    }

                    // clip with ymax
                    if (y1 >= y2 && y1 > axisy.max) {
                        if (y2 > axisy.max)
                            continue;
                        x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y1 = axisy.max;
                    }
                    else if (y2 >= y1 && y2 > axisy.max) {
                        if (y1 > axisy.max)
                            continue;
                        x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y2 = axisy.max;
                    }

                    // clip with xmin
                    if (x1 <= x2 && x1 < axisx.min) {
                        if (x2 < axisx.min)
                            continue;
                        y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x1 = axisx.min;
                    }
                    else if (x2 <= x1 && x2 < axisx.min) {
                        if (x1 < axisx.min)
                            continue;
                        y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x2 = axisx.min;
                    }

                    // clip with xmax
                    if (x1 >= x2 && x1 > axisx.max) {
                        if (x2 > axisx.max)
                            continue;
                        y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x1 = axisx.max;
                    }
                    else if (x2 >= x1 && x2 > axisx.max) {
                        if (x1 > axisx.max)
                            continue;
                        y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x2 = axisx.max;
                    }

                    if (x1 != prevx || y1 != prevy)
                        ctx.moveTo(axisx.p2c(x1) + xoffset, axisy.p2c(y1) + yoffset);

                    prevx = x2;
                    prevy = y2;
                    ctx.lineTo(axisx.p2c(x2) + xoffset, axisy.p2c(y2) + yoffset);
                }
                ctx.stroke();
            }

            function plotLineArea(datapoints, axisx, axisy) {
                var points = datapoints.points,
                    ps = datapoints.pointsize,
                    bottom = Math.min(Math.max(0, axisy.min), axisy.max),
                    i = 0, top, areaOpen = false,
                    ypos = 1, segmentStart = 0, segmentEnd = 0;

                // we process each segment in two turns, first forward
                // direction to sketch out top, then once we hit the
                // end we go backwards to sketch the bottom
                while (true) {
                    if (ps > 0 && i > points.length + ps)
                        break;

                    i += ps; // ps is negative if going backwards

                    var x1 = points[i - ps],
                        y1 = points[i - ps + ypos],
                        x2 = points[i], y2 = points[i + ypos];

                    if (areaOpen) {
                        if (ps > 0 && x1 != null && x2 == null) {
                            // at turning point
                            segmentEnd = i;
                            ps = -ps;
                            ypos = 2;
                            continue;
                        }

                        if (ps < 0 && i == segmentStart + ps) {
                            // done with the reverse sweep
                            ctx.fill();
                            areaOpen = false;
                            ps = -ps;
                            ypos = 1;
                            i = segmentStart = segmentEnd + ps;
                            continue;
                        }
                    }

                    if (x1 == null || x2 == null)
                        continue;

                    // clip x values

                    // clip with xmin
                    if (x1 <= x2 && x1 < axisx.min) {
                        if (x2 < axisx.min)
                            continue;
                        y1 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x1 = axisx.min;
                    }
                    else if (x2 <= x1 && x2 < axisx.min) {
                        if (x1 < axisx.min)
                            continue;
                        y2 = (axisx.min - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x2 = axisx.min;
                    }

                    // clip with xmax
                    if (x1 >= x2 && x1 > axisx.max) {
                        if (x2 > axisx.max)
                            continue;
                        y1 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x1 = axisx.max;
                    }
                    else if (x2 >= x1 && x2 > axisx.max) {
                        if (x1 > axisx.max)
                            continue;
                        y2 = (axisx.max - x1) / (x2 - x1) * (y2 - y1) + y1;
                        x2 = axisx.max;
                    }

                    if (!areaOpen) {
                        // open area
                        ctx.beginPath();
                        ctx.moveTo(axisx.p2c(x1), axisy.p2c(bottom));
                        areaOpen = true;
                    }

                    // now first check the case where both is outside
                    if (y1 >= axisy.max && y2 >= axisy.max) {
                        ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.max));
                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.max));
                        continue;
                    }
                    else if (y1 <= axisy.min && y2 <= axisy.min) {
                        ctx.lineTo(axisx.p2c(x1), axisy.p2c(axisy.min));
                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(axisy.min));
                        continue;
                    }

                    // else it's a bit more complicated, there might
                    // be a flat maxed out rectangle first, then a
                    // triangular cutout or reverse; to find these
                    // keep track of the current x values
                    var x1old = x1, x2old = x2;

                    // clip the y values, without shortcutting, we
                    // go through all cases in turn

                    // clip with ymin
                    if (y1 <= y2 && y1 < axisy.min && y2 >= axisy.min) {
                        x1 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y1 = axisy.min;
                    }
                    else if (y2 <= y1 && y2 < axisy.min && y1 >= axisy.min) {
                        x2 = (axisy.min - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y2 = axisy.min;
                    }

                    // clip with ymax
                    if (y1 >= y2 && y1 > axisy.max && y2 <= axisy.max) {
                        x1 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y1 = axisy.max;
                    }
                    else if (y2 >= y1 && y2 > axisy.max && y1 <= axisy.max) {
                        x2 = (axisy.max - y1) / (y2 - y1) * (x2 - x1) + x1;
                        y2 = axisy.max;
                    }

                    // if the x value was changed we got a rectangle
                    // to fill
                    if (x1 != x1old) {
                        ctx.lineTo(axisx.p2c(x1old), axisy.p2c(y1));
                        // it goes to (x1, y1), but we fill that below
                    }

                    // fill triangular section, this sometimes result
                    // in redundant points if (x1, y1) hasn't changed
                    // from previous line to, but we just ignore that
                    ctx.lineTo(axisx.p2c(x1), axisy.p2c(y1));
                    ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));

                    // fill the other rectangle if it's there
                    if (x2 != x2old) {
                        ctx.lineTo(axisx.p2c(x2), axisy.p2c(y2));
                        ctx.lineTo(axisx.p2c(x2old), axisy.p2c(y2));
                    }
                }
            }

            ctx.save();
            ctx.translate(plotOffset.left, plotOffset.top);
            ctx.lineJoin = "round";

            var lw = series.lines.lineWidth,
                sw = series.shadowSize;
            // FIXME: consider another form of shadow when filling is turned on
            if (lw > 0 && sw > 0) {
                // draw shadow as a thick and thin line with transparency
                ctx.lineWidth = sw;
                ctx.strokeStyle = "rgba(0,0,0,0.1)";
                // position shadow at angle from the mid of line
                var angle = Math.PI/18;
                plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/2), Math.cos(angle) * (lw/2 + sw/2), series.xaxis, series.yaxis);
                ctx.lineWidth = sw/2;
                plotLine(series.datapoints, Math.sin(angle) * (lw/2 + sw/4), Math.cos(angle) * (lw/2 + sw/4), series.xaxis, series.yaxis);
            }

            ctx.lineWidth = lw;
            ctx.strokeStyle = series.color;
            var fillStyle = getFillStyle(series.lines, series.color, 0, plotHeight);
            if (fillStyle) {
                ctx.fillStyle = fillStyle;
                plotLineArea(series.datapoints, series.xaxis, series.yaxis);
            }

            if (lw > 0)
                plotLine(series.datapoints, 0, 0, series.xaxis, series.yaxis);
            ctx.restore();
        }

        function drawSeriesPoints(series) {
            function plotPoints(datapoints, radius, fillStyle, offset, shadow, axisx, axisy, symbol) {
                var points = datapoints.points, ps = datapoints.pointsize;

                for (var i = 0; i < points.length; i += ps) {
                    var x = points[i], y = points[i + 1];
                    if (x == null || x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
                        continue;

                    ctx.beginPath();
                    x = axisx.p2c(x);
                    y = axisy.p2c(y) + offset;
                    if (symbol == "circle")
                        ctx.arc(x, y, radius, 0, shadow ? Math.PI : Math.PI * 2, false);
                    else
                        symbol(ctx, x, y, radius, shadow);
                    ctx.closePath();

                    if (fillStyle) {
                        ctx.fillStyle = fillStyle;
                        ctx.fill();
                    }
                    ctx.stroke();
                }
            }

            ctx.save();
            ctx.translate(plotOffset.left, plotOffset.top);

            var lw = series.points.lineWidth,
                sw = series.shadowSize,
                radius = series.points.radius,
                symbol = series.points.symbol;

            // If the user sets the line width to 0, we change it to a very 
            // small value. A line width of 0 seems to force the default of 1.
            // Doing the conditional here allows the shadow setting to still be 
            // optional even with a lineWidth of 0.

            if( lw == 0 )
                lw = 0.0001;

            if (lw > 0 && sw > 0) {
                // draw shadow in two steps
                var w = sw / 2;
                ctx.lineWidth = w;
                ctx.strokeStyle = "rgba(0,0,0,0.1)";
                plotPoints(series.datapoints, radius, null, w + w/2, true,
                           series.xaxis, series.yaxis, symbol);

                ctx.strokeStyle = "rgba(0,0,0,0.2)";
                plotPoints(series.datapoints, radius, null, w/2, true,
                           series.xaxis, series.yaxis, symbol);
            }

            ctx.lineWidth = lw;
            ctx.strokeStyle = series.color;
            plotPoints(series.datapoints, radius,
                       getFillStyle(series.points, series.color), 0, false,
                       series.xaxis, series.yaxis, symbol);
            ctx.restore();
        }

        function drawBar(x, y, b, barLeft, barRight, fillStyleCallback, axisx, axisy, c, horizontal, lineWidth) {
            var left, right, bottom, top,
                drawLeft, drawRight, drawTop, drawBottom,
                tmp;

            // in horizontal mode, we start the bar from the left
            // instead of from the bottom so it appears to be
            // horizontal rather than vertical
            if (horizontal) {
                drawBottom = drawRight = drawTop = true;
                drawLeft = false;
                left = b;
                right = x;
                top = y + barLeft;
                bottom = y + barRight;

                // account for negative bars
                if (right < left) {
                    tmp = right;
                    right = left;
                    left = tmp;
                    drawLeft = true;
                    drawRight = false;
                }
            }
            else {
                drawLeft = drawRight = drawTop = true;
                drawBottom = false;
                left = x + barLeft;
                right = x + barRight;
                bottom = b;
                top = y;

                // account for negative bars
                if (top < bottom) {
                    tmp = top;
                    top = bottom;
                    bottom = tmp;
                    drawBottom = true;
                    drawTop = false;
                }
            }

            // clip
            if (right < axisx.min || left > axisx.max ||
                top < axisy.min || bottom > axisy.max)
                return;

            if (left < axisx.min) {
                left = axisx.min;
                drawLeft = false;
            }

            if (right > axisx.max) {
                right = axisx.max;
                drawRight = false;
            }

            if (bottom < axisy.min) {
                bottom = axisy.min;
                drawBottom = false;
            }

            if (top > axisy.max) {
                top = axisy.max;
                drawTop = false;
            }

            left = axisx.p2c(left);
            bottom = axisy.p2c(bottom);
            right = axisx.p2c(right);
            top = axisy.p2c(top);

            // fill the bar
            if (fillStyleCallback) {
                c.fillStyle = fillStyleCallback(bottom, top);
                c.fillRect(left, top, right - left, bottom - top)
            }

            // draw outline
            if (lineWidth > 0 && (drawLeft || drawRight || drawTop || drawBottom)) {
                c.beginPath();

                // FIXME: inline moveTo is buggy with excanvas
                c.moveTo(left, bottom);
                if (drawLeft)
                    c.lineTo(left, top);
                else
                    c.moveTo(left, top);
                if (drawTop)
                    c.lineTo(right, top);
                else
                    c.moveTo(right, top);
                if (drawRight)
                    c.lineTo(right, bottom);
                else
                    c.moveTo(right, bottom);
                if (drawBottom)
                    c.lineTo(left, bottom);
                else
                    c.moveTo(left, bottom);
                c.stroke();
            }
        }

        function drawSeriesBars(series) {
            function plotBars(datapoints, barLeft, barRight, fillStyleCallback, axisx, axisy) {
                var points = datapoints.points, ps = datapoints.pointsize;

                for (var i = 0; i < points.length; i += ps) {
                    if (points[i] == null)
                        continue;
                    drawBar(points[i], points[i + 1], points[i + 2], barLeft, barRight, fillStyleCallback, axisx, axisy, ctx, series.bars.horizontal, series.bars.lineWidth);
                }
            }

            ctx.save();
            ctx.translate(plotOffset.left, plotOffset.top);

            // FIXME: figure out a way to add shadows (for instance along the right edge)
            ctx.lineWidth = series.bars.lineWidth;
            ctx.strokeStyle = series.color;

            var barLeft;

            switch (series.bars.align) {
                case "left":
                    barLeft = 0;
                    break;
                case "right":
                    barLeft = -series.bars.barWidth;
                    break;
                default:
                    barLeft = -series.bars.barWidth / 2;
            }

            var fillStyleCallback = series.bars.fill ? function (bottom, top) { return getFillStyle(series.bars, series.color, bottom, top); } : null;
            plotBars(series.datapoints, barLeft, barLeft + series.bars.barWidth, fillStyleCallback, series.xaxis, series.yaxis);
            ctx.restore();
        }

        function getFillStyle(filloptions, seriesColor, bottom, top) {
            var fill = filloptions.fill;
            if (!fill)
                return null;

            if (filloptions.fillColor)
                return getColorOrGradient(filloptions.fillColor, bottom, top, seriesColor);

            var c = $.color.parse(seriesColor);
            c.a = typeof fill == "number" ? fill : 0.4;
            c.normalize();
            return c.toString();
        }

        function insertLegend() {

            if (options.legend.container != null) {
                $(options.legend.container).html("");
            } else {
                placeholder.find(".legend").remove();
            }

            if (!options.legend.show) {
                return;
            }

            var fragments = [], entries = [], rowStarted = false,
                lf = options.legend.labelFormatter, s, label;

            // Build a list of legend entries, with each having a label and a color

            for (var i = 0; i < series.length; ++i) {
                s = series[i];
                if (s.label) {
                    label = lf ? lf(s.label, s) : s.label;
                    if (label) {
                        entries.push({
                            label: label,
                            color: s.color
                        });
                    }
                }
            }

            // Sort the legend using either the default or a custom comparator

            if (options.legend.sorted) {
                if ($.isFunction(options.legend.sorted)) {
                    entries.sort(options.legend.sorted);
                } else if (options.legend.sorted == "reverse") {
                	entries.reverse();
                } else {
                    var ascending = options.legend.sorted != "descending";
                    entries.sort(function(a, b) {
                        return a.label == b.label ? 0 : (
                            (a.label < b.label) != ascending ? 1 : -1   // Logical XOR
                        );
                    });
                }
            }

            // Generate markup for the list of entries, in their final order

            for (var i = 0; i < entries.length; ++i) {

                var entry = entries[i];

                if (i % options.legend.noColumns == 0) {
                    if (rowStarted)
                        fragments.push('</tr>');
                    fragments.push('<tr>');
                    rowStarted = true;
                }

                fragments.push(
                    '<td class="legendColorBox"><div style="border:1px solid ' + options.legend.labelBoxBorderColor + ';padding:1px"><div style="width:4px;height:0;border:5px solid ' + entry.color + ';overflow:hidden"></div></div></td>' +
                    '<td class="legendLabel">' + entry.label + '</td>'
                );
            }

            if (rowStarted)
                fragments.push('</tr>');

            if (fragments.length == 0)
                return;

            var table = '<table style="font-size:smaller;color:' + options.grid.color + '">' + fragments.join("") + '</table>';
            if (options.legend.container != null)
                $(options.legend.container).html(table);
            else {
                var pos = "",
                    p = options.legend.position,
                    m = options.legend.margin;
                if (m[0] == null)
                    m = [m, m];
                if (p.charAt(0) == "n")
                    pos += 'top:' + (m[1] + plotOffset.top) + 'px;';
                else if (p.charAt(0) == "s")
                    pos += 'bottom:' + (m[1] + plotOffset.bottom) + 'px;';
                if (p.charAt(1) == "e")
                    pos += 'right:' + (m[0] + plotOffset.right) + 'px;';
                else if (p.charAt(1) == "w")
                    pos += 'left:' + (m[0] + plotOffset.left) + 'px;';
                var legend = $('<div class="legend">' + table.replace('style="', 'style="position:absolute;' + pos +';') + '</div>').appendTo(placeholder);
                if (options.legend.backgroundOpacity != 0.0) {
                    // put in the transparent background
                    // separately to avoid blended labels and
                    // label boxes
                    var c = options.legend.backgroundColor;
                    if (c == null) {
                        c = options.grid.backgroundColor;
                        if (c && typeof c == "string")
                            c = $.color.parse(c);
                        else
                            c = $.color.extract(legend, 'background-color');
                        c.a = 1;
                        c = c.toString();
                    }
                    var div = legend.children();
                    $('<div style="position:absolute;width:' + div.width() + 'px;height:' + div.height() + 'px;' + pos +'background-color:' + c + ';"> </div>').prependTo(legend).css('opacity', options.legend.backgroundOpacity);
                }
            }
        }


        // interactive features

        var highlights = [],
            redrawTimeout = null;

        // returns the data item the mouse is over, or null if none is found
        function findNearbyItem(mouseX, mouseY, seriesFilter) {
            var maxDistance = options.grid.mouseActiveRadius,
                smallestDistance = maxDistance * maxDistance + 1,
                item = null, foundPoint = false, i, j, ps;

            for (i = series.length - 1; i >= 0; --i) {
                if (!seriesFilter(series[i]))
                    continue;

                var s = series[i],
                    axisx = s.xaxis,
                    axisy = s.yaxis,
                    points = s.datapoints.points,
                    mx = axisx.c2p(mouseX), // precompute some stuff to make the loop faster
                    my = axisy.c2p(mouseY),
                    maxx = maxDistance / axisx.scale,
                    maxy = maxDistance / axisy.scale;

                ps = s.datapoints.pointsize;
                // with inverse transforms, we can't use the maxx/maxy
                // optimization, sadly
                if (axisx.options.inverseTransform)
                    maxx = Number.MAX_VALUE;
                if (axisy.options.inverseTransform)
                    maxy = Number.MAX_VALUE;

                if (s.lines.show || s.points.show) {
                    for (j = 0; j < points.length; j += ps) {
                        var x = points[j], y = points[j + 1];
                        if (x == null)
                            continue;

                        // For points and lines, the cursor must be within a
                        // certain distance to the data point
                        if (x - mx > maxx || x - mx < -maxx ||
                            y - my > maxy || y - my < -maxy)
                            continue;

                        // We have to calculate distances in pixels, not in
                        // data units, because the scales of the axes may be different
                        var dx = Math.abs(axisx.p2c(x) - mouseX),
                            dy = Math.abs(axisy.p2c(y) - mouseY),
                            dist = dx * dx + dy * dy; // we save the sqrt

                        // use <= to ensure last point takes precedence
                        // (last generally means on top of)
                        if (dist < smallestDistance) {
                            smallestDistance = dist;
                            item = [i, j / ps];
                        }
                    }
                }

                if (s.bars.show && !item) { // no other point can be nearby

                    var barLeft, barRight;

                    switch (s.bars.align) {
                        case "left":
                            barLeft = 0;
                            break;
                        case "right":
                            barLeft = -s.bars.barWidth;
                            break;
                        default:
                            barLeft = -s.bars.barWidth / 2;
                    }

                    barRight = barLeft + s.bars.barWidth;

                    for (j = 0; j < points.length; j += ps) {
                        var x = points[j], y = points[j + 1], b = points[j + 2];
                        if (x == null)
                            continue;

                        // for a bar graph, the cursor must be inside the bar
                        if (series[i].bars.horizontal ?
                            (mx <= Math.max(b, x) && mx >= Math.min(b, x) &&
                             my >= y + barLeft && my <= y + barRight) :
                            (mx >= x + barLeft && mx <= x + barRight &&
                             my >= Math.min(b, y) && my <= Math.max(b, y)))
                                item = [i, j / ps];
                    }
                }
            }

            if (item) {
                i = item[0];
                j = item[1];
                ps = series[i].datapoints.pointsize;

                return { datapoint: series[i].datapoints.points.slice(j * ps, (j + 1) * ps),
                         dataIndex: j,
                         series: series[i],
                         seriesIndex: i };
            }

            return null;
        }

        function onMouseMove(e) {
            if (options.grid.hoverable)
                triggerClickHoverEvent("plothover", e,
                                       function (s) { return s["hoverable"] != false; });
        }

        function onMouseLeave(e) {
            if (options.grid.hoverable)
                triggerClickHoverEvent("plothover", e,
                                       function (s) { return false; });
        }

        function onClick(e) {
            triggerClickHoverEvent("plotclick", e,
                                   function (s) { return s["clickable"] != false; });
        }

        // trigger click or hover event (they send the same parameters
        // so we share their code)
        function triggerClickHoverEvent(eventname, event, seriesFilter) {
            var offset = eventHolder.offset(),
                canvasX = event.pageX - offset.left - plotOffset.left,
                canvasY = event.pageY - offset.top - plotOffset.top,
            pos = canvasToAxisCoords({ left: canvasX, top: canvasY });

            pos.pageX = event.pageX;
            pos.pageY = event.pageY;

            var item = findNearbyItem(canvasX, canvasY, seriesFilter);

            if (item) {
                // fill in mouse pos for any listeners out there
                item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left + plotOffset.left, 10);
                item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top + plotOffset.top, 10);
            }

            if (options.grid.autoHighlight) {
                // clear auto-highlights
                for (var i = 0; i < highlights.length; ++i) {
                    var h = highlights[i];
                    if (h.auto == eventname &&
                        !(item && h.series == item.series &&
                          h.point[0] == item.datapoint[0] &&
                          h.point[1] == item.datapoint[1]))
                        unhighlight(h.series, h.point);
                }

                if (item)
                    highlight(item.series, item.datapoint, eventname);
            }

            placeholder.trigger(eventname, [ pos, item ]);
        }

        function triggerRedrawOverlay() {
            var t = options.interaction.redrawOverlayInterval;
            if (t == -1) {      // skip event queue
                drawOverlay();
                return;
            }

            if (!redrawTimeout)
                redrawTimeout = setTimeout(drawOverlay, t);
        }

        function drawOverlay() {
            redrawTimeout = null;

            // draw highlights
            octx.save();
            overlay.clear();
            octx.translate(plotOffset.left, plotOffset.top);

            var i, hi;
            for (i = 0; i < highlights.length; ++i) {
                hi = highlights[i];

                if (hi.series.bars.show)
                    drawBarHighlight(hi.series, hi.point);
                else
                    drawPointHighlight(hi.series, hi.point);
            }
            octx.restore();

            executeHooks(hooks.drawOverlay, [octx]);
        }

        function highlight(s, point, auto) {
            if (typeof s == "number")
                s = series[s];

            if (typeof point == "number") {
                var ps = s.datapoints.pointsize;
                point = s.datapoints.points.slice(ps * point, ps * (point + 1));
            }

            var i = indexOfHighlight(s, point);
            if (i == -1) {
                highlights.push({ series: s, point: point, auto: auto });

                triggerRedrawOverlay();
            }
            else if (!auto)
                highlights[i].auto = false;
        }

        function unhighlight(s, point) {
            if (s == null && point == null) {
                highlights = [];
                triggerRedrawOverlay();
                return;
            }

            if (typeof s == "number")
                s = series[s];

            if (typeof point == "number") {
                var ps = s.datapoints.pointsize;
                point = s.datapoints.points.slice(ps * point, ps * (point + 1));
            }

            var i = indexOfHighlight(s, point);
            if (i != -1) {
                highlights.splice(i, 1);

                triggerRedrawOverlay();
            }
        }

        function indexOfHighlight(s, p) {
            for (var i = 0; i < highlights.length; ++i) {
                var h = highlights[i];
                if (h.series == s && h.point[0] == p[0]
                    && h.point[1] == p[1])
                    return i;
            }
            return -1;
        }

        function drawPointHighlight(series, point) {
            var x = point[0], y = point[1],
                axisx = series.xaxis, axisy = series.yaxis,
                highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString();

            if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max)
                return;

            var pointRadius = series.points.radius + series.points.lineWidth / 2;
            octx.lineWidth = pointRadius;
            octx.strokeStyle = highlightColor;
            var radius = 1.5 * pointRadius;
            x = axisx.p2c(x);
            y = axisy.p2c(y);

            octx.beginPath();
            if (series.points.symbol == "circle")
                octx.arc(x, y, radius, 0, 2 * Math.PI, false);
            else
                series.points.symbol(octx, x, y, radius, false);
            octx.closePath();
            octx.stroke();
        }

        function drawBarHighlight(series, point) {
            var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),
                fillStyle = highlightColor,
                barLeft;

            switch (series.bars.align) {
                case "left":
                    barLeft = 0;
                    break;
                case "right":
                    barLeft = -series.bars.barWidth;
                    break;
                default:
                    barLeft = -series.bars.barWidth / 2;
            }

            octx.lineWidth = series.bars.lineWidth;
            octx.strokeStyle = highlightColor;

            drawBar(point[0], point[1], point[2] || 0, barLeft, barLeft + series.bars.barWidth,
                    function () { return fillStyle; }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
        }

        function getColorOrGradient(spec, bottom, top, defaultColor) {
            if (typeof spec == "string")
                return spec;
            else {
                // assume this is a gradient spec; IE currently only
                // supports a simple vertical gradient properly, so that's
                // what we support too
                var gradient = ctx.createLinearGradient(0, top, 0, bottom);

                for (var i = 0, l = spec.colors.length; i < l; ++i) {
                    var c = spec.colors[i];
                    if (typeof c != "string") {
                        var co = $.color.parse(defaultColor);
                        if (c.brightness != null)
                            co = co.scale('rgb', c.brightness);
                        if (c.opacity != null)
                            co.a *= c.opacity;
                        c = co.toString();
                    }
                    gradient.addColorStop(i / (l - 1), c);
                }

                return gradient;
            }
        }
    }

    // Add the plot function to the top level of the jQuery object

    $.plot = function(placeholder, data, options) {
        //var t0 = new Date();
        var plot = new Plot($(placeholder), data, options, $.plot.plugins);
        //(window.console ? console.log : alert)("time used (msecs): " + ((new Date()).getTime() - t0.getTime()));
        return plot;
    };

    $.plot.version = "0.8.3";

    $.plot.plugins = [];

    // Also add the plot function as a chainable property

    $.fn.plot = function(data, options) {
        return this.each(function() {
            $.plot(this, data, options);
        });
    };

    // round to nearby lower multiple of base
    function floorInBase(n, base) {
        return base * Math.floor(n / base);
    }

})(jQuery);
/* Flot plugin for rendering pie charts.

Copyright (c) 2007-2014 IOLA and Ole Laursen.
Licensed under the MIT license.

The plugin assumes that each series has a single data value, and that each
value is a positive integer or zero.  Negative numbers don't make sense for a
pie chart, and have unpredictable results.  The values do NOT need to be
passed in as percentages; the plugin will calculate the total and per-slice
percentages internally.

* Created by Brian Medendorp

* Updated with contributions from btburnett3, Anthony Aragues and Xavi Ivars

The plugin supports these options:

	series: {
		pie: {
			show: true/false
			radius: 0-1 for percentage of fullsize, or a specified pixel length, or 'auto'
			innerRadius: 0-1 for percentage of fullsize or a specified pixel length, for creating a donut effect
			startAngle: 0-2 factor of PI used for starting angle (in radians) i.e 3/2 starts at the top, 0 and 2 have the same result
			tilt: 0-1 for percentage to tilt the pie, where 1 is no tilt, and 0 is completely flat (nothing will show)
			offset: {
				top: integer value to move the pie up or down
				left: integer value to move the pie left or right, or 'auto'
			},
			stroke: {
				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#FFF')
				width: integer pixel width of the stroke
			},
			label: {
				show: true/false, or 'auto'
				formatter:  a user-defined function that modifies the text/style of the label text
				radius: 0-1 for percentage of fullsize, or a specified pixel length
				background: {
					color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#000')
					opacity: 0-1
				},
				threshold: 0-1 for the percentage value at which to hide labels (if they're too small)
			},
			combine: {
				threshold: 0-1 for the percentage value at which to combine slices (if they're too small)
				color: any hexidecimal color value (other formats may or may not work, so best to stick with something like '#CCC'), if null, the plugin will automatically use the color of the first slice to be combined
				label: any text value of what the combined slice should be labeled
			}
			highlight: {
				opacity: 0-1
			}
		}
	}

More detail and specific examples can be found in the included HTML file.

*/


(function($) {

	// Maximum redraw attempts when fitting labels within the plot

	var REDRAW_ATTEMPTS = 10;

	// Factor by which to shrink the pie when fitting labels within the plot

	var REDRAW_SHRINK = 0.95;

	function init(plot) {

		var canvas = null,
			target = null,
			options = null,
			maxRadius = null,
			centerLeft = null,
			centerTop = null,
			processed = false,
			ctx = null;

		// interactive variables

		var highlights = [];

		// add hook to determine if pie plugin in enabled, and then perform necessary operations

		plot.hooks.processOptions.push(function(plot, options) {
			if (options.series.pie.show) {

				options.grid.show = false;

				// set labels.show

				if (options.series.pie.label.show == "auto") {
					if (options.legend.show) {
						options.series.pie.label.show = false;
					} else {
						options.series.pie.label.show = true;
					}
				}

				// set radius

				if (options.series.pie.radius == "auto") {
					if (options.series.pie.label.show) {
						options.series.pie.radius = 3/4;
					} else {
						options.series.pie.radius = 1;
					}
				}

				// ensure sane tilt

				if (options.series.pie.tilt > 1) {
					options.series.pie.tilt = 1;
				} else if (options.series.pie.tilt < 0) {
					options.series.pie.tilt = 0;
				}
			}
		});

		plot.hooks.bindEvents.push(function(plot, eventHolder) {
			var options = plot.getOptions();
			if (options.series.pie.show) {
				if (options.grid.hoverable) {
					eventHolder.unbind("mousemove").mousemove(onMouseMove);
				}
				if (options.grid.clickable) {
					eventHolder.unbind("click").click(onClick);
				}
			}
		});

		plot.hooks.processDatapoints.push(function(plot, series, data, datapoints) {
			var options = plot.getOptions();
			if (options.series.pie.show) {
				processDatapoints(plot, series, data, datapoints);
			}
		});

		plot.hooks.drawOverlay.push(function(plot, octx) {
			var options = plot.getOptions();
			if (options.series.pie.show) {
				drawOverlay(plot, octx);
			}
		});

		plot.hooks.draw.push(function(plot, newCtx) {
			var options = plot.getOptions();
			if (options.series.pie.show) {
				draw(plot, newCtx);
			}
		});

		function processDatapoints(plot, series, datapoints) {
			if (!processed)	{
				processed = true;
				canvas = plot.getCanvas();
				target = $(canvas).parent();
				options = plot.getOptions();
				plot.setData(combine(plot.getData()));
			}
		}

		function combine(data) {

			var total = 0,
				combined = 0,
				numCombined = 0,
				color = options.series.pie.combine.color,
				newdata = [];

			// Fix up the raw data from Flot, ensuring the data is numeric

			for (var i = 0; i < data.length; ++i) {

				var value = data[i].data;

				// If the data is an array, we'll assume that it's a standard
				// Flot x-y pair, and are concerned only with the second value.

				// Note how we use the original array, rather than creating a
				// new one; this is more efficient and preserves any extra data
				// that the user may have stored in higher indexes.

				if ($.isArray(value) && value.length == 1) {
    				value = value[0];
				}

				if ($.isArray(value)) {
					// Equivalent to $.isNumeric() but compatible with jQuery < 1.7
					if (!isNaN(parseFloat(value[1])) && isFinite(value[1])) {
						value[1] = +value[1];
					} else {
						value[1] = 0;
					}
				} else if (!isNaN(parseFloat(value)) && isFinite(value)) {
					value = [1, +value];
				} else {
					value = [1, 0];
				}

				data[i].data = [value];
			}

			// Sum up all the slices, so we can calculate percentages for each

			for (var i = 0; i < data.length; ++i) {
				total += data[i].data[0][1];
			}

			// Count the number of slices with percentages below the combine
			// threshold; if it turns out to be just one, we won't combine.

			for (var i = 0; i < data.length; ++i) {
				var value = data[i].data[0][1];
				if (value / total <= options.series.pie.combine.threshold) {
					combined += value;
					numCombined++;
					if (!color) {
						color = data[i].color;
					}
				}
			}

			for (var i = 0; i < data.length; ++i) {
				var value = data[i].data[0][1];
				if (numCombined < 2 || value / total > options.series.pie.combine.threshold) {
					newdata.push(
						$.extend(data[i], {     /* extend to allow keeping all other original data values
						                           and using them e.g. in labelFormatter. */
							data: [[1, value]],
							color: data[i].color,
							label: data[i].label,
							angle: value * Math.PI * 2 / total,
							percent: value / (total / 100)
						})
					);
				}
			}

			if (numCombined > 1) {
				newdata.push({
					data: [[1, combined]],
					color: color,
					label: options.series.pie.combine.label,
					angle: combined * Math.PI * 2 / total,
					percent: combined / (total / 100)
				});
			}

			return newdata;
		}

		function draw(plot, newCtx) {

			if (!target) {
				return; // if no series were passed
			}

			var canvasWidth = plot.getPlaceholder().width(),
				canvasHeight = plot.getPlaceholder().height(),
				legendWidth = target.children().filter(".legend").children().width() || 0;

			ctx = newCtx;

			// WARNING: HACK! REWRITE THIS CODE AS SOON AS POSSIBLE!

			// When combining smaller slices into an 'other' slice, we need to
			// add a new series.  Since Flot gives plugins no way to modify the
			// list of series, the pie plugin uses a hack where the first call
			// to processDatapoints results in a call to setData with the new
			// list of series, then subsequent processDatapoints do nothing.

			// The plugin-global 'processed' flag is used to control this hack;
			// it starts out false, and is set to true after the first call to
			// processDatapoints.

			// Unfortunately this turns future setData calls into no-ops; they
			// call processDatapoints, the flag is true, and nothing happens.

			// To fix this we'll set the flag back to false here in draw, when
			// all series have been processed, so the next sequence of calls to
			// processDatapoints once again starts out with a slice-combine.
			// This is really a hack; in 0.9 we need to give plugins a proper
			// way to modify series before any processing begins.

			processed = false;

			// calculate maximum radius and center point

			maxRadius =  Math.min(canvasWidth, canvasHeight / options.series.pie.tilt) / 2;
			centerTop = canvasHeight / 2 + options.series.pie.offset.top;
			centerLeft = canvasWidth / 2;

			if (options.series.pie.offset.left == "auto") {
				if (options.legend.position.match("w")) {
					centerLeft += legendWidth / 2;
				} else {
					centerLeft -= legendWidth / 2;
				}
				if (centerLeft < maxRadius) {
					centerLeft = maxRadius;
				} else if (centerLeft > canvasWidth - maxRadius) {
					centerLeft = canvasWidth - maxRadius;
				}
			} else {
				centerLeft += options.series.pie.offset.left;
			}

			var slices = plot.getData(),
				attempts = 0;

			// Keep shrinking the pie's radius until drawPie returns true,
			// indicating that all the labels fit, or we try too many times.

			do {
				if (attempts > 0) {
					maxRadius *= REDRAW_SHRINK;
				}
				attempts += 1;
				clear();
				if (options.series.pie.tilt <= 0.8) {
					drawShadow();
				}
			} while (!drawPie() && attempts < REDRAW_ATTEMPTS)

			if (attempts >= REDRAW_ATTEMPTS) {
				clear();
				target.prepend("<div class='error'>Could not draw pie with labels contained inside canvas</div>");
			}

			if (plot.setSeries && plot.insertLegend) {
				plot.setSeries(slices);
				plot.insertLegend();
			}

			// we're actually done at this point, just defining internal functions at this point

			function clear() {
				ctx.clearRect(0, 0, canvasWidth, canvasHeight);
				target.children().filter(".pieLabel, .pieLabelBackground").remove();
			}

			function drawShadow() {

				var shadowLeft = options.series.pie.shadow.left;
				var shadowTop = options.series.pie.shadow.top;
				var edge = 10;
				var alpha = options.series.pie.shadow.alpha;
				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;

				if (radius >= canvasWidth / 2 - shadowLeft || radius * options.series.pie.tilt >= canvasHeight / 2 - shadowTop || radius <= edge) {
					return;	// shadow would be outside canvas, so don't draw it
				}

				ctx.save();
				ctx.translate(shadowLeft,shadowTop);
				ctx.globalAlpha = alpha;
				ctx.fillStyle = "#000";

				// center and rotate to starting position

				ctx.translate(centerLeft,centerTop);
				ctx.scale(1, options.series.pie.tilt);

				//radius -= edge;

				for (var i = 1; i <= edge; i++) {
					ctx.beginPath();
					ctx.arc(0, 0, radius, 0, Math.PI * 2, false);
					ctx.fill();
					radius -= i;
				}

				ctx.restore();
			}

			function drawPie() {

				var startAngle = Math.PI * options.series.pie.startAngle;
				var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;

				// center and rotate to starting position

				ctx.save();
				ctx.translate(centerLeft,centerTop);
				ctx.scale(1, options.series.pie.tilt);
				//ctx.rotate(startAngle); // start at top; -- This doesn't work properly in Opera

				// draw slices

				ctx.save();
				var currentAngle = startAngle;
				for (var i = 0; i < slices.length; ++i) {
					slices[i].startAngle = currentAngle;
					drawSlice(slices[i].angle, slices[i].color, true);
				}
				ctx.restore();

				// draw slice outlines

				if (options.series.pie.stroke.width > 0) {
					ctx.save();
					ctx.lineWidth = options.series.pie.stroke.width;
					currentAngle = startAngle;
					for (var i = 0; i < slices.length; ++i) {
						drawSlice(slices[i].angle, options.series.pie.stroke.color, false);
					}
					ctx.restore();
				}

				// draw donut hole

				drawDonutHole(ctx);

				ctx.restore();

				// Draw the labels, returning true if they fit within the plot

				if (options.series.pie.label.show) {
					return drawLabels();
				} else return true;

				function drawSlice(angle, color, fill) {

					if (angle <= 0 || isNaN(angle)) {
						return;
					}

					if (fill) {
						ctx.fillStyle = color;
					} else {
						ctx.strokeStyle = color;
						ctx.lineJoin = "round";
					}

					ctx.beginPath();
					if (Math.abs(angle - Math.PI * 2) > 0.000000001) {
						ctx.moveTo(0, 0); // Center of the pie
					}

					//ctx.arc(0, 0, radius, 0, angle, false); // This doesn't work properly in Opera
					ctx.arc(0, 0, radius,currentAngle, currentAngle + angle / 2, false);
					ctx.arc(0, 0, radius,currentAngle + angle / 2, currentAngle + angle, false);
					ctx.closePath();
					//ctx.rotate(angle); // This doesn't work properly in Opera
					currentAngle += angle;

					if (fill) {
						ctx.fill();
					} else {
						ctx.stroke();
					}
				}

				function drawLabels() {

					var currentAngle = startAngle;
					var radius = options.series.pie.label.radius > 1 ? options.series.pie.label.radius : maxRadius * options.series.pie.label.radius;

					for (var i = 0; i < slices.length; ++i) {
						if (slices[i].percent >= options.series.pie.label.threshold * 100) {
							if (!drawLabel(slices[i], currentAngle, i)) {
								return false;
							}
						}
						currentAngle += slices[i].angle;
					}

					return true;

					function drawLabel(slice, startAngle, index) {

						if (slice.data[0][1] == 0) {
							return true;
						}

						// format label text

						var lf = options.legend.labelFormatter, text, plf = options.series.pie.label.formatter;

						if (lf) {
							text = lf(slice.label, slice);
						} else {
							text = slice.label;
						}

						if (plf) {
							text = plf(text, slice);
						}

						var halfAngle = ((startAngle + slice.angle) + startAngle) / 2;
						var x = centerLeft + Math.round(Math.cos(halfAngle) * radius);
						var y = centerTop + Math.round(Math.sin(halfAngle) * radius) * options.series.pie.tilt;

						var html = "<span class='pieLabel' id='pieLabel" + index + "' style='position:absolute;top:" + y + "px;left:" + x + "px;'>" + text + "</span>";
						target.append(html);

						var label = target.children("#pieLabel" + index);
						var labelTop = (y - label.height() / 2);
						var labelLeft = (x - label.width() / 2);

						label.css("top", labelTop);
						label.css("left", labelLeft);

						// check to make sure that the label is not outside the canvas

						if (0 - labelTop > 0 || 0 - labelLeft > 0 || canvasHeight - (labelTop + label.height()) < 0 || canvasWidth - (labelLeft + label.width()) < 0) {
							return false;
						}

						if (options.series.pie.label.background.opacity != 0) {

							// put in the transparent background separately to avoid blended labels and label boxes

							var c = options.series.pie.label.background.color;

							if (c == null) {
								c = slice.color;
							}

							var pos = "top:" + labelTop + "px;left:" + labelLeft + "px;";
							$("<div class='pieLabelBackground' style='position:absolute;width:" + label.width() + "px;height:" + label.height() + "px;" + pos + "background-color:" + c + ";'></div>")
								.css("opacity", options.series.pie.label.background.opacity)
								.insertBefore(label);
						}

						return true;
					} // end individual label function
				} // end drawLabels function
			} // end drawPie function
		} // end draw function

		// Placed here because it needs to be accessed from multiple locations

		function drawDonutHole(layer) {
			if (options.series.pie.innerRadius > 0) {

				// subtract the center

				layer.save();
				var innerRadius = options.series.pie.innerRadius > 1 ? options.series.pie.innerRadius : maxRadius * options.series.pie.innerRadius;
				layer.globalCompositeOperation = "destination-out"; // this does not work with excanvas, but it will fall back to using the stroke color
				layer.beginPath();
				layer.fillStyle = options.series.pie.stroke.color;
				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
				layer.fill();
				layer.closePath();
				layer.restore();

				// add inner stroke

				layer.save();
				layer.beginPath();
				layer.strokeStyle = options.series.pie.stroke.color;
				layer.arc(0, 0, innerRadius, 0, Math.PI * 2, false);
				layer.stroke();
				layer.closePath();
				layer.restore();

				// TODO: add extra shadow inside hole (with a mask) if the pie is tilted.
			}
		}

		//-- Additional Interactive related functions --

		function isPointInPoly(poly, pt) {
			for(var c = false, i = -1, l = poly.length, j = l - 1; ++i < l; j = i)
				((poly[i][1] <= pt[1] && pt[1] < poly[j][1]) || (poly[j][1] <= pt[1] && pt[1]< poly[i][1]))
				&& (pt[0] < (poly[j][0] - poly[i][0]) * (pt[1] - poly[i][1]) / (poly[j][1] - poly[i][1]) + poly[i][0])
				&& (c = !c);
			return c;
		}

		function findNearbySlice(mouseX, mouseY) {

			var slices = plot.getData(),
				options = plot.getOptions(),
				radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius,
				x, y;

			for (var i = 0; i < slices.length; ++i) {

				var s = slices[i];

				if (s.pie.show) {

					ctx.save();
					ctx.beginPath();
					ctx.moveTo(0, 0); // Center of the pie
					//ctx.scale(1, options.series.pie.tilt);	// this actually seems to break everything when here.
					ctx.arc(0, 0, radius, s.startAngle, s.startAngle + s.angle / 2, false);
					ctx.arc(0, 0, radius, s.startAngle + s.angle / 2, s.startAngle + s.angle, false);
					ctx.closePath();
					x = mouseX - centerLeft;
					y = mouseY - centerTop;

					if (ctx.isPointInPath) {
						if (ctx.isPointInPath(mouseX - centerLeft, mouseY - centerTop)) {
							ctx.restore();
							return {
								datapoint: [s.percent, s.data],
								dataIndex: 0,
								series: s,
								seriesIndex: i
							};
						}
					} else {

						// excanvas for IE doesn;t support isPointInPath, this is a workaround.

						var p1X = radius * Math.cos(s.startAngle),
							p1Y = radius * Math.sin(s.startAngle),
							p2X = radius * Math.cos(s.startAngle + s.angle / 4),
							p2Y = radius * Math.sin(s.startAngle + s.angle / 4),
							p3X = radius * Math.cos(s.startAngle + s.angle / 2),
							p3Y = radius * Math.sin(s.startAngle + s.angle / 2),
							p4X = radius * Math.cos(s.startAngle + s.angle / 1.5),
							p4Y = radius * Math.sin(s.startAngle + s.angle / 1.5),
							p5X = radius * Math.cos(s.startAngle + s.angle),
							p5Y = radius * Math.sin(s.startAngle + s.angle),
							arrPoly = [[0, 0], [p1X, p1Y], [p2X, p2Y], [p3X, p3Y], [p4X, p4Y], [p5X, p5Y]],
							arrPoint = [x, y];

						// TODO: perhaps do some mathmatical trickery here with the Y-coordinate to compensate for pie tilt?

						if (isPointInPoly(arrPoly, arrPoint)) {
							ctx.restore();
							return {
								datapoint: [s.percent, s.data],
								dataIndex: 0,
								series: s,
								seriesIndex: i
							};
						}
					}

					ctx.restore();
				}
			}

			return null;
		}

		function onMouseMove(e) {
			triggerClickHoverEvent("plothover", e);
		}

		function onClick(e) {
			triggerClickHoverEvent("plotclick", e);
		}

		// trigger click or hover event (they send the same parameters so we share their code)

		function triggerClickHoverEvent(eventname, e) {

			var offset = plot.offset();
			var canvasX = parseInt(e.pageX - offset.left);
			var canvasY =  parseInt(e.pageY - offset.top);
			var item = findNearbySlice(canvasX, canvasY);

			if (options.grid.autoHighlight) {

				// clear auto-highlights

				for (var i = 0; i < highlights.length; ++i) {
					var h = highlights[i];
					if (h.auto == eventname && !(item && h.series == item.series)) {
						unhighlight(h.series);
					}
				}
			}

			// highlight the slice

			if (item) {
				highlight(item.series, eventname);
			}

			// trigger any hover bind events

			var pos = { pageX: e.pageX, pageY: e.pageY };
			target.trigger(eventname, [pos, item]);
		}

		function highlight(s, auto) {
			//if (typeof s == "number") {
			//	s = series[s];
			//}

			var i = indexOfHighlight(s);

			if (i == -1) {
				highlights.push({ series: s, auto: auto });
				plot.triggerRedrawOverlay();
			} else if (!auto) {
				highlights[i].auto = false;
			}
		}

		function unhighlight(s) {
			if (s == null) {
				highlights = [];
				plot.triggerRedrawOverlay();
			}

			//if (typeof s == "number") {
			//	s = series[s];
			//}

			var i = indexOfHighlight(s);

			if (i != -1) {
				highlights.splice(i, 1);
				plot.triggerRedrawOverlay();
			}
		}

		function indexOfHighlight(s) {
			for (var i = 0; i < highlights.length; ++i) {
				var h = highlights[i];
				if (h.series == s)
					return i;
			}
			return -1;
		}

		function drawOverlay(plot, octx) {

			var options = plot.getOptions();

			var radius = options.series.pie.radius > 1 ? options.series.pie.radius : maxRadius * options.series.pie.radius;

			octx.save();
			octx.translate(centerLeft, centerTop);
			octx.scale(1, options.series.pie.tilt);

			for (var i = 0; i < highlights.length; ++i) {
				drawHighlight(highlights[i].series);
			}

			drawDonutHole(octx);

			octx.restore();

			function drawHighlight(series) {

				if (series.angle <= 0 || isNaN(series.angle)) {
					return;
				}

				//octx.fillStyle = parseColor(options.series.pie.highlight.color).scale(null, null, null, options.series.pie.highlight.opacity).toString();
				octx.fillStyle = "rgba(255, 255, 255, " + options.series.pie.highlight.opacity + ")"; // this is temporary until we have access to parseColor
				octx.beginPath();
				if (Math.abs(series.angle - Math.PI * 2) > 0.000000001) {
					octx.moveTo(0, 0); // Center of the pie
				}
				octx.arc(0, 0, radius, series.startAngle, series.startAngle + series.angle / 2, false);
				octx.arc(0, 0, radius, series.startAngle + series.angle / 2, series.startAngle + series.angle, false);
				octx.closePath();
				octx.fill();
			}
		}
	} // end init (plugin body)

	// define pie specific options and their default values

	var options = {
		series: {
			pie: {
				show: false,
				radius: "auto",	// actual radius of the visible pie (based on full calculated radius if <=1, or hard pixel value)
				innerRadius: 0, /* for donut */
				startAngle: 3/2,
				tilt: 1,
				shadow: {
					left: 5,	// shadow left offset
					top: 15,	// shadow top offset
					alpha: 0.02	// shadow alpha
				},
				offset: {
					top: 0,
					left: "auto"
				},
				stroke: {
					color: "#fff",
					width: 1
				},
				label: {
					show: "auto",
					formatter: function(label, slice) {
						return "<div style='font-size:x-small;text-align:center;padding:2px;color:" + slice.color + ";'>" + label + "<br/>" + Math.round(slice.percent) + "%</div>";
					},	// formatter function
					radius: 1,	// radius at which to place the labels (based on full calculated radius if <=1, or hard pixel value)
					background: {
						color: null,
						opacity: 0
					},
					threshold: 0	// percentage at which to hide the label (i.e. the slice is too narrow)
				},
				combine: {
					threshold: -1,	// percentage at which to combine little slices into one larger slice
					color: null,	// color to give the new slice (auto-generated if null)
					label: "Other"	// label to give the new slice
				},
				highlight: {
					//color: "#fff",		// will add this functionality once parseColor is available
					opacity: 0.5
				}
			}
		}
	};

	$.plot.plugins.push({
		init: init,
		options: options,
		name: "pie",
		version: "1.1"
	});

})(jQuery);
/* Flot plugin for automatically redrawing plots as the placeholder resizes.

Copyright (c) 2007-2014 IOLA and Ole Laursen.
Licensed under the MIT license.

It works by listening for changes on the placeholder div (through the jQuery
resize event plugin) - if the size changes, it will redraw the plot.

There are no options. If you need to disable the plugin for some plots, you
can just fix the size of their placeholders.

*/

/* Inline dependency:
 * jQuery resize event - v1.1 - 3/14/2010
 * http://benalman.com/projects/jquery-resize-plugin/
 *
 * Copyright (c) 2010 "Cowboy" Ben Alman
 * Dual licensed under the MIT and GPL licenses.
 * http://benalman.com/about/license/
 */

(function($,e,t){"$:nomunge";var i=[],n=$.resize=$.extend($.resize,{}),a,r=false,s="setTimeout",u="resize",m=u+"-special-event",o="pendingDelay",l="activeDelay",f="throttleWindow";n[o]=200;n[l]=20;n[f]=true;$.event.special[u]={setup:function(){if(!n[f]&&this[s]){return false}var e=$(this);i.push(this);e.data(m,{w:e.width(),h:e.height()});if(i.length===1){a=t;h()}},teardown:function(){if(!n[f]&&this[s]){return false}var e=$(this);for(var t=i.length-1;t>=0;t--){if(i[t]==this){i.splice(t,1);break}}e.removeData(m);if(!i.length){if(r){cancelAnimationFrame(a)}else{clearTimeout(a)}a=null}},add:function(e){if(!n[f]&&this[s]){return false}var i;function a(e,n,a){var r=$(this),s=r.data(m)||{};s.w=n!==t?n:r.width();s.h=a!==t?a:r.height();i.apply(this,arguments)}if($.isFunction(e)){i=e;return a}else{i=e.handler;e.handler=a}}};function h(t){if(r===true){r=t||1}for(var s=i.length-1;s>=0;s--){var l=$(i[s]);if(l[0]==e||l.is(":visible")){var f=l.width(),c=l.height(),d=l.data(m);if(d&&(f!==d.w||c!==d.h)){l.trigger(u,[d.w=f,d.h=c]);r=t||true}}else{d=l.data(m);d.w=0;d.h=0}}if(a!==null){if(r&&(t==null||t-r<1e3)){a=e.requestAnimationFrame(h)}else{a=setTimeout(h,n[o]);r=false}}}if(!e.requestAnimationFrame){e.requestAnimationFrame=function(){return e.webkitRequestAnimationFrame||e.mozRequestAnimationFrame||e.oRequestAnimationFrame||e.msRequestAnimationFrame||function(t,i){return e.setTimeout(function(){t((new Date).getTime())},n[l])}}()}if(!e.cancelAnimationFrame){e.cancelAnimationFrame=function(){return e.webkitCancelRequestAnimationFrame||e.mozCancelRequestAnimationFrame||e.oCancelRequestAnimationFrame||e.msCancelRequestAnimationFrame||clearTimeout}()}})(jQuery,this);

(function ($) {
    var options = { }; // no options

    function init(plot) {
        function onResize() {
            var placeholder = plot.getPlaceholder();

            // somebody might have hidden us and we can't plot
            // when we don't have the dimensions
            if (placeholder.width() == 0 || placeholder.height() == 0)
                return;

            plot.resize();
            plot.setupGrid();
            plot.draw();
        }
        
        function bindEvents(plot, eventHolder) {
            plot.getPlaceholder().resize(onResize);
        }

        function shutdown(plot, eventHolder) {
            plot.getPlaceholder().unbind("resize", onResize);
        }
        
        plot.hooks.bindEvents.push(bindEvents);
        plot.hooks.shutdown.push(shutdown);
    }
    
    $.plot.plugins.push({
        init: init,
        options: options,
        name: 'resize',
        version: '1.0'
    });
})(jQuery);
/* Pretty handling of time axes.

Copyright (c) 2007-2014 IOLA and Ole Laursen.
Licensed under the MIT license.

Set axis.mode to "time" to enable. See the section "Time series data" in
API.txt for details.

*/


(function($) {

	var options = {
		xaxis: {
			timezone: null,		// "browser" for local to the client or timezone for timezone-js
			timeformat: null,	// format string to use
			twelveHourClock: false,	// 12 or 24 time in time mode
			monthNames: null	// list of names of months
		}
	};

	// round to nearby lower multiple of base

	function floorInBase(n, base) {
		return base * Math.floor(n / base);
	}

	// Returns a string with the date d formatted according to fmt.
	// A subset of the Open Group's strftime format is supported.

	function formatDate(d, fmt, monthNames, dayNames) {

		if (typeof d.strftime == "function") {
			return d.strftime(fmt);
		}

		var leftPad = function(n, pad) {
			n = "" + n;
			pad = "" + (pad == null ? "0" : pad);
			return n.length == 1 ? pad + n : n;
		};

		var r = [];
		var escape = false;
		var hours = d.getHours();
		var isAM = hours < 12;

		if (monthNames == null) {
			monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
		}

		if (dayNames == null) {
			dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
		}

		var hours12;

		if (hours > 12) {
			hours12 = hours - 12;
		} else if (hours == 0) {
			hours12 = 12;
		} else {
			hours12 = hours;
		}

		for (var i = 0; i < fmt.length; ++i) {

			var c = fmt.charAt(i);

			if (escape) {
				switch (c) {
					case 'a': c = "" + dayNames[d.getDay()]; break;
					case 'b': c = "" + monthNames[d.getMonth()]; break;
					case 'd': c = leftPad(d.getDate()); break;
					case 'e': c = leftPad(d.getDate(), " "); break;
					case 'h':	// For back-compat with 0.7; remove in 1.0
					case 'H': c = leftPad(hours); break;
					case 'I': c = leftPad(hours12); break;
					case 'l': c = leftPad(hours12, " "); break;
					case 'm': c = leftPad(d.getMonth() + 1); break;
					case 'M': c = leftPad(d.getMinutes()); break;
					// quarters not in Open Group's strftime specification
					case 'q':
						c = "" + (Math.floor(d.getMonth() / 3) + 1); break;
					case 'S': c = leftPad(d.getSeconds()); break;
					case 'y': c = leftPad(d.getFullYear() % 100); break;
					case 'Y': c = "" + d.getFullYear(); break;
					case 'p': c = (isAM) ? ("" + "am") : ("" + "pm"); break;
					case 'P': c = (isAM) ? ("" + "AM") : ("" + "PM"); break;
					case 'w': c = "" + d.getDay(); break;
				}
				r.push(c);
				escape = false;
			} else {
				if (c == "%") {
					escape = true;
				} else {
					r.push(c);
				}
			}
		}

		return r.join("");
	}

	// To have a consistent view of time-based data independent of which time
	// zone the client happens to be in we need a date-like object independent
	// of time zones.  This is done through a wrapper that only calls the UTC
	// versions of the accessor methods.

	function makeUtcWrapper(d) {

		function addProxyMethod(sourceObj, sourceMethod, targetObj, targetMethod) {
			sourceObj[sourceMethod] = function() {
				return targetObj[targetMethod].apply(targetObj, arguments);
			};
		};

		var utc = {
			date: d
		};

		// support strftime, if found

		if (d.strftime != undefined) {
			addProxyMethod(utc, "strftime", d, "strftime");
		}

		addProxyMethod(utc, "getTime", d, "getTime");
		addProxyMethod(utc, "setTime", d, "setTime");

		var props = ["Date", "Day", "FullYear", "Hours", "Milliseconds", "Minutes", "Month", "Seconds"];

		for (var p = 0; p < props.length; p++) {
			addProxyMethod(utc, "get" + props[p], d, "getUTC" + props[p]);
			addProxyMethod(utc, "set" + props[p], d, "setUTC" + props[p]);
		}

		return utc;
	};

	// select time zone strategy.  This returns a date-like object tied to the
	// desired timezone

	function dateGenerator(ts, opts) {
		if (opts.timezone == "browser") {
			return new Date(ts);
		} else if (!opts.timezone || opts.timezone == "utc") {
			return makeUtcWrapper(new Date(ts));
		} else if (typeof timezoneJS != "undefined" && typeof timezoneJS.Date != "undefined") {
			var d = new timezoneJS.Date();
			// timezone-js is fickle, so be sure to set the time zone before
			// setting the time.
			d.setTimezone(opts.timezone);
			d.setTime(ts);
			return d;
		} else {
			return makeUtcWrapper(new Date(ts));
		}
	}
	
	// map of app. size of time units in milliseconds

	var timeUnitSize = {
		"second": 1000,
		"minute": 60 * 1000,
		"hour": 60 * 60 * 1000,
		"day": 24 * 60 * 60 * 1000,
		"month": 30 * 24 * 60 * 60 * 1000,
		"quarter": 3 * 30 * 24 * 60 * 60 * 1000,
		"year": 365.2425 * 24 * 60 * 60 * 1000
	};

	// the allowed tick sizes, after 1 year we use
	// an integer algorithm

	var baseSpec = [
		[1, "second"], [2, "second"], [5, "second"], [10, "second"],
		[30, "second"], 
		[1, "minute"], [2, "minute"], [5, "minute"], [10, "minute"],
		[30, "minute"], 
		[1, "hour"], [2, "hour"], [4, "hour"],
		[8, "hour"], [12, "hour"],
		[1, "day"], [2, "day"], [3, "day"],
		[0.25, "month"], [0.5, "month"], [1, "month"],
		[2, "month"]
	];

	// we don't know which variant(s) we'll need yet, but generating both is
	// cheap

	var specMonths = baseSpec.concat([[3, "month"], [6, "month"],
		[1, "year"]]);
	var specQuarters = baseSpec.concat([[1, "quarter"], [2, "quarter"],
		[1, "year"]]);

	function init(plot) {
		plot.hooks.processOptions.push(function (plot, options) {
			$.each(plot.getAxes(), function(axisName, axis) {

				var opts = axis.options;

				if (opts.mode == "time") {
					axis.tickGenerator = function(axis) {

						var ticks = [];
						var d = dateGenerator(axis.min, opts);
						var minSize = 0;

						// make quarter use a possibility if quarters are
						// mentioned in either of these options

						var spec = (opts.tickSize && opts.tickSize[1] ===
							"quarter") ||
							(opts.minTickSize && opts.minTickSize[1] ===
							"quarter") ? specQuarters : specMonths;

						if (opts.minTickSize != null) {
							if (typeof opts.tickSize == "number") {
								minSize = opts.tickSize;
							} else {
								minSize = opts.minTickSize[0] * timeUnitSize[opts.minTickSize[1]];
							}
						}

						for (var i = 0; i < spec.length - 1; ++i) {
							if (axis.delta < (spec[i][0] * timeUnitSize[spec[i][1]]
											  + spec[i + 1][0] * timeUnitSize[spec[i + 1][1]]) / 2
								&& spec[i][0] * timeUnitSize[spec[i][1]] >= minSize) {
								break;
							}
						}

						var size = spec[i][0];
						var unit = spec[i][1];

						// special-case the possibility of several years

						if (unit == "year") {

							// if given a minTickSize in years, just use it,
							// ensuring that it's an integer

							if (opts.minTickSize != null && opts.minTickSize[1] == "year") {
								size = Math.floor(opts.minTickSize[0]);
							} else {

								var magn = Math.pow(10, Math.floor(Math.log(axis.delta / timeUnitSize.year) / Math.LN10));
								var norm = (axis.delta / timeUnitSize.year) / magn;

								if (norm < 1.5) {
									size = 1;
								} else if (norm < 3) {
									size = 2;
								} else if (norm < 7.5) {
									size = 5;
								} else {
									size = 10;
								}

								size *= magn;
							}

							// minimum size for years is 1

							if (size < 1) {
								size = 1;
							}
						}

						axis.tickSize = opts.tickSize || [size, unit];
						var tickSize = axis.tickSize[0];
						unit = axis.tickSize[1];

						var step = tickSize * timeUnitSize[unit];

						if (unit == "second") {
							d.setSeconds(floorInBase(d.getSeconds(), tickSize));
						} else if (unit == "minute") {
							d.setMinutes(floorInBase(d.getMinutes(), tickSize));
						} else if (unit == "hour") {
							d.setHours(floorInBase(d.getHours(), tickSize));
						} else if (unit == "month") {
							d.setMonth(floorInBase(d.getMonth(), tickSize));
						} else if (unit == "quarter") {
							d.setMonth(3 * floorInBase(d.getMonth() / 3,
								tickSize));
						} else if (unit == "year") {
							d.setFullYear(floorInBase(d.getFullYear(), tickSize));
						}

						// reset smaller components

						d.setMilliseconds(0);

						if (step >= timeUnitSize.minute) {
							d.setSeconds(0);
						}
						if (step >= timeUnitSize.hour) {
							d.setMinutes(0);
						}
						if (step >= timeUnitSize.day) {
							d.setHours(0);
						}
						if (step >= timeUnitSize.day * 4) {
							d.setDate(1);
						}
						if (step >= timeUnitSize.month * 2) {
							d.setMonth(floorInBase(d.getMonth(), 3));
						}
						if (step >= timeUnitSize.quarter * 2) {
							d.setMonth(floorInBase(d.getMonth(), 6));
						}
						if (step >= timeUnitSize.year) {
							d.setMonth(0);
						}

						var carry = 0;
						var v = Number.NaN;
						var prev;

						do {

							prev = v;
							v = d.getTime();
							ticks.push(v);

							if (unit == "month" || unit == "quarter") {
								if (tickSize < 1) {

									// a bit complicated - we'll divide the
									// month/quarter up but we need to take
									// care of fractions so we don't end up in
									// the middle of a day

									d.setDate(1);
									var start = d.getTime();
									d.setMonth(d.getMonth() +
										(unit == "quarter" ? 3 : 1));
									var end = d.getTime();
									d.setTime(v + carry * timeUnitSize.hour + (end - start) * tickSize);
									carry = d.getHours();
									d.setHours(0);
								} else {
									d.setMonth(d.getMonth() +
										tickSize * (unit == "quarter" ? 3 : 1));
								}
							} else if (unit == "year") {
								d.setFullYear(d.getFullYear() + tickSize);
							} else {
								d.setTime(v + step);
							}
						} while (v < axis.max && v != prev);

						return ticks;
					};

					axis.tickFormatter = function (v, axis) {

						var d = dateGenerator(v, axis.options);

						// first check global format

						if (opts.timeformat != null) {
							return formatDate(d, opts.timeformat, opts.monthNames, opts.dayNames);
						}

						// possibly use quarters if quarters are mentioned in
						// any of these places

						var useQuarters = (axis.options.tickSize &&
								axis.options.tickSize[1] == "quarter") ||
							(axis.options.minTickSize &&
								axis.options.minTickSize[1] == "quarter");

						var t = axis.tickSize[0] * timeUnitSize[axis.tickSize[1]];
						var span = axis.max - axis.min;
						var suffix = (opts.twelveHourClock) ? " %p" : "";
						var hourCode = (opts.twelveHourClock) ? "%I" : "%H";
						var fmt;

						if (t < timeUnitSize.minute) {
							fmt = hourCode + ":%M:%S" + suffix;
						} else if (t < timeUnitSize.day) {
							if (span < 2 * timeUnitSize.day) {
								fmt = hourCode + ":%M" + suffix;
							} else {
								fmt = "%b %d " + hourCode + ":%M" + suffix;
							}
						} else if (t < timeUnitSize.month) {
							fmt = "%b %d";
						} else if ((useQuarters && t < timeUnitSize.quarter) ||
							(!useQuarters && t < timeUnitSize.year)) {
							if (span < timeUnitSize.year) {
								fmt = "%b";
							} else {
								fmt = "%b %Y";
							}
						} else if (useQuarters && t < timeUnitSize.year) {
							if (span < timeUnitSize.year) {
								fmt = "Q%q";
							} else {
								fmt = "Q%q %Y";
							}
						} else {
							fmt = "%Y";
						}

						var rt = formatDate(d, fmt, opts.monthNames, opts.dayNames);

						return rt;
					};
				}
			});
		});
	}

	$.plot.plugins.push({
		init: init,
		options: options,
		name: 'time',
		version: '1.0'
	});

	// Time-axis support used to be in Flot core, which exposed the
	// formatDate function on the plot object.  Various plugins depend
	// on the function, so we need to re-expose it here.

	$.plot.formatDate = formatDate;
	$.plot.dateGenerator = dateGenerator;

})(jQuery);
/*
 * jquery.flot.tooltip
 * 
 * description: easy-to-use tooltips for Flot charts
 * version: 0.8.5
 * authors: Krzysztof Urbas @krzysu [myviews.pl],Evan Steinkerchner @Roundaround
 * website: https://github.com/krzysu/flot.tooltip
 * 
 * build on 2015-05-11
 * released under MIT License, 2012
*/
 
!function(a){var b={tooltip:{show:!1,cssClass:"flotTip",content:"%s | X: %x | Y: %y",xDateFormat:null,yDateFormat:null,monthNames:null,dayNames:null,shifts:{x:10,y:20},defaultTheme:!0,lines:!1,onHover:function(a,b){},$compat:!1}};b.tooltipOpts=b.tooltip;var c=function(a){this.tipPosition={x:0,y:0},this.init(a)};c.prototype.init=function(b){function c(a){var c={};c.x=a.pageX,c.y=a.pageY,b.setTooltipPosition(c)}function d(c,d,f){var g=function(a,b,c,d){return Math.sqrt((c-a)*(c-a)+(d-b)*(d-b))},h=function(a,b,c,d,e,f,h){if(!h||(h=function(a,b,c,d,e,f){if("undefined"!=typeof c)return{x:c,y:b};if("undefined"!=typeof d)return{x:a,y:d};var g,h=-1/((f-d)/(e-c));return{x:g=(e*(a*h-b+d)+c*(a*-h+b-f))/(h*(e-c)+d-f),y:h*g-h*a+b}}(a,b,c,d,e,f),h.x>=Math.min(c,e)&&h.x<=Math.max(c,e)&&h.y>=Math.min(d,f)&&h.y<=Math.max(d,f))){var i=d-f,j=e-c,k=c*f-d*e;return Math.abs(i*a+j*b+k)/Math.sqrt(i*i+j*j)}var l=g(a,b,c,d),m=g(a,b,e,f);return l>m?m:l};if(f)b.showTooltip(f,d);else if(e.plotOptions.series.lines.show&&e.tooltipOptions.lines===!0){var i=e.plotOptions.grid.mouseActiveRadius,j={distance:i+1};a.each(b.getData(),function(a,c){for(var e=0,f=-1,i=1;i<c.data.length;i++)c.data[i-1][0]<=d.x&&c.data[i][0]>=d.x&&(e=i-1,f=i);if(-1===f)return void b.hideTooltip();var k={x:c.data[e][0],y:c.data[e][1]},l={x:c.data[f][0],y:c.data[f][1]},m=h(c.xaxis.p2c(d.x),c.yaxis.p2c(d.y),c.xaxis.p2c(k.x),c.yaxis.p2c(k.y),c.xaxis.p2c(l.x),c.yaxis.p2c(l.y),!1);if(m<j.distance){var n=g(k.x,k.y,d.x,d.y)<g(d.x,d.y,l.x,l.y)?e:f,o=(c.datapoints.pointsize,[d.x,k.y+(l.y-k.y)*((d.x-k.x)/(l.x-k.x))]),p={datapoint:o,dataIndex:n,series:c,seriesIndex:a};j={distance:m,item:p}}}),j.distance<i+1?b.showTooltip(j.item,d):b.hideTooltip()}else b.hideTooltip()}var e=this,f=a.plot.plugins.length;if(this.plotPlugins=[],f)for(var g=0;f>g;g++)this.plotPlugins.push(a.plot.plugins[g].name);b.hooks.bindEvents.push(function(b,f){if(e.plotOptions=b.getOptions(),"boolean"==typeof e.plotOptions.tooltip&&(e.plotOptions.tooltipOpts.show=e.plotOptions.tooltip,e.plotOptions.tooltip=e.plotOptions.tooltipOpts,delete e.plotOptions.tooltipOpts),e.plotOptions.tooltip.show!==!1&&"undefined"!=typeof e.plotOptions.tooltip.show){e.tooltipOptions=e.plotOptions.tooltip,e.tooltipOptions.$compat?(e.wfunc="width",e.hfunc="height"):(e.wfunc="innerWidth",e.hfunc="innerHeight");e.getDomElement();a(b.getPlaceholder()).bind("plothover",d),a(f).bind("mousemove",c)}}),b.hooks.shutdown.push(function(b,e){a(b.getPlaceholder()).unbind("plothover",d),a(e).unbind("mousemove",c)}),b.setTooltipPosition=function(b){var c=e.getDomElement(),d=c.outerWidth()+e.tooltipOptions.shifts.x,f=c.outerHeight()+e.tooltipOptions.shifts.y;b.x-a(window).scrollLeft()>a(window)[e.wfunc]()-d&&(b.x-=d),b.y-a(window).scrollTop()>a(window)[e.hfunc]()-f&&(b.y-=f),e.tipPosition.x=b.x,e.tipPosition.y=b.y},b.showTooltip=function(a,c){var d=e.getDomElement(),f=e.stringFormat(e.tooltipOptions.content,a);""!==f&&(d.html(f),b.setTooltipPosition({x:c.pageX,y:c.pageY}),d.css({left:e.tipPosition.x+e.tooltipOptions.shifts.x,top:e.tipPosition.y+e.tooltipOptions.shifts.y}).show(),"function"==typeof e.tooltipOptions.onHover&&e.tooltipOptions.onHover(a,d))},b.hideTooltip=function(){e.getDomElement().hide().html("")}},c.prototype.getDomElement=function(){var b=a("."+this.tooltipOptions.cssClass);return 0===b.length&&(b=a("<div />").addClass(this.tooltipOptions.cssClass),b.appendTo("body").hide().css({position:"absolute"}),this.tooltipOptions.defaultTheme&&b.css({background:"#fff","z-index":"1040",padding:"0.4em 0.6em","border-radius":"0.5em","font-size":"0.8em",border:"1px solid #111",display:"none","white-space":"nowrap"})),b},c.prototype.stringFormat=function(a,b){var c,d,e,f,g=/%p\.{0,1}(\d{0,})/,h=/%s/,i=/%c/,j=/%lx/,k=/%ly/,l=/%x\.{0,1}(\d{0,})/,m=/%y\.{0,1}(\d{0,})/,n="%x",o="%y",p="%ct";if("undefined"!=typeof b.series.threshold?(c=b.datapoint[0],d=b.datapoint[1],e=b.datapoint[2]):"undefined"!=typeof b.series.lines&&b.series.lines.steps?(c=b.series.datapoints.points[2*b.dataIndex],d=b.series.datapoints.points[2*b.dataIndex+1],e=""):(c=b.series.data[b.dataIndex][0],d=b.series.data[b.dataIndex][1],e=b.series.data[b.dataIndex][2]),null===b.series.label&&b.series.originSeries&&(b.series.label=b.series.originSeries.label),"function"==typeof a&&(a=a(b.series.label,c,d,b)),"boolean"==typeof a&&!a)return"";if("undefined"!=typeof b.series.percent?f=b.series.percent:"undefined"!=typeof b.series.percents&&(f=b.series.percents[b.dataIndex]),"number"==typeof f&&(a=this.adjustValPrecision(g,a,f)),a="undefined"!=typeof b.series.label?a.replace(h,b.series.label):a.replace(h,""),a="undefined"!=typeof b.series.color?a.replace(i,b.series.color):a.replace(i,""),a=this.hasAxisLabel("xaxis",b)?a.replace(j,b.series.xaxis.options.axisLabel):a.replace(j,""),a=this.hasAxisLabel("yaxis",b)?a.replace(k,b.series.yaxis.options.axisLabel):a.replace(k,""),this.isTimeMode("xaxis",b)&&this.isXDateFormat(b)&&(a=a.replace(l,this.timestampToDate(c,this.tooltipOptions.xDateFormat,b.series.xaxis.options))),this.isTimeMode("yaxis",b)&&this.isYDateFormat(b)&&(a=a.replace(m,this.timestampToDate(d,this.tooltipOptions.yDateFormat,b.series.yaxis.options))),"number"==typeof c&&(a=this.adjustValPrecision(l,a,c)),"number"==typeof d&&(a=this.adjustValPrecision(m,a,d)),"undefined"!=typeof b.series.xaxis.ticks){var q;q=this.hasRotatedXAxisTicks(b)?"rotatedTicks":"ticks";var r=b.dataIndex+b.seriesIndex;for(var s in b.series.xaxis[q])if(b.series.xaxis[q].hasOwnProperty(r)&&!this.isTimeMode("xaxis",b)){var t=this.isCategoriesMode("xaxis",b)?b.series.xaxis[q][r].label:b.series.xaxis[q][r].v;t===c&&(a=a.replace(l,b.series.xaxis[q][r].label))}}if("undefined"!=typeof b.series.yaxis.ticks)for(var s in b.series.yaxis.ticks)if(b.series.yaxis.ticks.hasOwnProperty(s)){var u=this.isCategoriesMode("yaxis",b)?b.series.yaxis.ticks[s].label:b.series.yaxis.ticks[s].v;u===d&&(a=a.replace(m,b.series.yaxis.ticks[s].label))}return"undefined"!=typeof b.series.xaxis.tickFormatter&&(a=a.replace(n,b.series.xaxis.tickFormatter(c,b.series.xaxis).replace(/\$/g,"$$"))),"undefined"!=typeof b.series.yaxis.tickFormatter&&(a=a.replace(o,b.series.yaxis.tickFormatter(d,b.series.yaxis).replace(/\$/g,"$$"))),e&&(a=a.replace(p,e)),a},c.prototype.isTimeMode=function(a,b){return"undefined"!=typeof b.series[a].options.mode&&"time"===b.series[a].options.mode},c.prototype.isXDateFormat=function(a){return"undefined"!=typeof this.tooltipOptions.xDateFormat&&null!==this.tooltipOptions.xDateFormat},c.prototype.isYDateFormat=function(a){return"undefined"!=typeof this.tooltipOptions.yDateFormat&&null!==this.tooltipOptions.yDateFormat},c.prototype.isCategoriesMode=function(a,b){return"undefined"!=typeof b.series[a].options.mode&&"categories"===b.series[a].options.mode},c.prototype.timestampToDate=function(b,c,d){var e=a.plot.dateGenerator(b,d);return a.plot.formatDate(e,c,this.tooltipOptions.monthNames,this.tooltipOptions.dayNames)},c.prototype.adjustValPrecision=function(a,b,c){var d,e=b.match(a);return null!==e&&""!==RegExp.$1&&(d=RegExp.$1,c=c.toFixed(d),b=b.replace(a,c)),b},c.prototype.hasAxisLabel=function(b,c){return-1!==a.inArray(this.plotPlugins,"axisLabels")&&"undefined"!=typeof c.series[b].options.axisLabel&&c.series[b].options.axisLabel.length>0},c.prototype.hasRotatedXAxisTicks=function(b){return-1!==a.inArray(this.plotPlugins,"tickRotor")&&"undefined"!=typeof b.series.xaxis.rotatedTicks};var d=function(a){new c(a)};a.plot.plugins.push({init:d,options:b,name:"tooltip",version:"0.8.5"})}(jQuery);
/*! DataTables 1.10.7
 * ©2008-2015 SpryMedia Ltd - datatables.net/license
 */

(function(Ea,Q,k){var P=function(h){function W(a){var b,c,e={};h.each(a,function(d){if((b=d.match(/^([^A-Z]+?)([A-Z])/))&&-1!=="a aa ai ao as b fn i m o s ".indexOf(b[1]+" "))c=d.replace(b[0],b[2].toLowerCase()),e[c]=d,"o"===b[1]&&W(a[d])});a._hungarianMap=e}function H(a,b,c){a._hungarianMap||W(a);var e;h.each(b,function(d){e=a._hungarianMap[d];if(e!==k&&(c||b[e]===k))"o"===e.charAt(0)?(b[e]||(b[e]={}),h.extend(!0,b[e],b[d]),H(a[e],b[e],c)):b[e]=b[d]})}function P(a){var b=m.defaults.oLanguage,c=a.sZeroRecords;
!a.sEmptyTable&&(c&&"No data available in table"===b.sEmptyTable)&&E(a,a,"sZeroRecords","sEmptyTable");!a.sLoadingRecords&&(c&&"Loading..."===b.sLoadingRecords)&&E(a,a,"sZeroRecords","sLoadingRecords");a.sInfoThousands&&(a.sThousands=a.sInfoThousands);(a=a.sDecimal)&&db(a)}function eb(a){A(a,"ordering","bSort");A(a,"orderMulti","bSortMulti");A(a,"orderClasses","bSortClasses");A(a,"orderCellsTop","bSortCellsTop");A(a,"order","aaSorting");A(a,"orderFixed","aaSortingFixed");A(a,"paging","bPaginate");
A(a,"pagingType","sPaginationType");A(a,"pageLength","iDisplayLength");A(a,"searching","bFilter");if(a=a.aoSearchCols)for(var b=0,c=a.length;b<c;b++)a[b]&&H(m.models.oSearch,a[b])}function fb(a){A(a,"orderable","bSortable");A(a,"orderData","aDataSort");A(a,"orderSequence","asSorting");A(a,"orderDataType","sortDataType");var b=a.aDataSort;b&&!h.isArray(b)&&(a.aDataSort=[b])}function gb(a){var a=a.oBrowser,b=h("<div/>").css({position:"absolute",top:0,left:0,height:1,width:1,overflow:"hidden"}).append(h("<div/>").css({position:"absolute",
top:1,left:1,width:100,overflow:"scroll"}).append(h('<div class="test"/>').css({width:"100%",height:10}))).appendTo("body"),c=b.find(".test");a.bScrollOversize=100===c[0].offsetWidth;a.bScrollbarLeft=1!==Math.round(c.offset().left);b.remove()}function hb(a,b,c,e,d,f){var g,j=!1;c!==k&&(g=c,j=!0);for(;e!==d;)a.hasOwnProperty(e)&&(g=j?b(g,a[e],e,a):a[e],j=!0,e+=f);return g}function Fa(a,b){var c=m.defaults.column,e=a.aoColumns.length,c=h.extend({},m.models.oColumn,c,{nTh:b?b:Q.createElement("th"),sTitle:c.sTitle?
c.sTitle:b?b.innerHTML:"",aDataSort:c.aDataSort?c.aDataSort:[e],mData:c.mData?c.mData:e,idx:e});a.aoColumns.push(c);c=a.aoPreSearchCols;c[e]=h.extend({},m.models.oSearch,c[e]);ka(a,e,h(b).data())}function ka(a,b,c){var b=a.aoColumns[b],e=a.oClasses,d=h(b.nTh);if(!b.sWidthOrig){b.sWidthOrig=d.attr("width")||null;var f=(d.attr("style")||"").match(/width:\s*(\d+[pxem%]+)/);f&&(b.sWidthOrig=f[1])}c!==k&&null!==c&&(fb(c),H(m.defaults.column,c),c.mDataProp!==k&&!c.mData&&(c.mData=c.mDataProp),c.sType&&
(b._sManualType=c.sType),c.className&&!c.sClass&&(c.sClass=c.className),h.extend(b,c),E(b,c,"sWidth","sWidthOrig"),c.iDataSort!==k&&(b.aDataSort=[c.iDataSort]),E(b,c,"aDataSort"));var g=b.mData,j=R(g),i=b.mRender?R(b.mRender):null,c=function(a){return"string"===typeof a&&-1!==a.indexOf("@")};b._bAttrSrc=h.isPlainObject(g)&&(c(g.sort)||c(g.type)||c(g.filter));b.fnGetData=function(a,b,c){var e=j(a,b,k,c);return i&&b?i(e,b,a,c):e};b.fnSetData=function(a,b,c){return S(g)(a,b,c)};"number"!==typeof g&&
(a._rowReadObject=!0);a.oFeatures.bSort||(b.bSortable=!1,d.addClass(e.sSortableNone));a=-1!==h.inArray("asc",b.asSorting);c=-1!==h.inArray("desc",b.asSorting);!b.bSortable||!a&&!c?(b.sSortingClass=e.sSortableNone,b.sSortingClassJUI=""):a&&!c?(b.sSortingClass=e.sSortableAsc,b.sSortingClassJUI=e.sSortJUIAscAllowed):!a&&c?(b.sSortingClass=e.sSortableDesc,b.sSortingClassJUI=e.sSortJUIDescAllowed):(b.sSortingClass=e.sSortable,b.sSortingClassJUI=e.sSortJUI)}function X(a){if(!1!==a.oFeatures.bAutoWidth){var b=
a.aoColumns;Ga(a);for(var c=0,e=b.length;c<e;c++)b[c].nTh.style.width=b[c].sWidth}b=a.oScroll;(""!==b.sY||""!==b.sX)&&Y(a);w(a,null,"column-sizing",[a])}function la(a,b){var c=Z(a,"bVisible");return"number"===typeof c[b]?c[b]:null}function $(a,b){var c=Z(a,"bVisible"),c=h.inArray(b,c);return-1!==c?c:null}function aa(a){return Z(a,"bVisible").length}function Z(a,b){var c=[];h.map(a.aoColumns,function(a,d){a[b]&&c.push(d)});return c}function Ha(a){var b=a.aoColumns,c=a.aoData,e=m.ext.type.detect,d,
f,g,j,i,h,l,q,n;d=0;for(f=b.length;d<f;d++)if(l=b[d],n=[],!l.sType&&l._sManualType)l.sType=l._sManualType;else if(!l.sType){g=0;for(j=e.length;g<j;g++){i=0;for(h=c.length;i<h;i++){n[i]===k&&(n[i]=x(a,i,d,"type"));q=e[g](n[i],a);if(!q&&g!==e.length-1)break;if("html"===q)break}if(q){l.sType=q;break}}l.sType||(l.sType="string")}}function ib(a,b,c,e){var d,f,g,j,i,o,l=a.aoColumns;if(b)for(d=b.length-1;0<=d;d--){o=b[d];var q=o.targets!==k?o.targets:o.aTargets;h.isArray(q)||(q=[q]);f=0;for(g=q.length;f<
g;f++)if("number"===typeof q[f]&&0<=q[f]){for(;l.length<=q[f];)Fa(a);e(q[f],o)}else if("number"===typeof q[f]&&0>q[f])e(l.length+q[f],o);else if("string"===typeof q[f]){j=0;for(i=l.length;j<i;j++)("_all"==q[f]||h(l[j].nTh).hasClass(q[f]))&&e(j,o)}}if(c){d=0;for(a=c.length;d<a;d++)e(d,c[d])}}function K(a,b,c,e){var d=a.aoData.length,f=h.extend(!0,{},m.models.oRow,{src:c?"dom":"data"});f._aData=b;a.aoData.push(f);for(var b=a.aoColumns,f=0,g=b.length;f<g;f++)c&&Ia(a,d,f,x(a,d,f)),b[f].sType=null;a.aiDisplayMaster.push(d);
(c||!a.oFeatures.bDeferRender)&&Ja(a,d,c,e);return d}function ma(a,b){var c;b instanceof h||(b=h(b));return b.map(function(b,d){c=na(a,d);return K(a,c.data,d,c.cells)})}function x(a,b,c,e){var d=a.iDraw,f=a.aoColumns[c],g=a.aoData[b]._aData,j=f.sDefaultContent,c=f.fnGetData(g,e,{settings:a,row:b,col:c});if(c===k)return a.iDrawError!=d&&null===j&&(I(a,0,"Requested unknown parameter "+("function"==typeof f.mData?"{function}":"'"+f.mData+"'")+" for row "+b,4),a.iDrawError=d),j;if((c===g||null===c)&&
null!==j)c=j;else if("function"===typeof c)return c.call(g);return null===c&&"display"==e?"":c}function Ia(a,b,c,e){a.aoColumns[c].fnSetData(a.aoData[b]._aData,e,{settings:a,row:b,col:c})}function Ka(a){return h.map(a.match(/(\\.|[^\.])+/g),function(a){return a.replace(/\\./g,".")})}function R(a){if(h.isPlainObject(a)){var b={};h.each(a,function(a,c){c&&(b[a]=R(c))});return function(a,c,f,g){var j=b[c]||b._;return j!==k?j(a,c,f,g):a}}if(null===a)return function(a){return a};if("function"===typeof a)return function(b,
c,f,g){return a(b,c,f,g)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var c=function(a,b,f){var g,j;if(""!==f){j=Ka(f);for(var i=0,h=j.length;i<h;i++){f=j[i].match(ba);g=j[i].match(T);if(f){j[i]=j[i].replace(ba,"");""!==j[i]&&(a=a[j[i]]);g=[];j.splice(0,i+1);j=j.join(".");i=0;for(h=a.length;i<h;i++)g.push(c(a[i],b,j));a=f[0].substring(1,f[0].length-1);a=""===a?g:g.join(a);break}else if(g){j[i]=j[i].replace(T,"");a=a[j[i]]();continue}if(null===a||a[j[i]]===
k)return k;a=a[j[i]]}}return a};return function(b,d){return c(b,d,a)}}return function(b){return b[a]}}function S(a){if(h.isPlainObject(a))return S(a._);if(null===a)return function(){};if("function"===typeof a)return function(b,e,d){a(b,"set",e,d)};if("string"===typeof a&&(-1!==a.indexOf(".")||-1!==a.indexOf("[")||-1!==a.indexOf("("))){var b=function(a,e,d){var d=Ka(d),f;f=d[d.length-1];for(var g,j,i=0,h=d.length-1;i<h;i++){g=d[i].match(ba);j=d[i].match(T);if(g){d[i]=d[i].replace(ba,"");a[d[i]]=[];
f=d.slice();f.splice(0,i+1);g=f.join(".");j=0;for(h=e.length;j<h;j++)f={},b(f,e[j],g),a[d[i]].push(f);return}j&&(d[i]=d[i].replace(T,""),a=a[d[i]](e));if(null===a[d[i]]||a[d[i]]===k)a[d[i]]={};a=a[d[i]]}if(f.match(T))a[f.replace(T,"")](e);else a[f.replace(ba,"")]=e};return function(c,e){return b(c,e,a)}}return function(b,e){b[a]=e}}function La(a){return D(a.aoData,"_aData")}function oa(a){a.aoData.length=0;a.aiDisplayMaster.length=0;a.aiDisplay.length=0}function pa(a,b,c){for(var e=-1,d=0,f=a.length;d<
f;d++)a[d]==b?e=d:a[d]>b&&a[d]--; -1!=e&&c===k&&a.splice(e,1)}function ca(a,b,c,e){var d=a.aoData[b],f,g=function(c,f){for(;c.childNodes.length;)c.removeChild(c.firstChild);c.innerHTML=x(a,b,f,"display")};if("dom"===c||(!c||"auto"===c)&&"dom"===d.src)d._aData=na(a,d,e,e===k?k:d._aData).data;else{var j=d.anCells;if(j)if(e!==k)g(j[e],e);else{c=0;for(f=j.length;c<f;c++)g(j[c],c)}}d._aSortData=null;d._aFilterData=null;g=a.aoColumns;if(e!==k)g[e].sType=null;else{c=0;for(f=g.length;c<f;c++)g[c].sType=null;
Ma(d)}}function na(a,b,c,e){var d=[],f=b.firstChild,g,j=0,i,o=a.aoColumns,l=a._rowReadObject,e=e||l?{}:[],q=function(a,b){if("string"===typeof a){var c=a.indexOf("@");-1!==c&&(c=a.substring(c+1),S(a)(e,b.getAttribute(c)))}},a=function(a){if(c===k||c===j)g=o[j],i=h.trim(a.innerHTML),g&&g._bAttrSrc?(S(g.mData._)(e,i),q(g.mData.sort,a),q(g.mData.type,a),q(g.mData.filter,a)):l?(g._setter||(g._setter=S(g.mData)),g._setter(e,i)):e[j]=i;j++};if(f)for(;f;){b=f.nodeName.toUpperCase();if("TD"==b||"TH"==b)a(f),
d.push(f);f=f.nextSibling}else{d=b.anCells;f=0;for(b=d.length;f<b;f++)a(d[f])}return{data:e,cells:d}}function Ja(a,b,c,e){var d=a.aoData[b],f=d._aData,g=[],j,i,h,l,q;if(null===d.nTr){j=c||Q.createElement("tr");d.nTr=j;d.anCells=g;j._DT_RowIndex=b;Ma(d);l=0;for(q=a.aoColumns.length;l<q;l++){h=a.aoColumns[l];i=c?e[l]:Q.createElement(h.sCellType);g.push(i);if(!c||h.mRender||h.mData!==l)i.innerHTML=x(a,b,l,"display");h.sClass&&(i.className+=" "+h.sClass);h.bVisible&&!c?j.appendChild(i):!h.bVisible&&c&&
i.parentNode.removeChild(i);h.fnCreatedCell&&h.fnCreatedCell.call(a.oInstance,i,x(a,b,l),f,b,l)}w(a,"aoRowCreatedCallback",null,[j,f,b])}d.nTr.setAttribute("role","row")}function Ma(a){var b=a.nTr,c=a._aData;if(b){c.DT_RowId&&(b.id=c.DT_RowId);if(c.DT_RowClass){var e=c.DT_RowClass.split(" ");a.__rowc=a.__rowc?Na(a.__rowc.concat(e)):e;h(b).removeClass(a.__rowc.join(" ")).addClass(c.DT_RowClass)}c.DT_RowAttr&&h(b).attr(c.DT_RowAttr);c.DT_RowData&&h(b).data(c.DT_RowData)}}function jb(a){var b,c,e,d,
f,g=a.nTHead,j=a.nTFoot,i=0===h("th, td",g).length,o=a.oClasses,l=a.aoColumns;i&&(d=h("<tr/>").appendTo(g));b=0;for(c=l.length;b<c;b++)f=l[b],e=h(f.nTh).addClass(f.sClass),i&&e.appendTo(d),a.oFeatures.bSort&&(e.addClass(f.sSortingClass),!1!==f.bSortable&&(e.attr("tabindex",a.iTabIndex).attr("aria-controls",a.sTableId),Oa(a,f.nTh,b))),f.sTitle!=e.html()&&e.html(f.sTitle),Pa(a,"header")(a,e,f,o);i&&da(a.aoHeader,g);h(g).find(">tr").attr("role","row");h(g).find(">tr>th, >tr>td").addClass(o.sHeaderTH);
h(j).find(">tr>th, >tr>td").addClass(o.sFooterTH);if(null!==j){a=a.aoFooter[0];b=0;for(c=a.length;b<c;b++)f=l[b],f.nTf=a[b].cell,f.sClass&&h(f.nTf).addClass(f.sClass)}}function ea(a,b,c){var e,d,f,g=[],j=[],i=a.aoColumns.length,o;if(b){c===k&&(c=!1);e=0;for(d=b.length;e<d;e++){g[e]=b[e].slice();g[e].nTr=b[e].nTr;for(f=i-1;0<=f;f--)!a.aoColumns[f].bVisible&&!c&&g[e].splice(f,1);j.push([])}e=0;for(d=g.length;e<d;e++){if(a=g[e].nTr)for(;f=a.firstChild;)a.removeChild(f);f=0;for(b=g[e].length;f<b;f++)if(o=
i=1,j[e][f]===k){a.appendChild(g[e][f].cell);for(j[e][f]=1;g[e+i]!==k&&g[e][f].cell==g[e+i][f].cell;)j[e+i][f]=1,i++;for(;g[e][f+o]!==k&&g[e][f].cell==g[e][f+o].cell;){for(c=0;c<i;c++)j[e+c][f+o]=1;o++}h(g[e][f].cell).attr("rowspan",i).attr("colspan",o)}}}}function M(a){var b=w(a,"aoPreDrawCallback","preDraw",[a]);if(-1!==h.inArray(!1,b))C(a,!1);else{var b=[],c=0,e=a.asStripeClasses,d=e.length,f=a.oLanguage,g=a.iInitDisplayStart,j="ssp"==B(a),i=a.aiDisplay;a.bDrawing=!0;g!==k&&-1!==g&&(a._iDisplayStart=
j?g:g>=a.fnRecordsDisplay()?0:g,a.iInitDisplayStart=-1);var g=a._iDisplayStart,o=a.fnDisplayEnd();if(a.bDeferLoading)a.bDeferLoading=!1,a.iDraw++,C(a,!1);else if(j){if(!a.bDestroying&&!kb(a))return}else a.iDraw++;if(0!==i.length){f=j?a.aoData.length:o;for(j=j?0:g;j<f;j++){var l=i[j],q=a.aoData[l];null===q.nTr&&Ja(a,l);l=q.nTr;if(0!==d){var n=e[c%d];q._sRowStripe!=n&&(h(l).removeClass(q._sRowStripe).addClass(n),q._sRowStripe=n)}w(a,"aoRowCallback",null,[l,q._aData,c,j]);b.push(l);c++}}else c=f.sZeroRecords,
1==a.iDraw&&"ajax"==B(a)?c=f.sLoadingRecords:f.sEmptyTable&&0===a.fnRecordsTotal()&&(c=f.sEmptyTable),b[0]=h("<tr/>",{"class":d?e[0]:""}).append(h("<td />",{valign:"top",colSpan:aa(a),"class":a.oClasses.sRowEmpty}).html(c))[0];w(a,"aoHeaderCallback","header",[h(a.nTHead).children("tr")[0],La(a),g,o,i]);w(a,"aoFooterCallback","footer",[h(a.nTFoot).children("tr")[0],La(a),g,o,i]);e=h(a.nTBody);e.children().detach();e.append(h(b));w(a,"aoDrawCallback","draw",[a]);a.bSorted=!1;a.bFiltered=!1;a.bDrawing=
!1}}function N(a,b){var c=a.oFeatures,e=c.bFilter;c.bSort&&lb(a);e?fa(a,a.oPreviousSearch):a.aiDisplay=a.aiDisplayMaster.slice();!0!==b&&(a._iDisplayStart=0);a._drawHold=b;M(a);a._drawHold=!1}function mb(a){var b=a.oClasses,c=h(a.nTable),c=h("<div/>").insertBefore(c),e=a.oFeatures,d=h("<div/>",{id:a.sTableId+"_wrapper","class":b.sWrapper+(a.nTFoot?"":" "+b.sNoFooter)});a.nHolding=c[0];a.nTableWrapper=d[0];a.nTableReinsertBefore=a.nTable.nextSibling;for(var f=a.sDom.split(""),g,j,i,o,l,q,n=0;n<f.length;n++){g=
null;j=f[n];if("<"==j){i=h("<div/>")[0];o=f[n+1];if("'"==o||'"'==o){l="";for(q=2;f[n+q]!=o;)l+=f[n+q],q++;"H"==l?l=b.sJUIHeader:"F"==l&&(l=b.sJUIFooter);-1!=l.indexOf(".")?(o=l.split("."),i.id=o[0].substr(1,o[0].length-1),i.className=o[1]):"#"==l.charAt(0)?i.id=l.substr(1,l.length-1):i.className=l;n+=q}d.append(i);d=h(i)}else if(">"==j)d=d.parent();else if("l"==j&&e.bPaginate&&e.bLengthChange)g=nb(a);else if("f"==j&&e.bFilter)g=ob(a);else if("r"==j&&e.bProcessing)g=pb(a);else if("t"==j)g=qb(a);else if("i"==
j&&e.bInfo)g=rb(a);else if("p"==j&&e.bPaginate)g=sb(a);else if(0!==m.ext.feature.length){i=m.ext.feature;q=0;for(o=i.length;q<o;q++)if(j==i[q].cFeature){g=i[q].fnInit(a);break}}g&&(i=a.aanFeatures,i[j]||(i[j]=[]),i[j].push(g),d.append(g))}c.replaceWith(d)}function da(a,b){var c=h(b).children("tr"),e,d,f,g,j,i,o,l,q,n;a.splice(0,a.length);f=0;for(i=c.length;f<i;f++)a.push([]);f=0;for(i=c.length;f<i;f++){e=c[f];for(d=e.firstChild;d;){if("TD"==d.nodeName.toUpperCase()||"TH"==d.nodeName.toUpperCase()){l=
1*d.getAttribute("colspan");q=1*d.getAttribute("rowspan");l=!l||0===l||1===l?1:l;q=!q||0===q||1===q?1:q;g=0;for(j=a[f];j[g];)g++;o=g;n=1===l?!0:!1;for(j=0;j<l;j++)for(g=0;g<q;g++)a[f+g][o+j]={cell:d,unique:n},a[f+g].nTr=e}d=d.nextSibling}}}function qa(a,b,c){var e=[];c||(c=a.aoHeader,b&&(c=[],da(c,b)));for(var b=0,d=c.length;b<d;b++)for(var f=0,g=c[b].length;f<g;f++)if(c[b][f].unique&&(!e[f]||!a.bSortCellsTop))e[f]=c[b][f].cell;return e}function ra(a,b,c){w(a,"aoServerParams","serverParams",[b]);
if(b&&h.isArray(b)){var e={},d=/(.*?)\[\]$/;h.each(b,function(a,b){var c=b.name.match(d);c?(c=c[0],e[c]||(e[c]=[]),e[c].push(b.value)):e[b.name]=b.value});b=e}var f,g=a.ajax,j=a.oInstance,i=function(b){w(a,null,"xhr",[a,b,a.jqXHR]);c(b)};if(h.isPlainObject(g)&&g.data){f=g.data;var o=h.isFunction(f)?f(b,a):f,b=h.isFunction(f)&&o?o:h.extend(!0,b,o);delete g.data}o={data:b,success:function(b){var c=b.error||b.sError;c&&I(a,0,c);a.json=b;i(b)},dataType:"json",cache:!1,type:a.sServerMethod,error:function(b,
c){var f=w(a,null,"xhr",[a,null,a.jqXHR]);-1===h.inArray(!0,f)&&("parsererror"==c?I(a,0,"Invalid JSON response",1):4===b.readyState&&I(a,0,"Ajax error",7));C(a,!1)}};a.oAjaxData=b;w(a,null,"preXhr",[a,b]);a.fnServerData?a.fnServerData.call(j,a.sAjaxSource,h.map(b,function(a,b){return{name:b,value:a}}),i,a):a.sAjaxSource||"string"===typeof g?a.jqXHR=h.ajax(h.extend(o,{url:g||a.sAjaxSource})):h.isFunction(g)?a.jqXHR=g.call(j,b,i,a):(a.jqXHR=h.ajax(h.extend(o,g)),g.data=f)}function kb(a){return a.bAjaxDataGet?
(a.iDraw++,C(a,!0),ra(a,tb(a),function(b){ub(a,b)}),!1):!0}function tb(a){var b=a.aoColumns,c=b.length,e=a.oFeatures,d=a.oPreviousSearch,f=a.aoPreSearchCols,g,j=[],i,o,l,q=U(a);g=a._iDisplayStart;i=!1!==e.bPaginate?a._iDisplayLength:-1;var n=function(a,b){j.push({name:a,value:b})};n("sEcho",a.iDraw);n("iColumns",c);n("sColumns",D(b,"sName").join(","));n("iDisplayStart",g);n("iDisplayLength",i);var k={draw:a.iDraw,columns:[],order:[],start:g,length:i,search:{value:d.sSearch,regex:d.bRegex}};for(g=
0;g<c;g++)o=b[g],l=f[g],i="function"==typeof o.mData?"function":o.mData,k.columns.push({data:i,name:o.sName,searchable:o.bSearchable,orderable:o.bSortable,search:{value:l.sSearch,regex:l.bRegex}}),n("mDataProp_"+g,i),e.bFilter&&(n("sSearch_"+g,l.sSearch),n("bRegex_"+g,l.bRegex),n("bSearchable_"+g,o.bSearchable)),e.bSort&&n("bSortable_"+g,o.bSortable);e.bFilter&&(n("sSearch",d.sSearch),n("bRegex",d.bRegex));e.bSort&&(h.each(q,function(a,b){k.order.push({column:b.col,dir:b.dir});n("iSortCol_"+a,b.col);
n("sSortDir_"+a,b.dir)}),n("iSortingCols",q.length));b=m.ext.legacy.ajax;return null===b?a.sAjaxSource?j:k:b?j:k}function ub(a,b){var c=sa(a,b),e=b.sEcho!==k?b.sEcho:b.draw,d=b.iTotalRecords!==k?b.iTotalRecords:b.recordsTotal,f=b.iTotalDisplayRecords!==k?b.iTotalDisplayRecords:b.recordsFiltered;if(e){if(1*e<a.iDraw)return;a.iDraw=1*e}oa(a);a._iRecordsTotal=parseInt(d,10);a._iRecordsDisplay=parseInt(f,10);e=0;for(d=c.length;e<d;e++)K(a,c[e]);a.aiDisplay=a.aiDisplayMaster.slice();a.bAjaxDataGet=!1;
M(a);a._bInitComplete||ta(a,b);a.bAjaxDataGet=!0;C(a,!1)}function sa(a,b){var c=h.isPlainObject(a.ajax)&&a.ajax.dataSrc!==k?a.ajax.dataSrc:a.sAjaxDataProp;return"data"===c?b.aaData||b[c]:""!==c?R(c)(b):b}function ob(a){var b=a.oClasses,c=a.sTableId,e=a.oLanguage,d=a.oPreviousSearch,f=a.aanFeatures,g='<input type="search" class="'+b.sFilterInput+'"/>',j=e.sSearch,j=j.match(/_INPUT_/)?j.replace("_INPUT_",g):j+g,b=h("<div/>",{id:!f.f?c+"_filter":null,"class":b.sFilter}).append(h("<label/>").append(j)),
f=function(){var b=!this.value?"":this.value;b!=d.sSearch&&(fa(a,{sSearch:b,bRegex:d.bRegex,bSmart:d.bSmart,bCaseInsensitive:d.bCaseInsensitive}),a._iDisplayStart=0,M(a))},g=null!==a.searchDelay?a.searchDelay:"ssp"===B(a)?400:0,i=h("input",b).val(d.sSearch).attr("placeholder",e.sSearchPlaceholder).bind("keyup.DT search.DT input.DT paste.DT cut.DT",g?ua(f,g):f).bind("keypress.DT",function(a){if(13==a.keyCode)return!1}).attr("aria-controls",c);h(a.nTable).on("search.dt.DT",function(b,c){if(a===c)try{i[0]!==
Q.activeElement&&i.val(d.sSearch)}catch(f){}});return b[0]}function fa(a,b,c){var e=a.oPreviousSearch,d=a.aoPreSearchCols,f=function(a){e.sSearch=a.sSearch;e.bRegex=a.bRegex;e.bSmart=a.bSmart;e.bCaseInsensitive=a.bCaseInsensitive};Ha(a);if("ssp"!=B(a)){vb(a,b.sSearch,c,b.bEscapeRegex!==k?!b.bEscapeRegex:b.bRegex,b.bSmart,b.bCaseInsensitive);f(b);for(b=0;b<d.length;b++)wb(a,d[b].sSearch,b,d[b].bEscapeRegex!==k?!d[b].bEscapeRegex:d[b].bRegex,d[b].bSmart,d[b].bCaseInsensitive);xb(a)}else f(b);a.bFiltered=
!0;w(a,null,"search",[a])}function xb(a){for(var b=m.ext.search,c=a.aiDisplay,e,d,f=0,g=b.length;f<g;f++){for(var j=[],i=0,h=c.length;i<h;i++)d=c[i],e=a.aoData[d],b[f](a,e._aFilterData,d,e._aData,i)&&j.push(d);c.length=0;c.push.apply(c,j)}}function wb(a,b,c,e,d,f){if(""!==b)for(var g=a.aiDisplay,e=Qa(b,e,d,f),d=g.length-1;0<=d;d--)b=a.aoData[g[d]]._aFilterData[c],e.test(b)||g.splice(d,1)}function vb(a,b,c,e,d,f){var e=Qa(b,e,d,f),d=a.oPreviousSearch.sSearch,f=a.aiDisplayMaster,g;0!==m.ext.search.length&&
(c=!0);g=yb(a);if(0>=b.length)a.aiDisplay=f.slice();else{if(g||c||d.length>b.length||0!==b.indexOf(d)||a.bSorted)a.aiDisplay=f.slice();b=a.aiDisplay;for(c=b.length-1;0<=c;c--)e.test(a.aoData[b[c]]._sFilterRow)||b.splice(c,1)}}function Qa(a,b,c,e){a=b?a:va(a);c&&(a="^(?=.*?"+h.map(a.match(/"[^"]+"|[^ ]+/g)||[""],function(a){if('"'===a.charAt(0))var b=a.match(/^"(.*)"$/),a=b?b[1]:a;return a.replace('"',"")}).join(")(?=.*?")+").*$");return RegExp(a,e?"i":"")}function va(a){return a.replace(Yb,"\\$1")}
function yb(a){var b=a.aoColumns,c,e,d,f,g,j,i,h,l=m.ext.type.search;c=!1;e=0;for(f=a.aoData.length;e<f;e++)if(h=a.aoData[e],!h._aFilterData){j=[];d=0;for(g=b.length;d<g;d++)c=b[d],c.bSearchable?(i=x(a,e,d,"filter"),l[c.sType]&&(i=l[c.sType](i)),null===i&&(i=""),"string"!==typeof i&&i.toString&&(i=i.toString())):i="",i.indexOf&&-1!==i.indexOf("&")&&(wa.innerHTML=i,i=Zb?wa.textContent:wa.innerText),i.replace&&(i=i.replace(/[\r\n]/g,"")),j.push(i);h._aFilterData=j;h._sFilterRow=j.join("  ");c=!0}return c}
function zb(a){return{search:a.sSearch,smart:a.bSmart,regex:a.bRegex,caseInsensitive:a.bCaseInsensitive}}function Ab(a){return{sSearch:a.search,bSmart:a.smart,bRegex:a.regex,bCaseInsensitive:a.caseInsensitive}}function rb(a){var b=a.sTableId,c=a.aanFeatures.i,e=h("<div/>",{"class":a.oClasses.sInfo,id:!c?b+"_info":null});c||(a.aoDrawCallback.push({fn:Bb,sName:"information"}),e.attr("role","status").attr("aria-live","polite"),h(a.nTable).attr("aria-describedby",b+"_info"));return e[0]}function Bb(a){var b=
a.aanFeatures.i;if(0!==b.length){var c=a.oLanguage,e=a._iDisplayStart+1,d=a.fnDisplayEnd(),f=a.fnRecordsTotal(),g=a.fnRecordsDisplay(),j=g?c.sInfo:c.sInfoEmpty;g!==f&&(j+=" "+c.sInfoFiltered);j+=c.sInfoPostFix;j=Cb(a,j);c=c.fnInfoCallback;null!==c&&(j=c.call(a.oInstance,a,e,d,f,g,j));h(b).html(j)}}function Cb(a,b){var c=a.fnFormatNumber,e=a._iDisplayStart+1,d=a._iDisplayLength,f=a.fnRecordsDisplay(),g=-1===d;return b.replace(/_START_/g,c.call(a,e)).replace(/_END_/g,c.call(a,a.fnDisplayEnd())).replace(/_MAX_/g,
c.call(a,a.fnRecordsTotal())).replace(/_TOTAL_/g,c.call(a,f)).replace(/_PAGE_/g,c.call(a,g?1:Math.ceil(e/d))).replace(/_PAGES_/g,c.call(a,g?1:Math.ceil(f/d)))}function ga(a){var b,c,e=a.iInitDisplayStart,d=a.aoColumns,f;c=a.oFeatures;if(a.bInitialised){mb(a);jb(a);ea(a,a.aoHeader);ea(a,a.aoFooter);C(a,!0);c.bAutoWidth&&Ga(a);b=0;for(c=d.length;b<c;b++)f=d[b],f.sWidth&&(f.nTh.style.width=s(f.sWidth));N(a);d=B(a);"ssp"!=d&&("ajax"==d?ra(a,[],function(c){var f=sa(a,c);for(b=0;b<f.length;b++)K(a,f[b]);
a.iInitDisplayStart=e;N(a);C(a,!1);ta(a,c)},a):(C(a,!1),ta(a)))}else setTimeout(function(){ga(a)},200)}function ta(a,b){a._bInitComplete=!0;b&&X(a);w(a,"aoInitComplete","init",[a,b])}function Ra(a,b){var c=parseInt(b,10);a._iDisplayLength=c;Sa(a);w(a,null,"length",[a,c])}function nb(a){for(var b=a.oClasses,c=a.sTableId,e=a.aLengthMenu,d=h.isArray(e[0]),f=d?e[0]:e,e=d?e[1]:e,d=h("<select/>",{name:c+"_length","aria-controls":c,"class":b.sLengthSelect}),g=0,j=f.length;g<j;g++)d[0][g]=new Option(e[g],
f[g]);var i=h("<div><label/></div>").addClass(b.sLength);a.aanFeatures.l||(i[0].id=c+"_length");i.children().append(a.oLanguage.sLengthMenu.replace("_MENU_",d[0].outerHTML));h("select",i).val(a._iDisplayLength).bind("change.DT",function(){Ra(a,h(this).val());M(a)});h(a.nTable).bind("length.dt.DT",function(b,c,f){a===c&&h("select",i).val(f)});return i[0]}function sb(a){var b=a.sPaginationType,c=m.ext.pager[b],e="function"===typeof c,d=function(a){M(a)},b=h("<div/>").addClass(a.oClasses.sPaging+b)[0],
f=a.aanFeatures;e||c.fnInit(a,b,d);f.p||(b.id=a.sTableId+"_paginate",a.aoDrawCallback.push({fn:function(a){if(e){var b=a._iDisplayStart,i=a._iDisplayLength,h=a.fnRecordsDisplay(),l=-1===i,b=l?0:Math.ceil(b/i),i=l?1:Math.ceil(h/i),h=c(b,i),q,l=0;for(q=f.p.length;l<q;l++)Pa(a,"pageButton")(a,f.p[l],l,h,b,i)}else c.fnUpdate(a,d)},sName:"pagination"}));return b}function Ta(a,b,c){var e=a._iDisplayStart,d=a._iDisplayLength,f=a.fnRecordsDisplay();0===f||-1===d?e=0:"number"===typeof b?(e=b*d,e>f&&(e=0)):
"first"==b?e=0:"previous"==b?(e=0<=d?e-d:0,0>e&&(e=0)):"next"==b?e+d<f&&(e+=d):"last"==b?e=Math.floor((f-1)/d)*d:I(a,0,"Unknown paging action: "+b,5);b=a._iDisplayStart!==e;a._iDisplayStart=e;b&&(w(a,null,"page",[a]),c&&M(a));return b}function pb(a){return h("<div/>",{id:!a.aanFeatures.r?a.sTableId+"_processing":null,"class":a.oClasses.sProcessing}).html(a.oLanguage.sProcessing).insertBefore(a.nTable)[0]}function C(a,b){a.oFeatures.bProcessing&&h(a.aanFeatures.r).css("display",b?"block":"none");w(a,
null,"processing",[a,b])}function qb(a){var b=h(a.nTable);b.attr("role","grid");var c=a.oScroll;if(""===c.sX&&""===c.sY)return a.nTable;var e=c.sX,d=c.sY,f=a.oClasses,g=b.children("caption"),j=g.length?g[0]._captionSide:null,i=h(b[0].cloneNode(!1)),o=h(b[0].cloneNode(!1)),l=b.children("tfoot");c.sX&&"100%"===b.attr("width")&&b.removeAttr("width");l.length||(l=null);c=h("<div/>",{"class":f.sScrollWrapper}).append(h("<div/>",{"class":f.sScrollHead}).css({overflow:"hidden",position:"relative",border:0,
width:e?!e?null:s(e):"100%"}).append(h("<div/>",{"class":f.sScrollHeadInner}).css({"box-sizing":"content-box",width:c.sXInner||"100%"}).append(i.removeAttr("id").css("margin-left",0).append("top"===j?g:null).append(b.children("thead"))))).append(h("<div/>",{"class":f.sScrollBody}).css({overflow:"auto",height:!d?null:s(d),width:!e?null:s(e)}).append(b));l&&c.append(h("<div/>",{"class":f.sScrollFoot}).css({overflow:"hidden",border:0,width:e?!e?null:s(e):"100%"}).append(h("<div/>",{"class":f.sScrollFootInner}).append(o.removeAttr("id").css("margin-left",
0).append("bottom"===j?g:null).append(b.children("tfoot")))));var b=c.children(),q=b[0],f=b[1],n=l?b[2]:null;if(e)h(f).on("scroll.DT",function(){var a=this.scrollLeft;q.scrollLeft=a;l&&(n.scrollLeft=a)});a.nScrollHead=q;a.nScrollBody=f;a.nScrollFoot=n;a.aoDrawCallback.push({fn:Y,sName:"scrolling"});return c[0]}function Y(a){var b=a.oScroll,c=b.sX,e=b.sXInner,d=b.sY,f=b.iBarWidth,g=h(a.nScrollHead),j=g[0].style,i=g.children("div"),o=i[0].style,l=i.children("table"),i=a.nScrollBody,q=h(i),n=i.style,
k=h(a.nScrollFoot).children("div"),p=k.children("table"),m=h(a.nTHead),r=h(a.nTable),t=r[0],O=t.style,L=a.nTFoot?h(a.nTFoot):null,ha=a.oBrowser,w=ha.bScrollOversize,v,u,y,x,z,A=[],B=[],C=[],D,E=function(a){a=a.style;a.paddingTop="0";a.paddingBottom="0";a.borderTopWidth="0";a.borderBottomWidth="0";a.height=0};r.children("thead, tfoot").remove();z=m.clone().prependTo(r);v=m.find("tr");y=z.find("tr");z.find("th, td").removeAttr("tabindex");L&&(x=L.clone().prependTo(r),u=L.find("tr"),x=x.find("tr"));
c||(n.width="100%",g[0].style.width="100%");h.each(qa(a,z),function(b,c){D=la(a,b);c.style.width=a.aoColumns[D].sWidth});L&&G(function(a){a.style.width=""},x);b.bCollapse&&""!==d&&(n.height=q[0].offsetHeight+m[0].offsetHeight+"px");g=r.outerWidth();if(""===c){if(O.width="100%",w&&(r.find("tbody").height()>i.offsetHeight||"scroll"==q.css("overflow-y")))O.width=s(r.outerWidth()-f)}else""!==e?O.width=s(e):g==q.width()&&q.height()<r.height()?(O.width=s(g-f),r.outerWidth()>g-f&&(O.width=s(g))):O.width=
s(g);g=r.outerWidth();G(E,y);G(function(a){C.push(a.innerHTML);A.push(s(h(a).css("width")))},y);G(function(a,b){a.style.width=A[b]},v);h(y).height(0);L&&(G(E,x),G(function(a){B.push(s(h(a).css("width")))},x),G(function(a,b){a.style.width=B[b]},u),h(x).height(0));G(function(a,b){a.innerHTML='<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+C[b]+"</div>";a.style.width=A[b]},y);L&&G(function(a,b){a.innerHTML="";a.style.width=B[b]},x);if(r.outerWidth()<g){u=i.scrollHeight>i.offsetHeight||
"scroll"==q.css("overflow-y")?g+f:g;if(w&&(i.scrollHeight>i.offsetHeight||"scroll"==q.css("overflow-y")))O.width=s(u-f);(""===c||""!==e)&&I(a,1,"Possible column misalignment",6)}else u="100%";n.width=s(u);j.width=s(u);L&&(a.nScrollFoot.style.width=s(u));!d&&w&&(n.height=s(t.offsetHeight+f));d&&b.bCollapse&&(n.height=s(d),b=c&&t.offsetWidth>i.offsetWidth?f:0,t.offsetHeight<i.offsetHeight&&(n.height=s(t.offsetHeight+b)));b=r.outerWidth();l[0].style.width=s(b);o.width=s(b);l=r.height()>i.clientHeight||
"scroll"==q.css("overflow-y");ha="padding"+(ha.bScrollbarLeft?"Left":"Right");o[ha]=l?f+"px":"0px";L&&(p[0].style.width=s(b),k[0].style.width=s(b),k[0].style[ha]=l?f+"px":"0px");q.scroll();if((a.bSorted||a.bFiltered)&&!a._drawHold)i.scrollTop=0}function G(a,b,c){for(var e=0,d=0,f=b.length,g,j;d<f;){g=b[d].firstChild;for(j=c?c[d].firstChild:null;g;)1===g.nodeType&&(c?a(g,j,e):a(g,e),e++),g=g.nextSibling,j=c?j.nextSibling:null;d++}}function Ga(a){var b=a.nTable,c=a.aoColumns,e=a.oScroll,d=e.sY,f=e.sX,
g=e.sXInner,j=c.length,e=Z(a,"bVisible"),i=h("th",a.nTHead),o=b.getAttribute("width"),l=b.parentNode,k=!1,n,m;(n=b.style.width)&&-1!==n.indexOf("%")&&(o=n);for(n=0;n<e.length;n++)m=c[e[n]],null!==m.sWidth&&(m.sWidth=Db(m.sWidthOrig,l),k=!0);if(!k&&!f&&!d&&j==aa(a)&&j==i.length)for(n=0;n<j;n++)c[n].sWidth=s(i.eq(n).width());else{j=h(b).clone().css("visibility","hidden").removeAttr("id");j.find("tbody tr").remove();var p=h("<tr/>").appendTo(j.find("tbody"));j.find("tfoot th, tfoot td").css("width",
"");i=qa(a,j.find("thead")[0]);for(n=0;n<e.length;n++)m=c[e[n]],i[n].style.width=null!==m.sWidthOrig&&""!==m.sWidthOrig?s(m.sWidthOrig):"";if(a.aoData.length)for(n=0;n<e.length;n++)k=e[n],m=c[k],h(Eb(a,k)).clone(!1).append(m.sContentPadding).appendTo(p);j.appendTo(l);f&&g?j.width(g):f?(j.css("width","auto"),j.width()<l.offsetWidth&&j.width(l.offsetWidth)):d?j.width(l.offsetWidth):o&&j.width(o);Fb(a,j[0]);if(f){for(n=g=0;n<e.length;n++)m=c[e[n]],d=h(i[n]).outerWidth(),g+=null===m.sWidthOrig?d:parseInt(m.sWidth,
10)+d-h(i[n]).width();j.width(s(g));b.style.width=s(g)}for(n=0;n<e.length;n++)if(m=c[e[n]],d=h(i[n]).width())m.sWidth=s(d);b.style.width=s(j.css("width"));j.remove()}o&&(b.style.width=s(o));if((o||f)&&!a._reszEvt)b=function(){h(Ea).bind("resize.DT-"+a.sInstance,ua(function(){X(a)}))},a.oBrowser.bScrollOversize?setTimeout(b,1E3):b(),a._reszEvt=!0}function ua(a,b){var c=b!==k?b:200,e,d;return function(){var b=this,g=+new Date,j=arguments;e&&g<e+c?(clearTimeout(d),d=setTimeout(function(){e=k;a.apply(b,
j)},c)):(e=g,a.apply(b,j))}}function Db(a,b){if(!a)return 0;var c=h("<div/>").css("width",s(a)).appendTo(b||Q.body),e=c[0].offsetWidth;c.remove();return e}function Fb(a,b){var c=a.oScroll;if(c.sX||c.sY)c=!c.sX?c.iBarWidth:0,b.style.width=s(h(b).outerWidth()-c)}function Eb(a,b){var c=Gb(a,b);if(0>c)return null;var e=a.aoData[c];return!e.nTr?h("<td/>").html(x(a,c,b,"display"))[0]:e.anCells[b]}function Gb(a,b){for(var c,e=-1,d=-1,f=0,g=a.aoData.length;f<g;f++)c=x(a,f,b,"display")+"",c=c.replace($b,""),
c.length>e&&(e=c.length,d=f);return d}function s(a){return null===a?"0px":"number"==typeof a?0>a?"0px":a+"px":a.match(/\d$/)?a+"px":a}function Hb(){var a=m.__scrollbarWidth;if(a===k){var b=h("<p/>").css({position:"absolute",top:0,left:0,width:"100%",height:150,padding:0,overflow:"scroll",visibility:"hidden"}).appendTo("body"),a=b[0].offsetWidth-b[0].clientWidth;m.__scrollbarWidth=a;b.remove()}return a}function U(a){var b,c,e=[],d=a.aoColumns,f,g,j,i;b=a.aaSortingFixed;c=h.isPlainObject(b);var o=[];
f=function(a){a.length&&!h.isArray(a[0])?o.push(a):o.push.apply(o,a)};h.isArray(b)&&f(b);c&&b.pre&&f(b.pre);f(a.aaSorting);c&&b.post&&f(b.post);for(a=0;a<o.length;a++){i=o[a][0];f=d[i].aDataSort;b=0;for(c=f.length;b<c;b++)g=f[b],j=d[g].sType||"string",o[a]._idx===k&&(o[a]._idx=h.inArray(o[a][1],d[g].asSorting)),e.push({src:i,col:g,dir:o[a][1],index:o[a]._idx,type:j,formatter:m.ext.type.order[j+"-pre"]})}return e}function lb(a){var b,c,e=[],d=m.ext.type.order,f=a.aoData,g=0,j,i=a.aiDisplayMaster,h;
Ha(a);h=U(a);b=0;for(c=h.length;b<c;b++)j=h[b],j.formatter&&g++,Ib(a,j.col);if("ssp"!=B(a)&&0!==h.length){b=0;for(c=i.length;b<c;b++)e[i[b]]=b;g===h.length?i.sort(function(a,b){var c,d,g,j,i=h.length,k=f[a]._aSortData,m=f[b]._aSortData;for(g=0;g<i;g++)if(j=h[g],c=k[j.col],d=m[j.col],c=c<d?-1:c>d?1:0,0!==c)return"asc"===j.dir?c:-c;c=e[a];d=e[b];return c<d?-1:c>d?1:0}):i.sort(function(a,b){var c,g,j,i,k=h.length,m=f[a]._aSortData,r=f[b]._aSortData;for(j=0;j<k;j++)if(i=h[j],c=m[i.col],g=r[i.col],i=d[i.type+
"-"+i.dir]||d["string-"+i.dir],c=i(c,g),0!==c)return c;c=e[a];g=e[b];return c<g?-1:c>g?1:0})}a.bSorted=!0}function Jb(a){for(var b,c,e=a.aoColumns,d=U(a),a=a.oLanguage.oAria,f=0,g=e.length;f<g;f++){c=e[f];var j=c.asSorting;b=c.sTitle.replace(/<.*?>/g,"");var i=c.nTh;i.removeAttribute("aria-sort");c.bSortable&&(0<d.length&&d[0].col==f?(i.setAttribute("aria-sort","asc"==d[0].dir?"ascending":"descending"),c=j[d[0].index+1]||j[0]):c=j[0],b+="asc"===c?a.sSortAscending:a.sSortDescending);i.setAttribute("aria-label",
b)}}function Ua(a,b,c,e){var d=a.aaSorting,f=a.aoColumns[b].asSorting,g=function(a,b){var c=a._idx;c===k&&(c=h.inArray(a[1],f));return c+1<f.length?c+1:b?null:0};"number"===typeof d[0]&&(d=a.aaSorting=[d]);c&&a.oFeatures.bSortMulti?(c=h.inArray(b,D(d,"0")),-1!==c?(b=g(d[c],!0),null===b&&1===d.length&&(b=0),null===b?d.splice(c,1):(d[c][1]=f[b],d[c]._idx=b)):(d.push([b,f[0],0]),d[d.length-1]._idx=0)):d.length&&d[0][0]==b?(b=g(d[0]),d.length=1,d[0][1]=f[b],d[0]._idx=b):(d.length=0,d.push([b,f[0]]),d[0]._idx=
0);N(a);"function"==typeof e&&e(a)}function Oa(a,b,c,e){var d=a.aoColumns[c];Va(b,{},function(b){!1!==d.bSortable&&(a.oFeatures.bProcessing?(C(a,!0),setTimeout(function(){Ua(a,c,b.shiftKey,e);"ssp"!==B(a)&&C(a,!1)},0)):Ua(a,c,b.shiftKey,e))})}function xa(a){var b=a.aLastSort,c=a.oClasses.sSortColumn,e=U(a),d=a.oFeatures,f,g;if(d.bSort&&d.bSortClasses){d=0;for(f=b.length;d<f;d++)g=b[d].src,h(D(a.aoData,"anCells",g)).removeClass(c+(2>d?d+1:3));d=0;for(f=e.length;d<f;d++)g=e[d].src,h(D(a.aoData,"anCells",
g)).addClass(c+(2>d?d+1:3))}a.aLastSort=e}function Ib(a,b){var c=a.aoColumns[b],e=m.ext.order[c.sSortDataType],d;e&&(d=e.call(a.oInstance,a,b,$(a,b)));for(var f,g=m.ext.type.order[c.sType+"-pre"],j=0,i=a.aoData.length;j<i;j++)if(c=a.aoData[j],c._aSortData||(c._aSortData=[]),!c._aSortData[b]||e)f=e?d[j]:x(a,j,b,"sort"),c._aSortData[b]=g?g(f):f}function ya(a){if(a.oFeatures.bStateSave&&!a.bDestroying){var b={time:+new Date,start:a._iDisplayStart,length:a._iDisplayLength,order:h.extend(!0,[],a.aaSorting),
search:zb(a.oPreviousSearch),columns:h.map(a.aoColumns,function(b,e){return{visible:b.bVisible,search:zb(a.aoPreSearchCols[e])}})};w(a,"aoStateSaveParams","stateSaveParams",[a,b]);a.oSavedState=b;a.fnStateSaveCallback.call(a.oInstance,a,b)}}function Kb(a){var b,c,e=a.aoColumns;if(a.oFeatures.bStateSave){var d=a.fnStateLoadCallback.call(a.oInstance,a);if(d&&d.time&&(b=w(a,"aoStateLoadParams","stateLoadParams",[a,d]),-1===h.inArray(!1,b)&&(b=a.iStateDuration,!(0<b&&d.time<+new Date-1E3*b)&&e.length===
d.columns.length))){a.oLoadedState=h.extend(!0,{},d);d.start!==k&&(a._iDisplayStart=d.start,a.iInitDisplayStart=d.start);d.length!==k&&(a._iDisplayLength=d.length);d.order!==k&&(a.aaSorting=[],h.each(d.order,function(b,c){a.aaSorting.push(c[0]>=e.length?[0,c[1]]:c)}));d.search!==k&&h.extend(a.oPreviousSearch,Ab(d.search));b=0;for(c=d.columns.length;b<c;b++){var f=d.columns[b];f.visible!==k&&(e[b].bVisible=f.visible);f.search!==k&&h.extend(a.aoPreSearchCols[b],Ab(f.search))}w(a,"aoStateLoaded","stateLoaded",
[a,d])}}}function za(a){var b=m.settings,a=h.inArray(a,D(b,"nTable"));return-1!==a?b[a]:null}function I(a,b,c,e){c="DataTables warning: "+(null!==a?"table id="+a.sTableId+" - ":"")+c;e&&(c+=". For more information about this error, please see http://datatables.net/tn/"+e);if(b)Ea.console&&console.log&&console.log(c);else if(b=m.ext,b=b.sErrMode||b.errMode,w(a,null,"error",[a,e,c]),"alert"==b)alert(c);else{if("throw"==b)throw Error(c);"function"==typeof b&&b(a,e,c)}}function E(a,b,c,e){h.isArray(c)?
h.each(c,function(c,f){h.isArray(f)?E(a,b,f[0],f[1]):E(a,b,f)}):(e===k&&(e=c),b[c]!==k&&(a[e]=b[c]))}function Lb(a,b,c){var e,d;for(d in b)b.hasOwnProperty(d)&&(e=b[d],h.isPlainObject(e)?(h.isPlainObject(a[d])||(a[d]={}),h.extend(!0,a[d],e)):a[d]=c&&"data"!==d&&"aaData"!==d&&h.isArray(e)?e.slice():e);return a}function Va(a,b,c){h(a).bind("click.DT",b,function(b){a.blur();c(b)}).bind("keypress.DT",b,function(a){13===a.which&&(a.preventDefault(),c(a))}).bind("selectstart.DT",function(){return!1})}function z(a,
b,c,e){c&&a[b].push({fn:c,sName:e})}function w(a,b,c,e){var d=[];b&&(d=h.map(a[b].slice().reverse(),function(b){return b.fn.apply(a.oInstance,e)}));null!==c&&(b=h.Event(c+".dt"),h(a.nTable).trigger(b,e),d.push(b.result));return d}function Sa(a){var b=a._iDisplayStart,c=a.fnDisplayEnd(),e=a._iDisplayLength;b>=c&&(b=c-e);b-=b%e;if(-1===e||0>b)b=0;a._iDisplayStart=b}function Pa(a,b){var c=a.renderer,e=m.ext.renderer[b];return h.isPlainObject(c)&&c[b]?e[c[b]]||e._:"string"===typeof c?e[c]||e._:e._}function B(a){return a.oFeatures.bServerSide?
"ssp":a.ajax||a.sAjaxSource?"ajax":"dom"}function Wa(a,b){var c=[],c=Mb.numbers_length,e=Math.floor(c/2);b<=c?c=V(0,b):a<=e?(c=V(0,c-2),c.push("ellipsis"),c.push(b-1)):(a>=b-1-e?c=V(b-(c-2),b):(c=V(a-e+2,a+e-1),c.push("ellipsis"),c.push(b-1)),c.splice(0,0,"ellipsis"),c.splice(0,0,0));c.DT_el="span";return c}function db(a){h.each({num:function(b){return Aa(b,a)},"num-fmt":function(b){return Aa(b,a,Xa)},"html-num":function(b){return Aa(b,a,Ba)},"html-num-fmt":function(b){return Aa(b,a,Ba,Xa)}},function(b,
c){u.type.order[b+a+"-pre"]=c;b.match(/^html\-/)&&(u.type.search[b+a]=u.type.search.html)})}function Nb(a){return function(){var b=[za(this[m.ext.iApiIndex])].concat(Array.prototype.slice.call(arguments));return m.ext.internal[a].apply(this,b)}}var m,u,t,r,v,Ya={},Ob=/[\r\n]/g,Ba=/<.*?>/g,ac=/^[\w\+\-]/,bc=/[\w\+\-]$/,Yb=RegExp("(\\/|\\.|\\*|\\+|\\?|\\||\\(|\\)|\\[|\\]|\\{|\\}|\\\\|\\$|\\^|\\-)","g"),Xa=/[',$\u00a3\u20ac\u00a5%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi,J=function(a){return!a||!0===a||
"-"===a?!0:!1},Pb=function(a){var b=parseInt(a,10);return!isNaN(b)&&isFinite(a)?b:null},Qb=function(a,b){Ya[b]||(Ya[b]=RegExp(va(b),"g"));return"string"===typeof a&&"."!==b?a.replace(/\./g,"").replace(Ya[b],"."):a},Za=function(a,b,c){var e="string"===typeof a;if(J(a))return!0;b&&e&&(a=Qb(a,b));c&&e&&(a=a.replace(Xa,""));return!isNaN(parseFloat(a))&&isFinite(a)},Rb=function(a,b,c){return J(a)?!0:!(J(a)||"string"===typeof a)?null:Za(a.replace(Ba,""),b,c)?!0:null},D=function(a,b,c){var e=[],d=0,f=a.length;
if(c!==k)for(;d<f;d++)a[d]&&a[d][b]&&e.push(a[d][b][c]);else for(;d<f;d++)a[d]&&e.push(a[d][b]);return e},ia=function(a,b,c,e){var d=[],f=0,g=b.length;if(e!==k)for(;f<g;f++)a[b[f]][c]&&d.push(a[b[f]][c][e]);else for(;f<g;f++)d.push(a[b[f]][c]);return d},V=function(a,b){var c=[],e;b===k?(b=0,e=a):(e=b,b=a);for(var d=b;d<e;d++)c.push(d);return c},Sb=function(a){for(var b=[],c=0,e=a.length;c<e;c++)a[c]&&b.push(a[c]);return b},Na=function(a){var b=[],c,e,d=a.length,f,g=0;e=0;a:for(;e<d;e++){c=a[e];for(f=
0;f<g;f++)if(b[f]===c)continue a;b.push(c);g++}return b},A=function(a,b,c){a[b]!==k&&(a[c]=a[b])},ba=/\[.*?\]$/,T=/\(\)$/,wa=h("<div>")[0],Zb=wa.textContent!==k,$b=/<.*?>/g;m=function(a){this.$=function(a,b){return this.api(!0).$(a,b)};this._=function(a,b){return this.api(!0).rows(a,b).data()};this.api=function(a){return a?new t(za(this[u.iApiIndex])):new t(this)};this.fnAddData=function(a,b){var c=this.api(!0),e=h.isArray(a)&&(h.isArray(a[0])||h.isPlainObject(a[0]))?c.rows.add(a):c.row.add(a);(b===
k||b)&&c.draw();return e.flatten().toArray()};this.fnAdjustColumnSizing=function(a){var b=this.api(!0).columns.adjust(),c=b.settings()[0],e=c.oScroll;a===k||a?b.draw(!1):(""!==e.sX||""!==e.sY)&&Y(c)};this.fnClearTable=function(a){var b=this.api(!0).clear();(a===k||a)&&b.draw()};this.fnClose=function(a){this.api(!0).row(a).child.hide()};this.fnDeleteRow=function(a,b,c){var e=this.api(!0),a=e.rows(a),d=a.settings()[0],h=d.aoData[a[0][0]];a.remove();b&&b.call(this,d,h);(c===k||c)&&e.draw();return h};
this.fnDestroy=function(a){this.api(!0).destroy(a)};this.fnDraw=function(a){this.api(!0).draw(a)};this.fnFilter=function(a,b,c,e,d,h){d=this.api(!0);null===b||b===k?d.search(a,c,e,h):d.column(b).search(a,c,e,h);d.draw()};this.fnGetData=function(a,b){var c=this.api(!0);if(a!==k){var e=a.nodeName?a.nodeName.toLowerCase():"";return b!==k||"td"==e||"th"==e?c.cell(a,b).data():c.row(a).data()||null}return c.data().toArray()};this.fnGetNodes=function(a){var b=this.api(!0);return a!==k?b.row(a).node():b.rows().nodes().flatten().toArray()};
this.fnGetPosition=function(a){var b=this.api(!0),c=a.nodeName.toUpperCase();return"TR"==c?b.row(a).index():"TD"==c||"TH"==c?(a=b.cell(a).index(),[a.row,a.columnVisible,a.column]):null};this.fnIsOpen=function(a){return this.api(!0).row(a).child.isShown()};this.fnOpen=function(a,b,c){return this.api(!0).row(a).child(b,c).show().child()[0]};this.fnPageChange=function(a,b){var c=this.api(!0).page(a);(b===k||b)&&c.draw(!1)};this.fnSetColumnVis=function(a,b,c){a=this.api(!0).column(a).visible(b);(c===
k||c)&&a.columns.adjust().draw()};this.fnSettings=function(){return za(this[u.iApiIndex])};this.fnSort=function(a){this.api(!0).order(a).draw()};this.fnSortListener=function(a,b,c){this.api(!0).order.listener(a,b,c)};this.fnUpdate=function(a,b,c,e,d){var h=this.api(!0);c===k||null===c?h.row(b).data(a):h.cell(b,c).data(a);(d===k||d)&&h.columns.adjust();(e===k||e)&&h.draw();return 0};this.fnVersionCheck=u.fnVersionCheck;var b=this,c=a===k,e=this.length;c&&(a={});this.oApi=this.internal=u.internal;for(var d in m.ext.internal)d&&
(this[d]=Nb(d));this.each(function(){var d={},d=1<e?Lb(d,a,!0):a,g=0,j,i=this.getAttribute("id"),o=!1,l=m.defaults,q=h(this);if("table"!=this.nodeName.toLowerCase())I(null,0,"Non-table node initialisation ("+this.nodeName+")",2);else{eb(l);fb(l.column);H(l,l,!0);H(l.column,l.column,!0);H(l,h.extend(d,q.data()));var n=m.settings,g=0;for(j=n.length;g<j;g++){var r=n[g];if(r.nTable==this||r.nTHead.parentNode==this||r.nTFoot&&r.nTFoot.parentNode==this){g=d.bRetrieve!==k?d.bRetrieve:l.bRetrieve;if(c||g)return r.oInstance;
if(d.bDestroy!==k?d.bDestroy:l.bDestroy){r.oInstance.fnDestroy();break}else{I(r,0,"Cannot reinitialise DataTable",3);return}}if(r.sTableId==this.id){n.splice(g,1);break}}if(null===i||""===i)this.id=i="DataTables_Table_"+m.ext._unique++;var p=h.extend(!0,{},m.models.oSettings,{sDestroyWidth:q[0].style.width,sInstance:i,sTableId:i});p.nTable=this;p.oApi=b.internal;p.oInit=d;n.push(p);p.oInstance=1===b.length?b:q.dataTable();eb(d);d.oLanguage&&P(d.oLanguage);d.aLengthMenu&&!d.iDisplayLength&&(d.iDisplayLength=
h.isArray(d.aLengthMenu[0])?d.aLengthMenu[0][0]:d.aLengthMenu[0]);d=Lb(h.extend(!0,{},l),d);E(p.oFeatures,d,"bPaginate bLengthChange bFilter bSort bSortMulti bInfo bProcessing bAutoWidth bSortClasses bServerSide bDeferRender".split(" "));E(p,d,["asStripeClasses","ajax","fnServerData","fnFormatNumber","sServerMethod","aaSorting","aaSortingFixed","aLengthMenu","sPaginationType","sAjaxSource","sAjaxDataProp","iStateDuration","sDom","bSortCellsTop","iTabIndex","fnStateLoadCallback","fnStateSaveCallback",
"renderer","searchDelay",["iCookieDuration","iStateDuration"],["oSearch","oPreviousSearch"],["aoSearchCols","aoPreSearchCols"],["iDisplayLength","_iDisplayLength"],["bJQueryUI","bJUI"]]);E(p.oScroll,d,[["sScrollX","sX"],["sScrollXInner","sXInner"],["sScrollY","sY"],["bScrollCollapse","bCollapse"]]);E(p.oLanguage,d,"fnInfoCallback");z(p,"aoDrawCallback",d.fnDrawCallback,"user");z(p,"aoServerParams",d.fnServerParams,"user");z(p,"aoStateSaveParams",d.fnStateSaveParams,"user");z(p,"aoStateLoadParams",
d.fnStateLoadParams,"user");z(p,"aoStateLoaded",d.fnStateLoaded,"user");z(p,"aoRowCallback",d.fnRowCallback,"user");z(p,"aoRowCreatedCallback",d.fnCreatedRow,"user");z(p,"aoHeaderCallback",d.fnHeaderCallback,"user");z(p,"aoFooterCallback",d.fnFooterCallback,"user");z(p,"aoInitComplete",d.fnInitComplete,"user");z(p,"aoPreDrawCallback",d.fnPreDrawCallback,"user");i=p.oClasses;d.bJQueryUI?(h.extend(i,m.ext.oJUIClasses,d.oClasses),d.sDom===l.sDom&&"lfrtip"===l.sDom&&(p.sDom='<"H"lfr>t<"F"ip>'),p.renderer)?
h.isPlainObject(p.renderer)&&!p.renderer.header&&(p.renderer.header="jqueryui"):p.renderer="jqueryui":h.extend(i,m.ext.classes,d.oClasses);q.addClass(i.sTable);if(""!==p.oScroll.sX||""!==p.oScroll.sY)p.oScroll.iBarWidth=Hb();!0===p.oScroll.sX&&(p.oScroll.sX="100%");p.iInitDisplayStart===k&&(p.iInitDisplayStart=d.iDisplayStart,p._iDisplayStart=d.iDisplayStart);null!==d.iDeferLoading&&(p.bDeferLoading=!0,g=h.isArray(d.iDeferLoading),p._iRecordsDisplay=g?d.iDeferLoading[0]:d.iDeferLoading,p._iRecordsTotal=
g?d.iDeferLoading[1]:d.iDeferLoading);var t=p.oLanguage;h.extend(!0,t,d.oLanguage);""!==t.sUrl&&(h.ajax({dataType:"json",url:t.sUrl,success:function(a){P(a);H(l.oLanguage,a);h.extend(true,t,a);ga(p)},error:function(){ga(p)}}),o=!0);null===d.asStripeClasses&&(p.asStripeClasses=[i.sStripeOdd,i.sStripeEven]);var g=p.asStripeClasses,s=q.children("tbody").find("tr").eq(0);-1!==h.inArray(!0,h.map(g,function(a){return s.hasClass(a)}))&&(h("tbody tr",this).removeClass(g.join(" ")),p.asDestroyStripes=g.slice());
n=[];g=this.getElementsByTagName("thead");0!==g.length&&(da(p.aoHeader,g[0]),n=qa(p));if(null===d.aoColumns){r=[];g=0;for(j=n.length;g<j;g++)r.push(null)}else r=d.aoColumns;g=0;for(j=r.length;g<j;g++)Fa(p,n?n[g]:null);ib(p,d.aoColumnDefs,r,function(a,b){ka(p,a,b)});if(s.length){var u=function(a,b){return a.getAttribute("data-"+b)!==null?b:null};h.each(na(p,s[0]).cells,function(a,b){var c=p.aoColumns[a];if(c.mData===a){var d=u(b,"sort")||u(b,"order"),e=u(b,"filter")||u(b,"search");if(d!==null||e!==
null){c.mData={_:a+".display",sort:d!==null?a+".@data-"+d:k,type:d!==null?a+".@data-"+d:k,filter:e!==null?a+".@data-"+e:k};ka(p,a)}}})}var v=p.oFeatures;d.bStateSave&&(v.bStateSave=!0,Kb(p,d),z(p,"aoDrawCallback",ya,"state_save"));if(d.aaSorting===k){n=p.aaSorting;g=0;for(j=n.length;g<j;g++)n[g][1]=p.aoColumns[g].asSorting[0]}xa(p);v.bSort&&z(p,"aoDrawCallback",function(){if(p.bSorted){var a=U(p),b={};h.each(a,function(a,c){b[c.src]=c.dir});w(p,null,"order",[p,a,b]);Jb(p)}});z(p,"aoDrawCallback",
function(){(p.bSorted||B(p)==="ssp"||v.bDeferRender)&&xa(p)},"sc");gb(p);g=q.children("caption").each(function(){this._captionSide=q.css("caption-side")});j=q.children("thead");0===j.length&&(j=h("<thead/>").appendTo(this));p.nTHead=j[0];j=q.children("tbody");0===j.length&&(j=h("<tbody/>").appendTo(this));p.nTBody=j[0];j=q.children("tfoot");if(0===j.length&&0<g.length&&(""!==p.oScroll.sX||""!==p.oScroll.sY))j=h("<tfoot/>").appendTo(this);0===j.length||0===j.children().length?q.addClass(i.sNoFooter):
0<j.length&&(p.nTFoot=j[0],da(p.aoFooter,p.nTFoot));if(d.aaData)for(g=0;g<d.aaData.length;g++)K(p,d.aaData[g]);else(p.bDeferLoading||"dom"==B(p))&&ma(p,h(p.nTBody).children("tr"));p.aiDisplay=p.aiDisplayMaster.slice();p.bInitialised=!0;!1===o&&ga(p)}});b=null;return this};var Tb=[],y=Array.prototype,cc=function(a){var b,c,e=m.settings,d=h.map(e,function(a){return a.nTable});if(a){if(a.nTable&&a.oApi)return[a];if(a.nodeName&&"table"===a.nodeName.toLowerCase())return b=h.inArray(a,d),-1!==b?[e[b]]:
null;if(a&&"function"===typeof a.settings)return a.settings().toArray();"string"===typeof a?c=h(a):a instanceof h&&(c=a)}else return[];if(c)return c.map(function(){b=h.inArray(this,d);return-1!==b?e[b]:null}).toArray()};t=function(a,b){if(!(this instanceof t))return new t(a,b);var c=[],e=function(a){(a=cc(a))&&c.push.apply(c,a)};if(h.isArray(a))for(var d=0,f=a.length;d<f;d++)e(a[d]);else e(a);this.context=Na(c);b&&this.push.apply(this,b.toArray?b.toArray():b);this.selector={rows:null,cols:null,opts:null};
t.extend(this,this,Tb)};m.Api=t;t.prototype={any:function(){return 0!==this.flatten().length},concat:y.concat,context:[],each:function(a){for(var b=0,c=this.length;b<c;b++)a.call(this,this[b],b,this);return this},eq:function(a){var b=this.context;return b.length>a?new t(b[a],this[a]):null},filter:function(a){var b=[];if(y.filter)b=y.filter.call(this,a,this);else for(var c=0,e=this.length;c<e;c++)a.call(this,this[c],c,this)&&b.push(this[c]);return new t(this.context,b)},flatten:function(){var a=[];
return new t(this.context,a.concat.apply(a,this.toArray()))},join:y.join,indexOf:y.indexOf||function(a,b){for(var c=b||0,e=this.length;c<e;c++)if(this[c]===a)return c;return-1},iterator:function(a,b,c,e){var d=[],f,g,h,i,o,l=this.context,q,n,m=this.selector;"string"===typeof a&&(e=c,c=b,b=a,a=!1);g=0;for(h=l.length;g<h;g++){var p=new t(l[g]);if("table"===b)f=c.call(p,l[g],g),f!==k&&d.push(f);else if("columns"===b||"rows"===b)f=c.call(p,l[g],this[g],g),f!==k&&d.push(f);else if("column"===b||"column-rows"===
b||"row"===b||"cell"===b){n=this[g];"column-rows"===b&&(q=Ca(l[g],m.opts));i=0;for(o=n.length;i<o;i++)f=n[i],f="cell"===b?c.call(p,l[g],f.row,f.column,g,i):c.call(p,l[g],f,g,i,q),f!==k&&d.push(f)}}return d.length||e?(a=new t(l,a?d.concat.apply([],d):d),b=a.selector,b.rows=m.rows,b.cols=m.cols,b.opts=m.opts,a):this},lastIndexOf:y.lastIndexOf||function(a,b){return this.indexOf.apply(this.toArray.reverse(),arguments)},length:0,map:function(a){var b=[];if(y.map)b=y.map.call(this,a,this);else for(var c=
0,e=this.length;c<e;c++)b.push(a.call(this,this[c],c));return new t(this.context,b)},pluck:function(a){return this.map(function(b){return b[a]})},pop:y.pop,push:y.push,reduce:y.reduce||function(a,b){return hb(this,a,b,0,this.length,1)},reduceRight:y.reduceRight||function(a,b){return hb(this,a,b,this.length-1,-1,-1)},reverse:y.reverse,selector:null,shift:y.shift,sort:y.sort,splice:y.splice,toArray:function(){return y.slice.call(this)},to$:function(){return h(this)},toJQuery:function(){return h(this)},
unique:function(){return new t(this.context,Na(this))},unshift:y.unshift};t.extend=function(a,b,c){if(c.length&&b&&(b instanceof t||b.__dt_wrapper)){var e,d,f,g=function(a,b,c){return function(){var d=b.apply(a,arguments);t.extend(d,d,c.methodExt);return d}};e=0;for(d=c.length;e<d;e++)f=c[e],b[f.name]="function"===typeof f.val?g(a,f.val,f):h.isPlainObject(f.val)?{}:f.val,b[f.name].__dt_wrapper=!0,t.extend(a,b[f.name],f.propExt)}};t.register=r=function(a,b){if(h.isArray(a))for(var c=0,e=a.length;c<
e;c++)t.register(a[c],b);else for(var d=a.split("."),f=Tb,g,j,c=0,e=d.length;c<e;c++){g=(j=-1!==d[c].indexOf("()"))?d[c].replace("()",""):d[c];var i;a:{i=0;for(var o=f.length;i<o;i++)if(f[i].name===g){i=f[i];break a}i=null}i||(i={name:g,val:{},methodExt:[],propExt:[]},f.push(i));c===e-1?i.val=b:f=j?i.methodExt:i.propExt}};t.registerPlural=v=function(a,b,c){t.register(a,c);t.register(b,function(){var a=c.apply(this,arguments);return a===this?this:a instanceof t?a.length?h.isArray(a[0])?new t(a.context,
a[0]):a[0]:k:a})};r("tables()",function(a){var b;if(a){b=t;var c=this.context;if("number"===typeof a)a=[c[a]];else var e=h.map(c,function(a){return a.nTable}),a=h(e).filter(a).map(function(){var a=h.inArray(this,e);return c[a]}).toArray();b=new b(a)}else b=this;return b});r("table()",function(a){var a=this.tables(a),b=a.context;return b.length?new t(b[0]):a});v("tables().nodes()","table().node()",function(){return this.iterator("table",function(a){return a.nTable},1)});v("tables().body()","table().body()",
function(){return this.iterator("table",function(a){return a.nTBody},1)});v("tables().header()","table().header()",function(){return this.iterator("table",function(a){return a.nTHead},1)});v("tables().footer()","table().footer()",function(){return this.iterator("table",function(a){return a.nTFoot},1)});v("tables().containers()","table().container()",function(){return this.iterator("table",function(a){return a.nTableWrapper},1)});r("draw()",function(a){return this.iterator("table",function(b){N(b,
!1===a)})});r("page()",function(a){return a===k?this.page.info().page:this.iterator("table",function(b){Ta(b,a)})});r("page.info()",function(){if(0===this.context.length)return k;var a=this.context[0],b=a._iDisplayStart,c=a._iDisplayLength,e=a.fnRecordsDisplay(),d=-1===c;return{page:d?0:Math.floor(b/c),pages:d?1:Math.ceil(e/c),start:b,end:a.fnDisplayEnd(),length:c,recordsTotal:a.fnRecordsTotal(),recordsDisplay:e}});r("page.len()",function(a){return a===k?0!==this.context.length?this.context[0]._iDisplayLength:
k:this.iterator("table",function(b){Ra(b,a)})});var Ub=function(a,b,c){if(c){var e=new t(a);e.one("draw",function(){c(e.ajax.json())})}"ssp"==B(a)?N(a,b):(C(a,!0),ra(a,[],function(c){oa(a);for(var c=sa(a,c),e=0,g=c.length;e<g;e++)K(a,c[e]);N(a,b);C(a,!1)}))};r("ajax.json()",function(){var a=this.context;if(0<a.length)return a[0].json});r("ajax.params()",function(){var a=this.context;if(0<a.length)return a[0].oAjaxData});r("ajax.reload()",function(a,b){return this.iterator("table",function(c){Ub(c,
!1===b,a)})});r("ajax.url()",function(a){var b=this.context;if(a===k){if(0===b.length)return k;b=b[0];return b.ajax?h.isPlainObject(b.ajax)?b.ajax.url:b.ajax:b.sAjaxSource}return this.iterator("table",function(b){h.isPlainObject(b.ajax)?b.ajax.url=a:b.ajax=a})});r("ajax.url().load()",function(a,b){return this.iterator("table",function(c){Ub(c,!1===b,a)})});var $a=function(a,b,c,e,d){var f=[],g,j,i,o,l,q;i=typeof b;if(!b||"string"===i||"function"===i||b.length===k)b=[b];i=0;for(o=b.length;i<o;i++){j=
b[i]&&b[i].split?b[i].split(","):[b[i]];l=0;for(q=j.length;l<q;l++)(g=c("string"===typeof j[l]?h.trim(j[l]):j[l]))&&g.length&&f.push.apply(f,g)}a=u.selector[a];if(a.length){i=0;for(o=a.length;i<o;i++)f=a[i](e,d,f)}return f},ab=function(a){a||(a={});a.filter&&a.search===k&&(a.search=a.filter);return h.extend({search:"none",order:"current",page:"all"},a)},bb=function(a){for(var b=0,c=a.length;b<c;b++)if(0<a[b].length)return a[0]=a[b],a[0].length=1,a.length=1,a.context=[a.context[b]],a;a.length=0;return a},
Ca=function(a,b){var c,e,d,f=[],g=a.aiDisplay;c=a.aiDisplayMaster;var j=b.search;e=b.order;d=b.page;if("ssp"==B(a))return"removed"===j?[]:V(0,c.length);if("current"==d){c=a._iDisplayStart;for(e=a.fnDisplayEnd();c<e;c++)f.push(g[c])}else if("current"==e||"applied"==e)f="none"==j?c.slice():"applied"==j?g.slice():h.map(c,function(a){return-1===h.inArray(a,g)?a:null});else if("index"==e||"original"==e){c=0;for(e=a.aoData.length;c<e;c++)"none"==j?f.push(c):(d=h.inArray(c,g),(-1===d&&"removed"==j||0<=d&&
"applied"==j)&&f.push(c))}return f};r("rows()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=ab(b),c=this.iterator("table",function(c){var d=b;return $a("row",a,function(a){var b=Pb(a);if(b!==null&&!d)return[b];var j=Ca(c,d);if(b!==null&&h.inArray(b,j)!==-1)return[b];if(!a)return j;if(typeof a==="function")return h.map(j,function(b){var d=c.aoData[b];return a(b,d._aData,d.nTr)?b:null});b=Sb(ia(c.aoData,j,"nTr"));return a.nodeName&&h.inArray(a,b)!==-1?[a._DT_RowIndex]:h(b).filter(a).map(function(){return this._DT_RowIndex}).toArray()},
c,d)},1);c.selector.rows=a;c.selector.opts=b;return c});r("rows().nodes()",function(){return this.iterator("row",function(a,b){return a.aoData[b].nTr||k},1)});r("rows().data()",function(){return this.iterator(!0,"rows",function(a,b){return ia(a.aoData,b,"_aData")},1)});v("rows().cache()","row().cache()",function(a){return this.iterator("row",function(b,c){var e=b.aoData[c];return"search"===a?e._aFilterData:e._aSortData},1)});v("rows().invalidate()","row().invalidate()",function(a){return this.iterator("row",
function(b,c){ca(b,c,a)})});v("rows().indexes()","row().index()",function(){return this.iterator("row",function(a,b){return b},1)});v("rows().remove()","row().remove()",function(){var a=this;return this.iterator("row",function(b,c,e){var d=b.aoData;d.splice(c,1);for(var f=0,g=d.length;f<g;f++)null!==d[f].nTr&&(d[f].nTr._DT_RowIndex=f);h.inArray(c,b.aiDisplay);pa(b.aiDisplayMaster,c);pa(b.aiDisplay,c);pa(a[e],c,!1);Sa(b)})});r("rows.add()",function(a){var b=this.iterator("table",function(b){var c,
f,g,h=[];f=0;for(g=a.length;f<g;f++)c=a[f],c.nodeName&&"TR"===c.nodeName.toUpperCase()?h.push(ma(b,c)[0]):h.push(K(b,c));return h},1),c=this.rows(-1);c.pop();c.push.apply(c,b.toArray());return c});r("row()",function(a,b){return bb(this.rows(a,b))});r("row().data()",function(a){var b=this.context;if(a===k)return b.length&&this.length?b[0].aoData[this[0]]._aData:k;b[0].aoData[this[0]]._aData=a;ca(b[0],this[0],"data");return this});r("row().node()",function(){var a=this.context;return a.length&&this.length?
a[0].aoData[this[0]].nTr||null:null});r("row.add()",function(a){a instanceof h&&a.length&&(a=a[0]);var b=this.iterator("table",function(b){return a.nodeName&&"TR"===a.nodeName.toUpperCase()?ma(b,a)[0]:K(b,a)});return this.row(b[0])});var cb=function(a,b){var c=a.context;c.length&&(c=c[0].aoData[b!==k?b:a[0]],c._details&&(c._details.remove(),c._detailsShow=k,c._details=k))},Vb=function(a,b){var c=a.context;if(c.length&&a.length){var e=c[0].aoData[a[0]];if(e._details){(e._detailsShow=b)?e._details.insertAfter(e.nTr):
e._details.detach();var d=c[0],f=new t(d),g=d.aoData;f.off("draw.dt.DT_details column-visibility.dt.DT_details destroy.dt.DT_details");0<D(g,"_details").length&&(f.on("draw.dt.DT_details",function(a,b){d===b&&f.rows({page:"current"}).eq(0).each(function(a){a=g[a];a._detailsShow&&a._details.insertAfter(a.nTr)})}),f.on("column-visibility.dt.DT_details",function(a,b){if(d===b)for(var c,e=aa(b),f=0,h=g.length;f<h;f++)c=g[f],c._details&&c._details.children("td[colspan]").attr("colspan",e)}),f.on("destroy.dt.DT_details",
function(a,b){if(d===b)for(var c=0,e=g.length;c<e;c++)g[c]._details&&cb(f,c)}))}}};r("row().child()",function(a,b){var c=this.context;if(a===k)return c.length&&this.length?c[0].aoData[this[0]]._details:k;if(!0===a)this.child.show();else if(!1===a)cb(this);else if(c.length&&this.length){var e=c[0],c=c[0].aoData[this[0]],d=[],f=function(a,b){if(h.isArray(a)||a instanceof h)for(var c=0,k=a.length;c<k;c++)f(a[c],b);else a.nodeName&&"tr"===a.nodeName.toLowerCase()?d.push(a):(c=h("<tr><td/></tr>").addClass(b),
h("td",c).addClass(b).html(a)[0].colSpan=aa(e),d.push(c[0]))};f(a,b);c._details&&c._details.remove();c._details=h(d);c._detailsShow&&c._details.insertAfter(c.nTr)}return this});r(["row().child.show()","row().child().show()"],function(){Vb(this,!0);return this});r(["row().child.hide()","row().child().hide()"],function(){Vb(this,!1);return this});r(["row().child.remove()","row().child().remove()"],function(){cb(this);return this});r("row().child.isShown()",function(){var a=this.context;return a.length&&
this.length?a[0].aoData[this[0]]._detailsShow||!1:!1});var dc=/^(.+):(name|visIdx|visible)$/,Wb=function(a,b,c,e,d){for(var c=[],e=0,f=d.length;e<f;e++)c.push(x(a,d[e],b));return c};r("columns()",function(a,b){a===k?a="":h.isPlainObject(a)&&(b=a,a="");var b=ab(b),c=this.iterator("table",function(c){var d=a,f=b,g=c.aoColumns,j=D(g,"sName"),i=D(g,"nTh");return $a("column",d,function(a){var b=Pb(a);if(a==="")return V(g.length);if(b!==null)return[b>=0?b:g.length+b];if(typeof a==="function"){var d=Ca(c,
f);return h.map(g,function(b,f){return a(f,Wb(c,f,0,0,d),i[f])?f:null})}var k=typeof a==="string"?a.match(dc):"";if(k)switch(k[2]){case "visIdx":case "visible":b=parseInt(k[1],10);if(b<0){var m=h.map(g,function(a,b){return a.bVisible?b:null});return[m[m.length+b]]}return[la(c,b)];case "name":return h.map(j,function(a,b){return a===k[1]?b:null})}else return h(i).filter(a).map(function(){return h.inArray(this,i)}).toArray()},c,f)},1);c.selector.cols=a;c.selector.opts=b;return c});v("columns().header()",
"column().header()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTh},1)});v("columns().footer()","column().footer()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].nTf},1)});v("columns().data()","column().data()",function(){return this.iterator("column-rows",Wb,1)});v("columns().dataSrc()","column().dataSrc()",function(){return this.iterator("column",function(a,b){return a.aoColumns[b].mData},1)});v("columns().cache()","column().cache()",
function(a){return this.iterator("column-rows",function(b,c,e,d,f){return ia(b.aoData,f,"search"===a?"_aFilterData":"_aSortData",c)},1)});v("columns().nodes()","column().nodes()",function(){return this.iterator("column-rows",function(a,b,c,e,d){return ia(a.aoData,d,"anCells",b)},1)});v("columns().visible()","column().visible()",function(a,b){return this.iterator("column",function(c,e){if(a===k)return c.aoColumns[e].bVisible;var d=c.aoColumns,f=d[e],g=c.aoData,j,i,m;if(a!==k&&f.bVisible!==a){if(a){var l=
h.inArray(!0,D(d,"bVisible"),e+1);j=0;for(i=g.length;j<i;j++)m=g[j].nTr,d=g[j].anCells,m&&m.insertBefore(d[e],d[l]||null)}else h(D(c.aoData,"anCells",e)).detach();f.bVisible=a;ea(c,c.aoHeader);ea(c,c.aoFooter);if(b===k||b)X(c),(c.oScroll.sX||c.oScroll.sY)&&Y(c);w(c,null,"column-visibility",[c,e,a]);ya(c)}})});v("columns().indexes()","column().index()",function(a){return this.iterator("column",function(b,c){return"visible"===a?$(b,c):c},1)});r("columns.adjust()",function(){return this.iterator("table",
function(a){X(a)},1)});r("column.index()",function(a,b){if(0!==this.context.length){var c=this.context[0];if("fromVisible"===a||"toData"===a)return la(c,b);if("fromData"===a||"toVisible"===a)return $(c,b)}});r("column()",function(a,b){return bb(this.columns(a,b))});r("cells()",function(a,b,c){h.isPlainObject(a)&&(a.row===k?(c=a,a=null):(c=b,b=null));h.isPlainObject(b)&&(c=b,b=null);if(null===b||b===k)return this.iterator("table",function(b){var d=a,e=ab(c),f=b.aoData,g=Ca(b,e),i=Sb(ia(f,g,"anCells")),
j=h([].concat.apply([],i)),l,m=b.aoColumns.length,o,r,t,s,u,v;return $a("cell",d,function(a){var c=typeof a==="function";if(a===null||a===k||c){o=[];r=0;for(t=g.length;r<t;r++){l=g[r];for(s=0;s<m;s++){u={row:l,column:s};if(c){v=b.aoData[l];a(u,x(b,l,s),v.anCells?v.anCells[s]:null)&&o.push(u)}else o.push(u)}}return o}return h.isPlainObject(a)?[a]:j.filter(a).map(function(a,b){l=b.parentNode._DT_RowIndex;return{row:l,column:h.inArray(b,f[l].anCells)}}).toArray()},b,e)});var e=this.columns(b,c),d=this.rows(a,
c),f,g,j,i,m,l=this.iterator("table",function(a,b){f=[];g=0;for(j=d[b].length;g<j;g++){i=0;for(m=e[b].length;i<m;i++)f.push({row:d[b][g],column:e[b][i]})}return f},1);h.extend(l.selector,{cols:b,rows:a,opts:c});return l});v("cells().nodes()","cell().node()",function(){return this.iterator("cell",function(a,b,c){return(a=a.aoData[b].anCells)?a[c]:k},1)});r("cells().data()",function(){return this.iterator("cell",function(a,b,c){return x(a,b,c)},1)});v("cells().cache()","cell().cache()",function(a){a=
"search"===a?"_aFilterData":"_aSortData";return this.iterator("cell",function(b,c,e){return b.aoData[c][a][e]},1)});v("cells().render()","cell().render()",function(a){return this.iterator("cell",function(b,c,e){return x(b,c,e,a)},1)});v("cells().indexes()","cell().index()",function(){return this.iterator("cell",function(a,b,c){return{row:b,column:c,columnVisible:$(a,c)}},1)});v("cells().invalidate()","cell().invalidate()",function(a){return this.iterator("cell",function(b,c,e){ca(b,c,a,e)})});r("cell()",
function(a,b,c){return bb(this.cells(a,b,c))});r("cell().data()",function(a){var b=this.context,c=this[0];if(a===k)return b.length&&c.length?x(b[0],c[0].row,c[0].column):k;Ia(b[0],c[0].row,c[0].column,a);ca(b[0],c[0].row,"data",c[0].column);return this});r("order()",function(a,b){var c=this.context;if(a===k)return 0!==c.length?c[0].aaSorting:k;"number"===typeof a?a=[[a,b]]:h.isArray(a[0])||(a=Array.prototype.slice.call(arguments));return this.iterator("table",function(b){b.aaSorting=a.slice()})});
r("order.listener()",function(a,b,c){return this.iterator("table",function(e){Oa(e,a,b,c)})});r(["columns().order()","column().order()"],function(a){var b=this;return this.iterator("table",function(c,e){var d=[];h.each(b[e],function(b,c){d.push([c,a])});c.aaSorting=d})});r("search()",function(a,b,c,e){var d=this.context;return a===k?0!==d.length?d[0].oPreviousSearch.sSearch:k:this.iterator("table",function(d){d.oFeatures.bFilter&&fa(d,h.extend({},d.oPreviousSearch,{sSearch:a+"",bRegex:null===b?!1:
b,bSmart:null===c?!0:c,bCaseInsensitive:null===e?!0:e}),1)})});v("columns().search()","column().search()",function(a,b,c,e){return this.iterator("column",function(d,f){var g=d.aoPreSearchCols;if(a===k)return g[f].sSearch;d.oFeatures.bFilter&&(h.extend(g[f],{sSearch:a+"",bRegex:null===b?!1:b,bSmart:null===c?!0:c,bCaseInsensitive:null===e?!0:e}),fa(d,d.oPreviousSearch,1))})});r("state()",function(){return this.context.length?this.context[0].oSavedState:null});r("state.clear()",function(){return this.iterator("table",
function(a){a.fnStateSaveCallback.call(a.oInstance,a,{})})});r("state.loaded()",function(){return this.context.length?this.context[0].oLoadedState:null});r("state.save()",function(){return this.iterator("table",function(a){ya(a)})});m.versionCheck=m.fnVersionCheck=function(a){for(var b=m.version.split("."),a=a.split("."),c,e,d=0,f=a.length;d<f;d++)if(c=parseInt(b[d],10)||0,e=parseInt(a[d],10)||0,c!==e)return c>e;return!0};m.isDataTable=m.fnIsDataTable=function(a){var b=h(a).get(0),c=!1;h.each(m.settings,
function(a,d){var f=d.nScrollHead?h("table",d.nScrollHead)[0]:null,g=d.nScrollFoot?h("table",d.nScrollFoot)[0]:null;if(d.nTable===b||f===b||g===b)c=!0});return c};m.tables=m.fnTables=function(a){return h.map(m.settings,function(b){if(!a||a&&h(b.nTable).is(":visible"))return b.nTable})};m.util={throttle:ua,escapeRegex:va};m.camelToHungarian=H;r("$()",function(a,b){var c=this.rows(b).nodes(),c=h(c);return h([].concat(c.filter(a).toArray(),c.find(a).toArray()))});h.each(["on","one","off"],function(a,
b){r(b+"()",function(){var a=Array.prototype.slice.call(arguments);a[0].match(/\.dt\b/)||(a[0]+=".dt");var e=h(this.tables().nodes());e[b].apply(e,a);return this})});r("clear()",function(){return this.iterator("table",function(a){oa(a)})});r("settings()",function(){return new t(this.context,this.context)});r("init()",function(){var a=this.context;return a.length?a[0].oInit:null});r("data()",function(){return this.iterator("table",function(a){return D(a.aoData,"_aData")}).flatten()});r("destroy()",
function(a){a=a||!1;return this.iterator("table",function(b){var c=b.nTableWrapper.parentNode,e=b.oClasses,d=b.nTable,f=b.nTBody,g=b.nTHead,j=b.nTFoot,i=h(d),f=h(f),k=h(b.nTableWrapper),l=h.map(b.aoData,function(a){return a.nTr}),q;b.bDestroying=!0;w(b,"aoDestroyCallback","destroy",[b]);a||(new t(b)).columns().visible(!0);k.unbind(".DT").find(":not(tbody *)").unbind(".DT");h(Ea).unbind(".DT-"+b.sInstance);d!=g.parentNode&&(i.children("thead").detach(),i.append(g));j&&d!=j.parentNode&&(i.children("tfoot").detach(),
i.append(j));i.detach();k.detach();b.aaSorting=[];b.aaSortingFixed=[];xa(b);h(l).removeClass(b.asStripeClasses.join(" "));h("th, td",g).removeClass(e.sSortable+" "+e.sSortableAsc+" "+e.sSortableDesc+" "+e.sSortableNone);b.bJUI&&(h("th span."+e.sSortIcon+", td span."+e.sSortIcon,g).detach(),h("th, td",g).each(function(){var a=h("div."+e.sSortJUIWrapper,this);h(this).append(a.contents());a.detach()}));!a&&c&&c.insertBefore(d,b.nTableReinsertBefore);f.children().detach();f.append(l);i.css("width",b.sDestroyWidth).removeClass(e.sTable);
(q=b.asDestroyStripes.length)&&f.children().each(function(a){h(this).addClass(b.asDestroyStripes[a%q])});c=h.inArray(b,m.settings);-1!==c&&m.settings.splice(c,1)})});h.each(["column","row","cell"],function(a,b){r(b+"s().every()",function(a){return this.iterator(b,function(e,d,f){a.call((new t(e))[b](d,f))})})});r("i18n()",function(a,b,c){var e=this.context[0],a=R(a)(e.oLanguage);a===k&&(a=b);c!==k&&h.isPlainObject(a)&&(a=a[c]!==k?a[c]:a._);return a.replace("%d",c)});m.version="1.10.7";m.settings=
[];m.models={};m.models.oSearch={bCaseInsensitive:!0,sSearch:"",bRegex:!1,bSmart:!0};m.models.oRow={nTr:null,anCells:null,_aData:[],_aSortData:null,_aFilterData:null,_sFilterRow:null,_sRowStripe:"",src:null};m.models.oColumn={idx:null,aDataSort:null,asSorting:null,bSearchable:null,bSortable:null,bVisible:null,_sManualType:null,_bAttrSrc:!1,fnCreatedCell:null,fnGetData:null,fnSetData:null,mData:null,mRender:null,nTh:null,nTf:null,sClass:null,sContentPadding:null,sDefaultContent:null,sName:null,sSortDataType:"std",
sSortingClass:null,sSortingClassJUI:null,sTitle:null,sType:null,sWidth:null,sWidthOrig:null};m.defaults={aaData:null,aaSorting:[[0,"asc"]],aaSortingFixed:[],ajax:null,aLengthMenu:[10,25,50,100],aoColumns:null,aoColumnDefs:null,aoSearchCols:[],asStripeClasses:null,bAutoWidth:!0,bDeferRender:!1,bDestroy:!1,bFilter:!0,bInfo:!0,bJQueryUI:!1,bLengthChange:!0,bPaginate:!0,bProcessing:!1,bRetrieve:!1,bScrollCollapse:!1,bServerSide:!1,bSort:!0,bSortMulti:!0,bSortCellsTop:!1,bSortClasses:!0,bStateSave:!1,
fnCreatedRow:null,fnDrawCallback:null,fnFooterCallback:null,fnFormatNumber:function(a){return a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,this.oLanguage.sThousands)},fnHeaderCallback:null,fnInfoCallback:null,fnInitComplete:null,fnPreDrawCallback:null,fnRowCallback:null,fnServerData:null,fnServerParams:null,fnStateLoadCallback:function(a){try{return JSON.parse((-1===a.iStateDuration?sessionStorage:localStorage).getItem("DataTables_"+a.sInstance+"_"+location.pathname))}catch(b){}},fnStateLoadParams:null,
fnStateLoaded:null,fnStateSaveCallback:function(a,b){try{(-1===a.iStateDuration?sessionStorage:localStorage).setItem("DataTables_"+a.sInstance+"_"+location.pathname,JSON.stringify(b))}catch(c){}},fnStateSaveParams:null,iStateDuration:7200,iDeferLoading:null,iDisplayLength:10,iDisplayStart:0,iTabIndex:0,oClasses:{},oLanguage:{oAria:{sSortAscending:": activate to sort column ascending",sSortDescending:": activate to sort column descending"},oPaginate:{sFirst:"First",sLast:"Last",sNext:"Next",sPrevious:"Previous"},
sEmptyTable:"No data available in table",sInfo:"Showing _START_ to _END_ of _TOTAL_ entries",sInfoEmpty:"Showing 0 to 0 of 0 entries",sInfoFiltered:"(filtered from _MAX_ total entries)",sInfoPostFix:"",sDecimal:"",sThousands:",",sLengthMenu:"Show _MENU_ entries",sLoadingRecords:"Loading...",sProcessing:"Processing...",sSearch:"Search:",sSearchPlaceholder:"",sUrl:"",sZeroRecords:"No matching records found"},oSearch:h.extend({},m.models.oSearch),sAjaxDataProp:"data",sAjaxSource:null,sDom:"lfrtip",searchDelay:null,
sPaginationType:"simple_numbers",sScrollX:"",sScrollXInner:"",sScrollY:"",sServerMethod:"GET",renderer:null};W(m.defaults);m.defaults.column={aDataSort:null,iDataSort:-1,asSorting:["asc","desc"],bSearchable:!0,bSortable:!0,bVisible:!0,fnCreatedCell:null,mData:null,mRender:null,sCellType:"td",sClass:"",sContentPadding:"",sDefaultContent:null,sName:"",sSortDataType:"std",sTitle:null,sType:null,sWidth:null};W(m.defaults.column);m.models.oSettings={oFeatures:{bAutoWidth:null,bDeferRender:null,bFilter:null,
bInfo:null,bLengthChange:null,bPaginate:null,bProcessing:null,bServerSide:null,bSort:null,bSortMulti:null,bSortClasses:null,bStateSave:null},oScroll:{bCollapse:null,iBarWidth:0,sX:null,sXInner:null,sY:null},oLanguage:{fnInfoCallback:null},oBrowser:{bScrollOversize:!1,bScrollbarLeft:!1},ajax:null,aanFeatures:[],aoData:[],aiDisplay:[],aiDisplayMaster:[],aoColumns:[],aoHeader:[],aoFooter:[],oPreviousSearch:{},aoPreSearchCols:[],aaSorting:null,aaSortingFixed:[],asStripeClasses:null,asDestroyStripes:[],
sDestroyWidth:0,aoRowCallback:[],aoHeaderCallback:[],aoFooterCallback:[],aoDrawCallback:[],aoRowCreatedCallback:[],aoPreDrawCallback:[],aoInitComplete:[],aoStateSaveParams:[],aoStateLoadParams:[],aoStateLoaded:[],sTableId:"",nTable:null,nTHead:null,nTFoot:null,nTBody:null,nTableWrapper:null,bDeferLoading:!1,bInitialised:!1,aoOpenRows:[],sDom:null,searchDelay:null,sPaginationType:"two_button",iStateDuration:0,aoStateSave:[],aoStateLoad:[],oSavedState:null,oLoadedState:null,sAjaxSource:null,sAjaxDataProp:null,
bAjaxDataGet:!0,jqXHR:null,json:k,oAjaxData:k,fnServerData:null,aoServerParams:[],sServerMethod:null,fnFormatNumber:null,aLengthMenu:null,iDraw:0,bDrawing:!1,iDrawError:-1,_iDisplayLength:10,_iDisplayStart:0,_iRecordsTotal:0,_iRecordsDisplay:0,bJUI:null,oClasses:{},bFiltered:!1,bSorted:!1,bSortCellsTop:null,oInit:null,aoDestroyCallback:[],fnRecordsTotal:function(){return"ssp"==B(this)?1*this._iRecordsTotal:this.aiDisplayMaster.length},fnRecordsDisplay:function(){return"ssp"==B(this)?1*this._iRecordsDisplay:
this.aiDisplay.length},fnDisplayEnd:function(){var a=this._iDisplayLength,b=this._iDisplayStart,c=b+a,e=this.aiDisplay.length,d=this.oFeatures,f=d.bPaginate;return d.bServerSide?!1===f||-1===a?b+e:Math.min(b+a,this._iRecordsDisplay):!f||c>e||-1===a?e:c},oInstance:null,sInstance:null,iTabIndex:0,nScrollHead:null,nScrollFoot:null,aLastSort:[],oPlugins:{}};m.ext=u={buttons:{},classes:{},errMode:"alert",feature:[],search:[],selector:{cell:[],column:[],row:[]},internal:{},legacy:{ajax:null},pager:{},renderer:{pageButton:{},
header:{}},order:{},type:{detect:[],search:{},order:{}},_unique:0,fnVersionCheck:m.fnVersionCheck,iApiIndex:0,oJUIClasses:{},sVersion:m.version};h.extend(u,{afnFiltering:u.search,aTypes:u.type.detect,ofnSearch:u.type.search,oSort:u.type.order,afnSortData:u.order,aoFeatures:u.feature,oApi:u.internal,oStdClasses:u.classes,oPagination:u.pager});h.extend(m.ext.classes,{sTable:"dataTable",sNoFooter:"no-footer",sPageButton:"paginate_button",sPageButtonActive:"current",sPageButtonDisabled:"disabled",sStripeOdd:"odd",
sStripeEven:"even",sRowEmpty:"dataTables_empty",sWrapper:"dataTables_wrapper",sFilter:"dataTables_filter",sInfo:"dataTables_info",sPaging:"dataTables_paginate paging_",sLength:"dataTables_length",sProcessing:"dataTables_processing",sSortAsc:"sorting_asc",sSortDesc:"sorting_desc",sSortable:"sorting",sSortableAsc:"sorting_asc_disabled",sSortableDesc:"sorting_desc_disabled",sSortableNone:"sorting_disabled",sSortColumn:"sorting_",sFilterInput:"",sLengthSelect:"",sScrollWrapper:"dataTables_scroll",sScrollHead:"dataTables_scrollHead",
sScrollHeadInner:"dataTables_scrollHeadInner",sScrollBody:"dataTables_scrollBody",sScrollFoot:"dataTables_scrollFoot",sScrollFootInner:"dataTables_scrollFootInner",sHeaderTH:"",sFooterTH:"",sSortJUIAsc:"",sSortJUIDesc:"",sSortJUI:"",sSortJUIAscAllowed:"",sSortJUIDescAllowed:"",sSortJUIWrapper:"",sSortIcon:"",sJUIHeader:"",sJUIFooter:""});var Da="",Da="",F=Da+"ui-state-default",ja=Da+"css_right ui-icon ui-icon-",Xb=Da+"fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix";h.extend(m.ext.oJUIClasses,
m.ext.classes,{sPageButton:"fg-button ui-button "+F,sPageButtonActive:"ui-state-disabled",sPageButtonDisabled:"ui-state-disabled",sPaging:"dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi ui-buttonset-multi paging_",sSortAsc:F+" sorting_asc",sSortDesc:F+" sorting_desc",sSortable:F+" sorting",sSortableAsc:F+" sorting_asc_disabled",sSortableDesc:F+" sorting_desc_disabled",sSortableNone:F+" sorting_disabled",sSortJUIAsc:ja+"triangle-1-n",sSortJUIDesc:ja+"triangle-1-s",sSortJUI:ja+"carat-2-n-s",
sSortJUIAscAllowed:ja+"carat-1-n",sSortJUIDescAllowed:ja+"carat-1-s",sSortJUIWrapper:"DataTables_sort_wrapper",sSortIcon:"DataTables_sort_icon",sScrollHead:"dataTables_scrollHead "+F,sScrollFoot:"dataTables_scrollFoot "+F,sHeaderTH:F,sFooterTH:F,sJUIHeader:Xb+" ui-corner-tl ui-corner-tr",sJUIFooter:Xb+" ui-corner-bl ui-corner-br"});var Mb=m.ext.pager;h.extend(Mb,{simple:function(){return["previous","next"]},full:function(){return["first","previous","next","last"]},simple_numbers:function(a,b){return["previous",
Wa(a,b),"next"]},full_numbers:function(a,b){return["first","previous",Wa(a,b),"next","last"]},_numbers:Wa,numbers_length:7});h.extend(!0,m.ext.renderer,{pageButton:{_:function(a,b,c,e,d,f){var g=a.oClasses,j=a.oLanguage.oPaginate,i,k,l=0,m=function(b,e){var n,r,t,s,u=function(b){Ta(a,b.data.action,true)};n=0;for(r=e.length;n<r;n++){s=e[n];if(h.isArray(s)){t=h("<"+(s.DT_el||"div")+"/>").appendTo(b);m(t,s)}else{k=i="";switch(s){case "ellipsis":b.append('<span class="ellipsis">&#x2026;</span>');break;
case "first":i=j.sFirst;k=s+(d>0?"":" "+g.sPageButtonDisabled);break;case "previous":i=j.sPrevious;k=s+(d>0?"":" "+g.sPageButtonDisabled);break;case "next":i=j.sNext;k=s+(d<f-1?"":" "+g.sPageButtonDisabled);break;case "last":i=j.sLast;k=s+(d<f-1?"":" "+g.sPageButtonDisabled);break;default:i=s+1;k=d===s?g.sPageButtonActive:""}if(i){t=h("<a>",{"class":g.sPageButton+" "+k,"aria-controls":a.sTableId,"data-dt-idx":l,tabindex:a.iTabIndex,id:c===0&&typeof s==="string"?a.sTableId+"_"+s:null}).html(i).appendTo(b);
Va(t,{action:s},u);l++}}}},n;try{n=h(Q.activeElement).data("dt-idx")}catch(r){}m(h(b).empty(),e);n&&h(b).find("[data-dt-idx="+n+"]").focus()}}});h.extend(m.ext.type.detect,[function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c)?"num"+c:null},function(a){if(a&&!(a instanceof Date)&&(!ac.test(a)||!bc.test(a)))return null;var b=Date.parse(a);return null!==b&&!isNaN(b)||J(a)?"date":null},function(a,b){var c=b.oLanguage.sDecimal;return Za(a,c,!0)?"num-fmt"+c:null},function(a,b){var c=b.oLanguage.sDecimal;
return Rb(a,c)?"html-num"+c:null},function(a,b){var c=b.oLanguage.sDecimal;return Rb(a,c,!0)?"html-num-fmt"+c:null},function(a){return J(a)||"string"===typeof a&&-1!==a.indexOf("<")?"html":null}]);h.extend(m.ext.type.search,{html:function(a){return J(a)?a:"string"===typeof a?a.replace(Ob," ").replace(Ba,""):""},string:function(a){return J(a)?a:"string"===typeof a?a.replace(Ob," "):a}});var Aa=function(a,b,c,e){if(0!==a&&(!a||"-"===a))return-Infinity;b&&(a=Qb(a,b));a.replace&&(c&&(a=a.replace(c,"")),
e&&(a=a.replace(e,"")));return 1*a};h.extend(u.type.order,{"date-pre":function(a){return Date.parse(a)||0},"html-pre":function(a){return J(a)?"":a.replace?a.replace(/<.*?>/g,"").toLowerCase():a+""},"string-pre":function(a){return J(a)?"":"string"===typeof a?a.toLowerCase():!a.toString?"":a.toString()},"string-asc":function(a,b){return a<b?-1:a>b?1:0},"string-desc":function(a,b){return a<b?1:a>b?-1:0}});db("");h.extend(!0,m.ext.renderer,{header:{_:function(a,b,c,e){h(a.nTable).on("order.dt.DT",function(d,
f,g,h){if(a===f){d=c.idx;b.removeClass(c.sSortingClass+" "+e.sSortAsc+" "+e.sSortDesc).addClass(h[d]=="asc"?e.sSortAsc:h[d]=="desc"?e.sSortDesc:c.sSortingClass)}})},jqueryui:function(a,b,c,e){h("<div/>").addClass(e.sSortJUIWrapper).append(b.contents()).append(h("<span/>").addClass(e.sSortIcon+" "+c.sSortingClassJUI)).appendTo(b);h(a.nTable).on("order.dt.DT",function(d,f,g,h){if(a===f){d=c.idx;b.removeClass(e.sSortAsc+" "+e.sSortDesc).addClass(h[d]=="asc"?e.sSortAsc:h[d]=="desc"?e.sSortDesc:c.sSortingClass);
b.find("span."+e.sSortIcon).removeClass(e.sSortJUIAsc+" "+e.sSortJUIDesc+" "+e.sSortJUI+" "+e.sSortJUIAscAllowed+" "+e.sSortJUIDescAllowed).addClass(h[d]=="asc"?e.sSortJUIAsc:h[d]=="desc"?e.sSortJUIDesc:c.sSortingClassJUI)}})}}});m.render={number:function(a,b,c,e){return{display:function(d){if("number"!==typeof d&&"string"!==typeof d)return d;var f=0>d?"-":"",d=Math.abs(parseFloat(d)),g=parseInt(d,10),d=c?b+(d-g).toFixed(c).substring(2):"";return f+(e||"")+g.toString().replace(/\B(?=(\d{3})+(?!\d))/g,
a)+d}}}};h.extend(m.ext.internal,{_fnExternApiFunc:Nb,_fnBuildAjax:ra,_fnAjaxUpdate:kb,_fnAjaxParameters:tb,_fnAjaxUpdateDraw:ub,_fnAjaxDataSrc:sa,_fnAddColumn:Fa,_fnColumnOptions:ka,_fnAdjustColumnSizing:X,_fnVisibleToColumnIndex:la,_fnColumnIndexToVisible:$,_fnVisbleColumns:aa,_fnGetColumns:Z,_fnColumnTypes:Ha,_fnApplyColumnDefs:ib,_fnHungarianMap:W,_fnCamelToHungarian:H,_fnLanguageCompat:P,_fnBrowserDetect:gb,_fnAddData:K,_fnAddTr:ma,_fnNodeToDataIndex:function(a,b){return b._DT_RowIndex!==k?b._DT_RowIndex:
null},_fnNodeToColumnIndex:function(a,b,c){return h.inArray(c,a.aoData[b].anCells)},_fnGetCellData:x,_fnSetCellData:Ia,_fnSplitObjNotation:Ka,_fnGetObjectDataFn:R,_fnSetObjectDataFn:S,_fnGetDataMaster:La,_fnClearTable:oa,_fnDeleteIndex:pa,_fnInvalidate:ca,_fnGetRowElements:na,_fnCreateTr:Ja,_fnBuildHead:jb,_fnDrawHead:ea,_fnDraw:M,_fnReDraw:N,_fnAddOptionsHtml:mb,_fnDetectHeader:da,_fnGetUniqueThs:qa,_fnFeatureHtmlFilter:ob,_fnFilterComplete:fa,_fnFilterCustom:xb,_fnFilterColumn:wb,_fnFilter:vb,_fnFilterCreateSearch:Qa,
_fnEscapeRegex:va,_fnFilterData:yb,_fnFeatureHtmlInfo:rb,_fnUpdateInfo:Bb,_fnInfoMacros:Cb,_fnInitialise:ga,_fnInitComplete:ta,_fnLengthChange:Ra,_fnFeatureHtmlLength:nb,_fnFeatureHtmlPaginate:sb,_fnPageChange:Ta,_fnFeatureHtmlProcessing:pb,_fnProcessingDisplay:C,_fnFeatureHtmlTable:qb,_fnScrollDraw:Y,_fnApplyToChildren:G,_fnCalculateColumnWidths:Ga,_fnThrottle:ua,_fnConvertToWidth:Db,_fnScrollingWidthAdjust:Fb,_fnGetWidestNode:Eb,_fnGetMaxLenString:Gb,_fnStringToCss:s,_fnScrollBarWidth:Hb,_fnSortFlatten:U,
_fnSort:lb,_fnSortAria:Jb,_fnSortListener:Ua,_fnSortAttachListener:Oa,_fnSortingClasses:xa,_fnSortData:Ib,_fnSaveState:ya,_fnLoadState:Kb,_fnSettingsFromNode:za,_fnLog:I,_fnMap:E,_fnBindAction:Va,_fnCallbackReg:z,_fnCallbackFire:w,_fnLengthOverflow:Sa,_fnRenderer:Pa,_fnDataSource:B,_fnRowAttributes:Ma,_fnCalculateEnd:function(){}});h.fn.dataTable=m;h.fn.dataTableSettings=m.settings;h.fn.dataTableExt=m.ext;h.fn.DataTable=function(a){return h(this).dataTable(a).api()};h.each(m,function(a,b){h.fn.DataTable[a]=
b});return h.fn.dataTable};"function"===typeof define&&define.amd?define("datatables",["jquery"],P):"object"===typeof exports?module.exports=P(require("jquery")):jQuery&&!jQuery.fn.dataTable&&P(jQuery)})(window,document);
/*!
 * Bootstrap v3.3.7 (http://getbootstrap.com)
 * Copyright 2011-2016 Twitter, Inc.
 * Licensed under the MIT license
 */

if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1||b[0]>3)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher, but lower than version 4")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){if(a(b.target).is(this))return b.handleObj.handler.apply(this,arguments)}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.7",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a("#"===f?[]:f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.7",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c).prop(c,!0)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c).prop(c,!1))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")?(c.prop("checked")&&(a=!1),b.find(".active").removeClass("active"),this.$element.addClass("active")):"checkbox"==c.prop("type")&&(c.prop("checked")!==this.$element.hasClass("active")&&(a=!1),this.$element.toggleClass("active")),c.prop("checked",this.$element.hasClass("active")),a&&c.trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active")),this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target).closest(".btn");b.call(d,"toggle"),a(c.target).is('input[type="radio"], input[type="checkbox"]')||(c.preventDefault(),d.is("input,button")?d.trigger("focus"):d.find("input:visible,button:visible").first().trigger("focus"))}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus",/^focus(in)?$/.test(b.type))})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=null,this.sliding=null,this.interval=null,this.$active=null,this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){if(!/input|textarea/i.test(a.target.tagName)){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()}},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c=this.getItemIndex(b),d="prev"==a&&0===c||"next"==a&&c==this.$items.length-1;if(d&&!this.options.wrap)return b;var e="prev"==a?-1:1,f=(c+e)%this.$items.length;return this.$items.eq(f)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));if(!(a>this.$items.length-1||a<0))return this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){if(!this.sliding)return this.slide("next")},c.prototype.prev=function(){if(!this.sliding)return this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i=this;if(f.hasClass("active"))return this.sliding=!1;var j=f[0],k=a.Event("slide.bs.carousel",{relatedTarget:j,direction:h});if(this.$element.trigger(k),!k.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var l=a(this.$indicators.children()[this.getItemIndex(f)]);l&&l.addClass("active")}var m=a.Event("slid.bs.carousel",{relatedTarget:j,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger(m)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(m)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&/show|hide/.test(b)&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a('[data-toggle="collapse"][href="#'+b.id+'"],[data-toggle="collapse"][data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.7",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.children(".panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":e.data();c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function c(c){c&&3===c.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=b(d),f={relatedTarget:this};e.hasClass("open")&&(c&&"click"==c.type&&/input|textarea/i.test(c.target.tagName)&&a.contains(e[0],c.target)||(e.trigger(c=a.Event("hide.bs.dropdown",f)),c.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger(a.Event("hidden.bs.dropdown",f)))))}))}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.7",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=b(e),g=f.hasClass("open");if(c(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(document.createElement("div")).addClass("dropdown-backdrop").insertAfter(a(this)).on("click",c);var h={relatedTarget:this};if(f.trigger(d=a.Event("show.bs.dropdown",h)),d.isDefaultPrevented())return;e.trigger("focus").attr("aria-expanded","true"),f.toggleClass("open").trigger(a.Event("shown.bs.dropdown",h))}return!1}},g.prototype.keydown=function(c){if(/(38|40|27|32)/.test(c.which)&&!/input|textarea/i.test(c.target.tagName)){var d=a(this);if(c.preventDefault(),c.stopPropagation(),!d.is(".disabled, :disabled")){var e=b(d),g=e.hasClass("open");if(!g&&27!=c.which||g&&27==c.which)return 27==c.which&&e.find(f).trigger("focus"),d.trigger("click");var h=" li:not(.disabled):visible a",i=e.find(".dropdown-menu"+h);if(i.length){var j=i.index(c.target);38==c.which&&j>0&&j--,40==c.which&&j<i.length-1&&j++,~j||(j=0),i.eq(j).trigger("focus")}}}};var h=a.fn.dropdown;a.fn.dropdown=d,a.fn.dropdown.Constructor=g,a.fn.dropdown.noConflict=function(){return a.fn.dropdown=h,this},a(document).on("click.bs.dropdown.data-api",c).on("click.bs.dropdown.data-api",".dropdown form",function(a){a.stopPropagation()}).on("click.bs.dropdown.data-api",f,g.prototype.toggle).on("keydown.bs.dropdown.data-api",f,g.prototype.keydown).on("keydown.bs.dropdown.data-api",".dropdown-menu",g.prototype.keydown)}(jQuery),+function(a){"use strict";function b(b,d){return this.each(function(){var e=a(this),f=e.data("bs.modal"),g=a.extend({},c.DEFAULTS,e.data(),"object"==typeof b&&b);f||e.data("bs.modal",f=new c(this,g)),"string"==typeof b?f[b](d):g.show&&f.show(d)})}var c=function(b,c){this.options=c,this.$body=a(document.body),this.$element=a(b),this.$dialog=this.$element.find(".modal-dialog"),this.$backdrop=null,this.isShown=null,this.originalBodyPad=null,this.scrollbarWidth=0,this.ignoreBackdropClick=!1,this.options.remote&&this.$element.find(".modal-content").load(this.options.remote,a.proxy(function(){this.$element.trigger("loaded.bs.modal")},this))};c.VERSION="3.3.7",c.TRANSITION_DURATION=300,c.BACKDROP_TRANSITION_DURATION=150,c.DEFAULTS={backdrop:!0,keyboard:!0,show:!0},c.prototype.toggle=function(a){return this.isShown?this.hide():this.show(a)},c.prototype.show=function(b){var d=this,e=a.Event("show.bs.modal",{relatedTarget:b});this.$element.trigger(e),this.isShown||e.isDefaultPrevented()||(this.isShown=!0,this.checkScrollbar(),this.setScrollbar(),this.$body.addClass("modal-open"),this.escape(),this.resize(),this.$element.on("click.dismiss.bs.modal",'[data-dismiss="modal"]',a.proxy(this.hide,this)),this.$dialog.on("mousedown.dismiss.bs.modal",function(){d.$element.one("mouseup.dismiss.bs.modal",function(b){a(b.target).is(d.$element)&&(d.ignoreBackdropClick=!0)})}),this.backdrop(function(){var e=a.support.transition&&d.$element.hasClass("fade");d.$element.parent().length||d.$element.appendTo(d.$body),d.$element.show().scrollTop(0),d.adjustDialog(),e&&d.$element[0].offsetWidth,d.$element.addClass("in"),d.enforceFocus();var f=a.Event("shown.bs.modal",{relatedTarget:b});e?d.$dialog.one("bsTransitionEnd",function(){d.$element.trigger("focus").trigger(f)}).emulateTransitionEnd(c.TRANSITION_DURATION):d.$element.trigger("focus").trigger(f)}))},c.prototype.hide=function(b){b&&b.preventDefault(),b=a.Event("hide.bs.modal"),this.$element.trigger(b),this.isShown&&!b.isDefaultPrevented()&&(this.isShown=!1,this.escape(),this.resize(),a(document).off("focusin.bs.modal"),this.$element.removeClass("in").off("click.dismiss.bs.modal").off("mouseup.dismiss.bs.modal"),this.$dialog.off("mousedown.dismiss.bs.modal"),a.support.transition&&this.$element.hasClass("fade")?this.$element.one("bsTransitionEnd",a.proxy(this.hideModal,this)).emulateTransitionEnd(c.TRANSITION_DURATION):this.hideModal())},c.prototype.enforceFocus=function(){a(document).off("focusin.bs.modal").on("focusin.bs.modal",a.proxy(function(a){document===a.target||this.$element[0]===a.target||this.$element.has(a.target).length||this.$element.trigger("focus")},this))},c.prototype.escape=function(){this.isShown&&this.options.keyboard?this.$element.on("keydown.dismiss.bs.modal",a.proxy(function(a){27==a.which&&this.hide()},this)):this.isShown||this.$element.off("keydown.dismiss.bs.modal")},c.prototype.resize=function(){this.isShown?a(window).on("resize.bs.modal",a.proxy(this.handleUpdate,this)):a(window).off("resize.bs.modal")},c.prototype.hideModal=function(){var a=this;this.$element.hide(),this.backdrop(function(){a.$body.removeClass("modal-open"),a.resetAdjustments(),a.resetScrollbar(),a.$element.trigger("hidden.bs.modal")})},c.prototype.removeBackdrop=function(){this.$backdrop&&this.$backdrop.remove(),this.$backdrop=null},c.prototype.backdrop=function(b){var d=this,e=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var f=a.support.transition&&e;if(this.$backdrop=a(document.createElement("div")).addClass("modal-backdrop "+e).appendTo(this.$body),this.$element.on("click.dismiss.bs.modal",a.proxy(function(a){return this.ignoreBackdropClick?void(this.ignoreBackdropClick=!1):void(a.target===a.currentTarget&&("static"==this.options.backdrop?this.$element[0].focus():this.hide()))},this)),f&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),!b)return;f?this.$backdrop.one("bsTransitionEnd",b).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):b()}else if(!this.isShown&&this.$backdrop){this.$backdrop.removeClass("in");var g=function(){d.removeBackdrop(),b&&b()};a.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one("bsTransitionEnd",g).emulateTransitionEnd(c.BACKDROP_TRANSITION_DURATION):g()}else b&&b()},c.prototype.handleUpdate=function(){this.adjustDialog()},c.prototype.adjustDialog=function(){var a=this.$element[0].scrollHeight>document.documentElement.clientHeight;this.$element.css({paddingLeft:!this.bodyIsOverflowing&&a?this.scrollbarWidth:"",paddingRight:this.bodyIsOverflowing&&!a?this.scrollbarWidth:""})},c.prototype.resetAdjustments=function(){this.$element.css({paddingLeft:"",paddingRight:""})},c.prototype.checkScrollbar=function(){var a=window.innerWidth;if(!a){var b=document.documentElement.getBoundingClientRect();a=b.right-Math.abs(b.left)}this.bodyIsOverflowing=document.body.clientWidth<a,this.scrollbarWidth=this.measureScrollbar()},c.prototype.setScrollbar=function(){var a=parseInt(this.$body.css("padding-right")||0,10);this.originalBodyPad=document.body.style.paddingRight||"",this.bodyIsOverflowing&&this.$body.css("padding-right",a+this.scrollbarWidth)},c.prototype.resetScrollbar=function(){this.$body.css("padding-right",this.originalBodyPad)},c.prototype.measureScrollbar=function(){var a=document.createElement("div");a.className="modal-scrollbar-measure",this.$body.append(a);var b=a.offsetWidth-a.clientWidth;return this.$body[0].removeChild(a),b};var d=a.fn.modal;a.fn.modal=b,a.fn.modal.Constructor=c,a.fn.modal.noConflict=function(){return a.fn.modal=d,this},a(document).on("click.bs.modal.data-api",'[data-toggle="modal"]',function(c){var d=a(this),e=d.attr("href"),f=a(d.attr("data-target")||e&&e.replace(/.*(?=#[^\s]+$)/,"")),g=f.data("bs.modal")?"toggle":a.extend({remote:!/#/.test(e)&&e},f.data(),d.data());d.is("a")&&c.preventDefault(),f.one("show.bs.modal",function(a){a.isDefaultPrevented()||f.one("hidden.bs.modal",function(){d.is(":visible")&&d.trigger("focus")})}),b.call(f,g,this)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tooltip"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.tooltip",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.type=null,this.options=null,this.enabled=null,this.timeout=null,this.hoverState=null,this.$element=null,this.inState=null,this.init("tooltip",a,b)};c.VERSION="3.3.7",c.TRANSITION_DURATION=150,c.DEFAULTS={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);if(c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),!c.isInStateTrue())return clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide()},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-m<o.top?"bottom":"right"==h&&k.right+l>o.width?"left":"left"==h&&k.left-l<o.left?"right":h,f.removeClass(n).addClass(h)}var p=this.getCalculatedOffset(h,k,l,m);this.applyPlacement(p,h);var q=function(){var a=e.hoverState;e.$element.trigger("shown.bs."+e.type),e.hoverState=null,"out"==a&&e.leave(e)};a.support.transition&&this.$tip.hasClass("fade")?f.one("bsTransitionEnd",q).emulateTransitionEnd(c.TRANSITION_DURATION):q()}},c.prototype.applyPlacement=function(b,c){var d=this.tip(),e=d[0].offsetWidth,f=d[0].offsetHeight,g=parseInt(d.css("margin-top"),10),h=parseInt(d.css("margin-left"),10);isNaN(g)&&(g=0),isNaN(h)&&(h=0),b.top+=g,b.left+=h,a.offset.setOffset(d[0],a.extend({using:function(a){d.css({top:Math.round(a.top),left:Math.round(a.left)})}},b),0),d.addClass("in");var i=d[0].offsetWidth,j=d[0].offsetHeight;"top"==c&&j!=f&&(b.top=b.top+f-j);var k=this.getViewportAdjustedDelta(c,b,i,j);k.left?b.left+=k.left:b.top+=k.top;var l=/top|bottom/.test(c),m=l?2*k.left-e+i:2*k.top-f+j,n=l?"offsetWidth":"offsetHeight";d.offset(b),this.replaceArrow(m,d[0][n],l)},c.prototype.replaceArrow=function(a,b,c){this.arrow().css(c?"left":"top",50*(1-a/b)+"%").css(c?"top":"left","")},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle();a.find(".tooltip-inner")[this.options.html?"html":"text"](b),a.removeClass("fade in top bottom left right")},c.prototype.hide=function(b){function d(){"in"!=e.hoverState&&f.detach(),e.$element&&e.$element.removeAttr("aria-describedby").trigger("hidden.bs."+e.type),b&&b()}var e=this,f=a(this.$tip),g=a.Event("hide.bs."+this.type);if(this.$element.trigger(g),!g.isDefaultPrevented())return f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one("bsTransitionEnd",d).emulateTransitionEnd(c.TRANSITION_DURATION):d(),this.hoverState=null,this},c.prototype.fixTitle=function(){var a=this.$element;(a.attr("title")||"string"!=typeof a.attr("data-original-title"))&&a.attr("data-original-title",a.attr("title")||"").attr("title","")},c.prototype.hasContent=function(){return this.getTitle()},c.prototype.getPosition=function(b){b=b||this.$element;var c=b[0],d="BODY"==c.tagName,e=c.getBoundingClientRect();null==e.width&&(e=a.extend({},e,{width:e.right-e.left,height:e.bottom-e.top}));var f=window.SVGElement&&c instanceof window.SVGElement,g=d?{top:0,left:0}:f?null:b.offset(),h={scroll:d?document.documentElement.scrollTop||document.body.scrollTop:b.scrollTop()},i=d?{width:a(window).width(),height:a(window).height()}:null;return a.extend({},e,h,i,g)},c.prototype.getCalculatedOffset=function(a,b,c,d){return"bottom"==a?{top:b.top+b.height,left:b.left+b.width/2-c/2}:"top"==a?{top:b.top-d,left:b.left+b.width/2-c/2}:"left"==a?{top:b.top+b.height/2-d/2,left:b.left-c}:{top:b.top+b.height/2-d/2,left:b.left+b.width}},c.prototype.getViewportAdjustedDelta=function(a,b,c,d){var e={top:0,left:0};if(!this.$viewport)return e;var f=this.options.viewport&&this.options.viewport.padding||0,g=this.getPosition(this.$viewport);if(/right|left/.test(a)){var h=b.top-f-g.scroll,i=b.top+f-g.scroll+d;h<g.top?e.top=g.top-h:i>g.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;j<g.left?e.left=g.left-j:k>g.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null,a.$element=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;!e&&/destroy|hide/.test(b)||(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.7",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'<div class="popover" role="tooltip"><div class="arrow"></div><h3 class="popover-title"></h3><div class="popover-content"></div></div>'}),c.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),c.prototype.constructor=c,c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content").children().detach().end()[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},c.prototype.hasContent=function(){return this.getTitle()||this.getContent()},c.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")};var d=a.fn.popover;a.fn.popover=b,a.fn.popover.Constructor=c,a.fn.popover.noConflict=function(){return a.fn.popover=d,this}}(jQuery),+function(a){"use strict";function b(c,d){this.$body=a(document.body),this.$scrollElement=a(a(c).is(document.body)?window:c),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||"")+" .nav li > a",this.offsets=[],this.targets=[],this.activeTarget=null,this.scrollHeight=0,this.$scrollElement.on("scroll.bs.scrollspy",a.proxy(this.process,this)),this.refresh(),this.process()}function c(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})}b.VERSION="3.3.7",b.DEFAULTS={offset:10},b.prototype.getScrollHeight=function(){return this.$scrollElement[0].scrollHeight||Math.max(this.$body[0].scrollHeight,document.documentElement.scrollHeight)},b.prototype.refresh=function(){var b=this,c="offset",d=0;this.offsets=[],this.targets=[],this.scrollHeight=this.getScrollHeight(),a.isWindow(this.$scrollElement[0])||(c="position",d=this.$scrollElement.scrollTop()),this.$body.find(this.selector).map(function(){var b=a(this),e=b.data("target")||b.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[c]().top+d,e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){b.offsets.push(this[0]),b.targets.push(this[1])})},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.getScrollHeight(),d=this.options.offset+c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(this.scrollHeight!=c&&this.refresh(),b>=d)return g!=(a=f[f.length-1])&&this.activate(a);if(g&&b<e[0])return this.activeTarget=null,this.clear();for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(void 0===e[a+1]||b<e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){
this.activeTarget=b,this.clear();var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")},b.prototype.clear=function(){a(this.selector).parentsUntil(this.options.target,".active").removeClass("active")};var d=a.fn.scrollspy;a.fn.scrollspy=c,a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=d,this},a(window).on("load.bs.scrollspy.data-api",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);c.call(b,b.data())})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new c(this)),"string"==typeof b&&e[b]()})}var c=function(b){this.element=a(b)};c.VERSION="3.3.7",c.TRANSITION_DURATION=150,c.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a"),f=a.Event("hide.bs.tab",{relatedTarget:b[0]}),g=a.Event("show.bs.tab",{relatedTarget:e[0]});if(e.trigger(f),b.trigger(g),!g.isDefaultPrevented()&&!f.isDefaultPrevented()){var h=a(d);this.activate(b.closest("li"),c),this.activate(h,h.parent(),function(){e.trigger({type:"hidden.bs.tab",relatedTarget:b[0]}),b.trigger({type:"shown.bs.tab",relatedTarget:e[0]})})}}},c.prototype.activate=function(b,d,e){function f(){g.removeClass("active").find("> .dropdown-menu > .active").removeClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!1),b.addClass("active").find('[data-toggle="tab"]').attr("aria-expanded",!0),h?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu").length&&b.closest("li.dropdown").addClass("active").end().find('[data-toggle="tab"]').attr("aria-expanded",!0),e&&e()}var g=d.find("> .active"),h=e&&a.support.transition&&(g.length&&g.hasClass("fade")||!!d.find("> .fade").length);g.length&&h?g.one("bsTransitionEnd",f).emulateTransitionEnd(c.TRANSITION_DURATION):f(),g.removeClass("in")};var d=a.fn.tab;a.fn.tab=b,a.fn.tab.Constructor=c,a.fn.tab.noConflict=function(){return a.fn.tab=d,this};var e=function(c){c.preventDefault(),b.call(a(this),"show")};a(document).on("click.bs.tab.data-api",'[data-toggle="tab"]',e).on("click.bs.tab.data-api",'[data-toggle="pill"]',e)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof b&&b;e||d.data("bs.affix",e=new c(this,f)),"string"==typeof b&&e[b]()})}var c=function(b,d){this.options=a.extend({},c.DEFAULTS,d),this.$target=a(this.options.target).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(b),this.affixed=null,this.unpin=null,this.pinnedOffset=null,this.checkPosition()};c.VERSION="3.3.7",c.RESET="affix affix-top affix-bottom",c.DEFAULTS={offset:0,target:window},c.prototype.getState=function(a,b,c,d){var e=this.$target.scrollTop(),f=this.$element.offset(),g=this.$target.height();if(null!=c&&"top"==this.affixed)return e<c&&"top";if("bottom"==this.affixed)return null!=c?!(e+this.unpin<=f.top)&&"bottom":!(e+g<=a-d)&&"bottom";var h=null==this.affixed,i=h?e:f.top,j=h?g:b;return null!=c&&e<=c?"top":null!=d&&i+j>=a-d&&"bottom"},c.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(c.RESET).addClass("affix");var a=this.$target.scrollTop(),b=this.$element.offset();return this.pinnedOffset=b.top-a},c.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},c.prototype.checkPosition=function(){if(this.$element.is(":visible")){var b=this.$element.height(),d=this.options.offset,e=d.top,f=d.bottom,g=Math.max(a(document).height(),a(document.body).height());"object"!=typeof d&&(f=e=d),"function"==typeof e&&(e=d.top(this.$element)),"function"==typeof f&&(f=d.bottom(this.$element));var h=this.getState(g,b,e,f);if(this.affixed!=h){null!=this.unpin&&this.$element.css("top","");var i="affix"+(h?"-"+h:""),j=a.Event(i+".bs.affix");if(this.$element.trigger(j),j.isDefaultPrevented())return;this.affixed=h,this.unpin="bottom"==h?this.getPinnedOffset():null,this.$element.removeClass(c.RESET).addClass(i).trigger(i.replace("affix","affixed")+".bs.affix")}"bottom"==h&&this.$element.offset({top:g-b-f})}};var d=a.fn.affix;a.fn.affix=b,a.fn.affix.Constructor=c,a.fn.affix.noConflict=function(){return a.fn.affix=d,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var c=a(this),d=c.data();d.offset=d.offset||{},null!=d.offsetBottom&&(d.offset.bottom=d.offsetBottom),null!=d.offsetTop&&(d.offset.top=d.offsetTop),b.call(c,d)})})}(jQuery);
/*!
 DataTables Bootstrap 3 integration
 ©2011-2014 SpryMedia Ltd - datatables.net/license
*/

(function(){var f=function(c,b){c.extend(!0,b.defaults,{dom:"<'row'<'col-sm-6'l><'col-sm-6'f>><'row'<'col-sm-12'tr>><'row'<'col-sm-6'i><'col-sm-6'p>>",renderer:"bootstrap"});c.extend(b.ext.classes,{sWrapper:"dataTables_wrapper form-inline dt-bootstrap",sFilterInput:"form-control input-sm",sLengthSelect:"form-control input-sm"});b.ext.renderer.pageButton.bootstrap=function(g,f,p,k,h,l){var q=new b.Api(g),r=g.oClasses,i=g.oLanguage.oPaginate,d,e,o=function(b,f){var j,m,n,a,k=function(a){a.preventDefault();
c(a.currentTarget).hasClass("disabled")||q.page(a.data.action).draw(!1)};j=0;for(m=f.length;j<m;j++)if(a=f[j],c.isArray(a))o(b,a);else{e=d="";switch(a){case "ellipsis":d="&hellip;";e="disabled";break;case "first":d=i.sFirst;e=a+(0<h?"":" disabled");break;case "previous":d=i.sPrevious;e=a+(0<h?"":" disabled");break;case "next":d=i.sNext;e=a+(h<l-1?"":" disabled");break;case "last":d=i.sLast;e=a+(h<l-1?"":" disabled");break;default:d=a+1,e=h===a?"active":""}d&&(n=c("<li>",{"class":r.sPageButton+" "+
e,"aria-controls":g.sTableId,tabindex:g.iTabIndex,id:0===p&&"string"===typeof a?g.sTableId+"_"+a:null}).append(c("<a>",{href:"#"}).html(d)).appendTo(b),g.oApi._fnBindAction(n,{action:a},k))}};o(c(f).empty().html('<ul class="pagination"/>').children("ul"),k)};b.TableTools&&(c.extend(!0,b.TableTools.classes,{container:"DTTT btn-group",buttons:{normal:"btn btn-default",disabled:"disabled"},collection:{container:"DTTT_dropdown dropdown-menu",buttons:{normal:"",disabled:"disabled"}},print:{info:"DTTT_print_info"},
select:{row:"active"}}),c.extend(!0,b.TableTools.DEFAULTS.oTags,{collection:{container:"ul",button:"li",liner:"a"}}))};"function"===typeof define&&define.amd?define(["jquery","datatables"],f):"object"===typeof exports?f(require("jquery"),require("datatables")):jQuery&&f(jQuery,jQuery.fn.dataTable)})(window,document);
/*
 * metismenu - v1.1.3
 * Easy menu jQuery plugin for Twitter Bootstrap 3
 * https://github.com/onokumus/metisMenu
 *
 * Made by Osman Nuri Okumus
 * Under MIT License
 */

!function(a,b,c){function d(b,c){this.element=a(b),this.settings=a.extend({},f,c),this._defaults=f,this._name=e,this.init()}var e="metisMenu",f={toggle:!0,doubleTapToGo:!1};d.prototype={init:function(){var b=this.element,d=this.settings.toggle,f=this;this.isIE()<=9?(b.find("li.active").has("ul").children("ul").collapse("show"),b.find("li").not(".active").has("ul").children("ul").collapse("hide")):(b.find("li.active").has("ul").children("ul").addClass("collapse in"),b.find("li").not(".active").has("ul").children("ul").addClass("collapse")),f.settings.doubleTapToGo&&b.find("li.active").has("ul").children("a").addClass("doubleTapToGo"),b.find("li").has("ul").children("a").on("click."+e,function(b){return b.preventDefault(),f.settings.doubleTapToGo&&f.doubleTapToGo(a(this))&&"#"!==a(this).attr("href")&&""!==a(this).attr("href")?(b.stopPropagation(),void(c.location=a(this).attr("href"))):(a(this).parent("li").toggleClass("active").children("ul").collapse("toggle"),void(d&&a(this).parent("li").siblings().removeClass("active").children("ul.in").collapse("hide")))})},isIE:function(){for(var a,b=3,d=c.createElement("div"),e=d.getElementsByTagName("i");d.innerHTML="<!--[if gt IE "+ ++b+"]><i></i><![endif]-->",e[0];)return b>4?b:a},doubleTapToGo:function(a){var b=this.element;return a.hasClass("doubleTapToGo")?(a.removeClass("doubleTapToGo"),!0):a.parent().children("ul").length?(b.find(".doubleTapToGo").removeClass("doubleTapToGo"),a.addClass("doubleTapToGo"),!1):void 0},remove:function(){this.element.off("."+e),this.element.removeData(e)}},a.fn[e]=function(b){return this.each(function(){var c=a(this);c.data(e)&&c.data(e).remove(),c.data(e,new d(this,b))}),this}}(jQuery,window,document);
/* @license
morris.js v0.5.0
Copyright 2014 Olly Smith All rights reserved.
Licensed under the BSD-2-Clause License.
*/

(function(){var a,b,c,d,e=[].slice,f=function(a,b){return function(){return a.apply(b,arguments)}},g={}.hasOwnProperty,h=function(a,b){function c(){this.constructor=a}for(var d in b)g.call(b,d)&&(a[d]=b[d]);return c.prototype=b.prototype,a.prototype=new c,a.__super__=b.prototype,a},i=[].indexOf||function(a){for(var b=0,c=this.length;c>b;b++)if(b in this&&this[b]===a)return b;return-1};b=window.Morris={},a=jQuery,b.EventEmitter=function(){function a(){}return a.prototype.on=function(a,b){return null==this.handlers&&(this.handlers={}),null==this.handlers[a]&&(this.handlers[a]=[]),this.handlers[a].push(b),this},a.prototype.fire=function(){var a,b,c,d,f,g,h;if(c=arguments[0],a=2<=arguments.length?e.call(arguments,1):[],null!=this.handlers&&null!=this.handlers[c]){for(g=this.handlers[c],h=[],d=0,f=g.length;f>d;d++)b=g[d],h.push(b.apply(null,a));return h}},a}(),b.commas=function(a){var b,c,d,e;return null!=a?(d=0>a?"-":"",b=Math.abs(a),c=Math.floor(b).toFixed(0),d+=c.replace(/(?=(?:\d{3})+$)(?!^)/g,","),e=b.toString(),e.length>c.length&&(d+=e.slice(c.length)),d):"-"},b.pad2=function(a){return(10>a?"0":"")+a},b.Grid=function(c){function d(b){this.resizeHandler=f(this.resizeHandler,this);var c=this;if(this.el="string"==typeof b.element?a(document.getElementById(b.element)):a(b.element),null==this.el||0===this.el.length)throw new Error("Graph container element not found");"static"===this.el.css("position")&&this.el.css("position","relative"),this.options=a.extend({},this.gridDefaults,this.defaults||{},b),"string"==typeof this.options.units&&(this.options.postUnits=b.units),this.raphael=new Raphael(this.el[0]),this.elementWidth=null,this.elementHeight=null,this.dirty=!1,this.selectFrom=null,this.init&&this.init(),this.setData(this.options.data),this.el.bind("mousemove",function(a){var b,d,e,f,g;return d=c.el.offset(),g=a.pageX-d.left,c.selectFrom?(b=c.data[c.hitTest(Math.min(g,c.selectFrom))]._x,e=c.data[c.hitTest(Math.max(g,c.selectFrom))]._x,f=e-b,c.selectionRect.attr({x:b,width:f})):c.fire("hovermove",g,a.pageY-d.top)}),this.el.bind("mouseleave",function(){return c.selectFrom&&(c.selectionRect.hide(),c.selectFrom=null),c.fire("hoverout")}),this.el.bind("touchstart touchmove touchend",function(a){var b,d;return d=a.originalEvent.touches[0]||a.originalEvent.changedTouches[0],b=c.el.offset(),c.fire("hovermove",d.pageX-b.left,d.pageY-b.top)}),this.el.bind("click",function(a){var b;return b=c.el.offset(),c.fire("gridclick",a.pageX-b.left,a.pageY-b.top)}),this.options.rangeSelect&&(this.selectionRect=this.raphael.rect(0,0,0,this.el.innerHeight()).attr({fill:this.options.rangeSelectColor,stroke:!1}).toBack().hide(),this.el.bind("mousedown",function(a){var b;return b=c.el.offset(),c.startRange(a.pageX-b.left)}),this.el.bind("mouseup",function(a){var b;return b=c.el.offset(),c.endRange(a.pageX-b.left),c.fire("hovermove",a.pageX-b.left,a.pageY-b.top)})),this.options.resize&&a(window).bind("resize",function(){return null!=c.timeoutId&&window.clearTimeout(c.timeoutId),c.timeoutId=window.setTimeout(c.resizeHandler,100)}),this.el.css("-webkit-tap-highlight-color","rgba(0,0,0,0)"),this.postInit&&this.postInit()}return h(d,c),d.prototype.gridDefaults={dateFormat:null,axes:!0,grid:!0,gridLineColor:"#aaa",gridStrokeWidth:.5,gridTextColor:"#888",gridTextSize:12,gridTextFamily:"sans-serif",gridTextWeight:"normal",hideHover:!1,yLabelFormat:null,xLabelAngle:0,numLines:5,padding:25,parseTime:!0,postUnits:"",preUnits:"",ymax:"auto",ymin:"auto 0",goals:[],goalStrokeWidth:1,goalLineColors:["#666633","#999966","#cc6666","#663333"],events:[],eventStrokeWidth:1,eventLineColors:["#005a04","#ccffbb","#3a5f0b","#005502"],rangeSelect:null,rangeSelectColor:"#eef",resize:!1},d.prototype.setData=function(a,c){var d,e,f,g,h,i,j,k,l,m,n,o,p,q,r;return null==c&&(c=!0),this.options.data=a,null==a||0===a.length?(this.data=[],this.raphael.clear(),null!=this.hover&&this.hover.hide(),void 0):(o=this.cumulative?0:null,p=this.cumulative?0:null,this.options.goals.length>0&&(h=Math.min.apply(Math,this.options.goals),g=Math.max.apply(Math,this.options.goals),p=null!=p?Math.min(p,h):h,o=null!=o?Math.max(o,g):g),this.data=function(){var c,d,g;for(g=[],f=c=0,d=a.length;d>c;f=++c)j=a[f],i={src:j},i.label=j[this.options.xkey],this.options.parseTime?(i.x=b.parseDate(i.label),this.options.dateFormat?i.label=this.options.dateFormat(i.x):"number"==typeof i.label&&(i.label=new Date(i.label).toString())):(i.x=f,this.options.xLabelFormat&&(i.label=this.options.xLabelFormat(i))),l=0,i.y=function(){var a,b,c,d;for(c=this.options.ykeys,d=[],e=a=0,b=c.length;b>a;e=++a)n=c[e],q=j[n],"string"==typeof q&&(q=parseFloat(q)),null!=q&&"number"!=typeof q&&(q=null),null!=q&&(this.cumulative?l+=q:null!=o?(o=Math.max(q,o),p=Math.min(q,p)):o=p=q),this.cumulative&&null!=l&&(o=Math.max(l,o),p=Math.min(l,p)),d.push(q);return d}.call(this),g.push(i);return g}.call(this),this.options.parseTime&&(this.data=this.data.sort(function(a,b){return(a.x>b.x)-(b.x>a.x)})),this.xmin=this.data[0].x,this.xmax=this.data[this.data.length-1].x,this.events=[],this.options.events.length>0&&(this.events=this.options.parseTime?function(){var a,c,e,f;for(e=this.options.events,f=[],a=0,c=e.length;c>a;a++)d=e[a],f.push(b.parseDate(d));return f}.call(this):this.options.events,this.xmax=Math.max(this.xmax,Math.max.apply(Math,this.events)),this.xmin=Math.min(this.xmin,Math.min.apply(Math,this.events))),this.xmin===this.xmax&&(this.xmin-=1,this.xmax+=1),this.ymin=this.yboundary("min",p),this.ymax=this.yboundary("max",o),this.ymin===this.ymax&&(p&&(this.ymin-=1),this.ymax+=1),((r=this.options.axes)===!0||"both"===r||"y"===r||this.options.grid===!0)&&(this.options.ymax===this.gridDefaults.ymax&&this.options.ymin===this.gridDefaults.ymin?(this.grid=this.autoGridLines(this.ymin,this.ymax,this.options.numLines),this.ymin=Math.min(this.ymin,this.grid[0]),this.ymax=Math.max(this.ymax,this.grid[this.grid.length-1])):(k=(this.ymax-this.ymin)/(this.options.numLines-1),this.grid=function(){var a,b,c,d;for(d=[],m=a=b=this.ymin,c=this.ymax;k>0?c>=a:a>=c;m=a+=k)d.push(m);return d}.call(this))),this.dirty=!0,c?this.redraw():void 0)},d.prototype.yboundary=function(a,b){var c,d;return c=this.options["y"+a],"string"==typeof c?"auto"===c.slice(0,4)?c.length>5?(d=parseInt(c.slice(5),10),null==b?d:Math[a](b,d)):null!=b?b:0:parseInt(c,10):c},d.prototype.autoGridLines=function(a,b,c){var d,e,f,g,h,i,j,k,l;return h=b-a,l=Math.floor(Math.log(h)/Math.log(10)),j=Math.pow(10,l),e=Math.floor(a/j)*j,d=Math.ceil(b/j)*j,i=(d-e)/(c-1),1===j&&i>1&&Math.ceil(i)!==i&&(i=Math.ceil(i),d=e+i*(c-1)),0>e&&d>0&&(e=Math.floor(a/i)*i,d=Math.ceil(b/i)*i),1>i?(g=Math.floor(Math.log(i)/Math.log(10)),f=function(){var a,b;for(b=[],k=a=e;i>0?d>=a:a>=d;k=a+=i)b.push(parseFloat(k.toFixed(1-g)));return b}()):f=function(){var a,b;for(b=[],k=a=e;i>0?d>=a:a>=d;k=a+=i)b.push(k);return b}(),f},d.prototype._calc=function(){var a,b,c,d,e,f,g,h;return e=this.el.width(),c=this.el.height(),(this.elementWidth!==e||this.elementHeight!==c||this.dirty)&&(this.elementWidth=e,this.elementHeight=c,this.dirty=!1,this.left=this.options.padding,this.right=this.elementWidth-this.options.padding,this.top=this.options.padding,this.bottom=this.elementHeight-this.options.padding,((g=this.options.axes)===!0||"both"===g||"y"===g)&&(f=function(){var a,c,d,e;for(d=this.grid,e=[],a=0,c=d.length;c>a;a++)b=d[a],e.push(this.measureText(this.yAxisFormat(b)).width);return e}.call(this),this.left+=Math.max.apply(Math,f)),((h=this.options.axes)===!0||"both"===h||"x"===h)&&(a=function(){var a,b,c;for(c=[],d=a=0,b=this.data.length;b>=0?b>a:a>b;d=b>=0?++a:--a)c.push(this.measureText(this.data[d].text,-this.options.xLabelAngle).height);return c}.call(this),this.bottom-=Math.max.apply(Math,a)),this.width=Math.max(1,this.right-this.left),this.height=Math.max(1,this.bottom-this.top),this.dx=this.width/(this.xmax-this.xmin),this.dy=this.height/(this.ymax-this.ymin),this.calc)?this.calc():void 0},d.prototype.transY=function(a){return this.bottom-(a-this.ymin)*this.dy},d.prototype.transX=function(a){return 1===this.data.length?(this.left+this.right)/2:this.left+(a-this.xmin)*this.dx},d.prototype.redraw=function(){return this.raphael.clear(),this._calc(),this.drawGrid(),this.drawGoals(),this.drawEvents(),this.draw?this.draw():void 0},d.prototype.measureText=function(a,b){var c,d;return null==b&&(b=0),d=this.raphael.text(100,100,a).attr("font-size",this.options.gridTextSize).attr("font-family",this.options.gridTextFamily).attr("font-weight",this.options.gridTextWeight).rotate(b),c=d.getBBox(),d.remove(),c},d.prototype.yAxisFormat=function(a){return this.yLabelFormat(a)},d.prototype.yLabelFormat=function(a){return"function"==typeof this.options.yLabelFormat?this.options.yLabelFormat(a):""+this.options.preUnits+b.commas(a)+this.options.postUnits},d.prototype.drawGrid=function(){var a,b,c,d,e,f,g,h;if(this.options.grid!==!1||(e=this.options.axes)===!0||"both"===e||"y"===e){for(f=this.grid,h=[],c=0,d=f.length;d>c;c++)a=f[c],b=this.transY(a),((g=this.options.axes)===!0||"both"===g||"y"===g)&&this.drawYAxisLabel(this.left-this.options.padding/2,b,this.yAxisFormat(a)),this.options.grid?h.push(this.drawGridLine("M"+this.left+","+b+"H"+(this.left+this.width))):h.push(void 0);return h}},d.prototype.drawGoals=function(){var a,b,c,d,e,f,g;for(f=this.options.goals,g=[],c=d=0,e=f.length;e>d;c=++d)b=f[c],a=this.options.goalLineColors[c%this.options.goalLineColors.length],g.push(this.drawGoal(b,a));return g},d.prototype.drawEvents=function(){var a,b,c,d,e,f,g;for(f=this.events,g=[],c=d=0,e=f.length;e>d;c=++d)b=f[c],a=this.options.eventLineColors[c%this.options.eventLineColors.length],g.push(this.drawEvent(b,a));return g},d.prototype.drawGoal=function(a,b){return this.raphael.path("M"+this.left+","+this.transY(a)+"H"+this.right).attr("stroke",b).attr("stroke-width",this.options.goalStrokeWidth)},d.prototype.drawEvent=function(a,b){return this.raphael.path("M"+this.transX(a)+","+this.bottom+"V"+this.top).attr("stroke",b).attr("stroke-width",this.options.eventStrokeWidth)},d.prototype.drawYAxisLabel=function(a,b,c){return this.raphael.text(a,b,c).attr("font-size",this.options.gridTextSize).attr("font-family",this.options.gridTextFamily).attr("font-weight",this.options.gridTextWeight).attr("fill",this.options.gridTextColor).attr("text-anchor","end")},d.prototype.drawGridLine=function(a){return this.raphael.path(a).attr("stroke",this.options.gridLineColor).attr("stroke-width",this.options.gridStrokeWidth)},d.prototype.startRange=function(a){return this.hover.hide(),this.selectFrom=a,this.selectionRect.attr({x:a,width:0}).show()},d.prototype.endRange=function(a){var b,c;return this.selectFrom?(c=Math.min(this.selectFrom,a),b=Math.max(this.selectFrom,a),this.options.rangeSelect.call(this.el,{start:this.data[this.hitTest(c)].x,end:this.data[this.hitTest(b)].x}),this.selectFrom=null):void 0},d.prototype.resizeHandler=function(){return this.timeoutId=null,this.raphael.setSize(this.el.width(),this.el.height()),this.redraw()},d}(b.EventEmitter),b.parseDate=function(a){var b,c,d,e,f,g,h,i,j,k,l;return"number"==typeof a?a:(c=a.match(/^(\d+) Q(\d)$/),e=a.match(/^(\d+)-(\d+)$/),f=a.match(/^(\d+)-(\d+)-(\d+)$/),h=a.match(/^(\d+) W(\d+)$/),i=a.match(/^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+)(Z|([+-])(\d\d):?(\d\d))?$/),j=a.match(/^(\d+)-(\d+)-(\d+)[ T](\d+):(\d+):(\d+(\.\d+)?)(Z|([+-])(\d\d):?(\d\d))?$/),c?new Date(parseInt(c[1],10),3*parseInt(c[2],10)-1,1).getTime():e?new Date(parseInt(e[1],10),parseInt(e[2],10)-1,1).getTime():f?new Date(parseInt(f[1],10),parseInt(f[2],10)-1,parseInt(f[3],10)).getTime():h?(k=new Date(parseInt(h[1],10),0,1),4!==k.getDay()&&k.setMonth(0,1+(4-k.getDay()+7)%7),k.getTime()+6048e5*parseInt(h[2],10)):i?i[6]?(g=0,"Z"!==i[6]&&(g=60*parseInt(i[8],10)+parseInt(i[9],10),"+"===i[7]&&(g=0-g)),Date.UTC(parseInt(i[1],10),parseInt(i[2],10)-1,parseInt(i[3],10),parseInt(i[4],10),parseInt(i[5],10)+g)):new Date(parseInt(i[1],10),parseInt(i[2],10)-1,parseInt(i[3],10),parseInt(i[4],10),parseInt(i[5],10)).getTime():j?(l=parseFloat(j[6]),b=Math.floor(l),d=Math.round(1e3*(l-b)),j[8]?(g=0,"Z"!==j[8]&&(g=60*parseInt(j[10],10)+parseInt(j[11],10),"+"===j[9]&&(g=0-g)),Date.UTC(parseInt(j[1],10),parseInt(j[2],10)-1,parseInt(j[3],10),parseInt(j[4],10),parseInt(j[5],10)+g,b,d)):new Date(parseInt(j[1],10),parseInt(j[2],10)-1,parseInt(j[3],10),parseInt(j[4],10),parseInt(j[5],10),b,d).getTime()):new Date(parseInt(a,10),0,1).getTime())},b.Hover=function(){function c(c){null==c&&(c={}),this.options=a.extend({},b.Hover.defaults,c),this.el=a("<div class='"+this.options["class"]+"'></div>"),this.el.hide(),this.options.parent.append(this.el)}return c.defaults={"class":"morris-hover morris-default-style"},c.prototype.update=function(a,b,c){return a?(this.html(a),this.show(),this.moveTo(b,c)):this.hide()},c.prototype.html=function(a){return this.el.html(a)},c.prototype.moveTo=function(a,b){var c,d,e,f,g,h;return g=this.options.parent.innerWidth(),f=this.options.parent.innerHeight(),d=this.el.outerWidth(),c=this.el.outerHeight(),e=Math.min(Math.max(0,a-d/2),g-d),null!=b?(h=b-c-10,0>h&&(h=b+10,h+c>f&&(h=f/2-c/2))):h=f/2-c/2,this.el.css({left:e+"px",top:parseInt(h)+"px"})},c.prototype.show=function(){return this.el.show()},c.prototype.hide=function(){return this.el.hide()},c}(),b.Line=function(a){function c(a){return this.hilight=f(this.hilight,this),this.onHoverOut=f(this.onHoverOut,this),this.onHoverMove=f(this.onHoverMove,this),this.onGridClick=f(this.onGridClick,this),this instanceof b.Line?(c.__super__.constructor.call(this,a),void 0):new b.Line(a)}return h(c,a),c.prototype.init=function(){return"always"!==this.options.hideHover?(this.hover=new b.Hover({parent:this.el}),this.on("hovermove",this.onHoverMove),this.on("hoverout",this.onHoverOut),this.on("gridclick",this.onGridClick)):void 0},c.prototype.defaults={lineWidth:3,pointSize:4,lineColors:["#0b62a4","#7A92A3","#4da74d","#afd8f8","#edc240","#cb4b4b","#9440ed"],pointStrokeWidths:[1],pointStrokeColors:["#ffffff"],pointFillColors:[],smooth:!0,xLabels:"auto",xLabelFormat:null,xLabelMargin:24,hideHover:!1},c.prototype.calc=function(){return this.calcPoints(),this.generatePaths()},c.prototype.calcPoints=function(){var a,b,c,d,e,f;for(e=this.data,f=[],c=0,d=e.length;d>c;c++)a=e[c],a._x=this.transX(a.x),a._y=function(){var c,d,e,f;for(e=a.y,f=[],c=0,d=e.length;d>c;c++)b=e[c],null!=b?f.push(this.transY(b)):f.push(b);return f}.call(this),f.push(a._ymax=Math.min.apply(Math,[this.bottom].concat(function(){var c,d,e,f;for(e=a._y,f=[],c=0,d=e.length;d>c;c++)b=e[c],null!=b&&f.push(b);return f}())));return f},c.prototype.hitTest=function(a){var b,c,d,e,f;if(0===this.data.length)return null;for(f=this.data.slice(1),b=d=0,e=f.length;e>d&&(c=f[b],!(a<(c._x+this.data[b]._x)/2));b=++d);return b},c.prototype.onGridClick=function(a,b){var c;return c=this.hitTest(a),this.fire("click",c,this.data[c].src,a,b)},c.prototype.onHoverMove=function(a){var b;return b=this.hitTest(a),this.displayHoverForRow(b)},c.prototype.onHoverOut=function(){return this.options.hideHover!==!1?this.displayHoverForRow(null):void 0},c.prototype.displayHoverForRow=function(a){var b;return null!=a?((b=this.hover).update.apply(b,this.hoverContentForRow(a)),this.hilight(a)):(this.hover.hide(),this.hilight())},c.prototype.hoverContentForRow=function(a){var b,c,d,e,f,g,h;for(d=this.data[a],b="<div class='morris-hover-row-label'>"+d.label+"</div>",h=d.y,c=f=0,g=h.length;g>f;c=++f)e=h[c],b+="<div class='morris-hover-point' style='color: "+this.colorFor(d,c,"label")+"'>\n  "+this.options.labels[c]+":\n  "+this.yLabelFormat(e)+"\n</div>";return"function"==typeof this.options.hoverCallback&&(b=this.options.hoverCallback(a,this.options,b,d.src)),[b,d._x,d._ymax]},c.prototype.generatePaths=function(){var a,c,d,e;return this.paths=function(){var f,g,h,j;for(j=[],c=f=0,g=this.options.ykeys.length;g>=0?g>f:f>g;c=g>=0?++f:--f)e="boolean"==typeof this.options.smooth?this.options.smooth:(h=this.options.ykeys[c],i.call(this.options.smooth,h)>=0),a=function(){var a,b,e,f;for(e=this.data,f=[],a=0,b=e.length;b>a;a++)d=e[a],void 0!==d._y[c]&&f.push({x:d._x,y:d._y[c]});return f}.call(this),a.length>1?j.push(b.Line.createPath(a,e,this.bottom)):j.push(null);return j}.call(this)},c.prototype.draw=function(){var a;return((a=this.options.axes)===!0||"both"===a||"x"===a)&&this.drawXAxis(),this.drawSeries(),this.options.hideHover===!1?this.displayHoverForRow(this.data.length-1):void 0},c.prototype.drawXAxis=function(){var a,c,d,e,f,g,h,i,j,k,l=this;for(h=this.bottom+this.options.padding/2,f=null,e=null,a=function(a,b){var c,d,g,i,j;return c=l.drawXAxisLabel(l.transX(b),h,a),j=c.getBBox(),c.transform("r"+-l.options.xLabelAngle),d=c.getBBox(),c.transform("t0,"+d.height/2+"..."),0!==l.options.xLabelAngle&&(i=-.5*j.width*Math.cos(l.options.xLabelAngle*Math.PI/180),c.transform("t"+i+",0...")),d=c.getBBox(),(null==f||f>=d.x+d.width||null!=e&&e>=d.x)&&d.x>=0&&d.x+d.width<l.el.width()?(0!==l.options.xLabelAngle&&(g=1.25*l.options.gridTextSize/Math.sin(l.options.xLabelAngle*Math.PI/180),e=d.x-g),f=d.x-l.options.xLabelMargin):c.remove()},d=this.options.parseTime?1===this.data.length&&"auto"===this.options.xLabels?[[this.data[0].label,this.data[0].x]]:b.labelSeries(this.xmin,this.xmax,this.width,this.options.xLabels,this.options.xLabelFormat):function(){var a,b,c,d;for(c=this.data,d=[],a=0,b=c.length;b>a;a++)g=c[a],d.push([g.label,g.x]);return d}.call(this),d.reverse(),k=[],i=0,j=d.length;j>i;i++)c=d[i],k.push(a(c[0],c[1]));return k},c.prototype.drawSeries=function(){var a,b,c,d,e,f;for(this.seriesPoints=[],a=b=d=this.options.ykeys.length-1;0>=d?0>=b:b>=0;a=0>=d?++b:--b)this._drawLineFor(a);for(f=[],a=c=e=this.options.ykeys.length-1;0>=e?0>=c:c>=0;a=0>=e?++c:--c)f.push(this._drawPointFor(a));return f},c.prototype._drawPointFor=function(a){var b,c,d,e,f,g;for(this.seriesPoints[a]=[],f=this.data,g=[],d=0,e=f.length;e>d;d++)c=f[d],b=null,null!=c._y[a]&&(b=this.drawLinePoint(c._x,c._y[a],this.colorFor(c,a,"point"),a)),g.push(this.seriesPoints[a].push(b));return g},c.prototype._drawLineFor=function(a){var b;return b=this.paths[a],null!==b?this.drawLinePath(b,this.colorFor(null,a,"line"),a):void 0},c.createPath=function(a,c,d){var e,f,g,h,i,j,k,l,m,n,o,p,q,r;for(k="",c&&(g=b.Line.gradients(a)),l={y:null},h=q=0,r=a.length;r>q;h=++q)e=a[h],null!=e.y&&(null!=l.y?c?(f=g[h],j=g[h-1],i=(e.x-l.x)/4,m=l.x+i,o=Math.min(d,l.y+i*j),n=e.x-i,p=Math.min(d,e.y-i*f),k+="C"+m+","+o+","+n+","+p+","+e.x+","+e.y):k+="L"+e.x+","+e.y:c&&null==g[h]||(k+="M"+e.x+","+e.y)),l=e;return k},c.gradients=function(a){var b,c,d,e,f,g,h,i;for(c=function(a,b){return(a.y-b.y)/(a.x-b.x)},i=[],d=g=0,h=a.length;h>g;d=++g)b=a[d],null!=b.y?(e=a[d+1]||{y:null},f=a[d-1]||{y:null},null!=f.y&&null!=e.y?i.push(c(f,e)):null!=f.y?i.push(c(f,b)):null!=e.y?i.push(c(b,e)):i.push(null)):i.push(null);return i},c.prototype.hilight=function(a){var b,c,d,e,f;if(null!==this.prevHilight&&this.prevHilight!==a)for(b=c=0,e=this.seriesPoints.length-1;e>=0?e>=c:c>=e;b=e>=0?++c:--c)this.seriesPoints[b][this.prevHilight]&&this.seriesPoints[b][this.prevHilight].animate(this.pointShrinkSeries(b));if(null!==a&&this.prevHilight!==a)for(b=d=0,f=this.seriesPoints.length-1;f>=0?f>=d:d>=f;b=f>=0?++d:--d)this.seriesPoints[b][a]&&this.seriesPoints[b][a].animate(this.pointGrowSeries(b));return this.prevHilight=a},c.prototype.colorFor=function(a,b,c){return"function"==typeof this.options.lineColors?this.options.lineColors.call(this,a,b,c):"point"===c?this.options.pointFillColors[b%this.options.pointFillColors.length]||this.options.lineColors[b%this.options.lineColors.length]:this.options.lineColors[b%this.options.lineColors.length]},c.prototype.drawXAxisLabel=function(a,b,c){return this.raphael.text(a,b,c).attr("font-size",this.options.gridTextSize).attr("font-family",this.options.gridTextFamily).attr("font-weight",this.options.gridTextWeight).attr("fill",this.options.gridTextColor)},c.prototype.drawLinePath=function(a,b,c){return this.raphael.path(a).attr("stroke",b).attr("stroke-width",this.lineWidthForSeries(c))},c.prototype.drawLinePoint=function(a,b,c,d){return this.raphael.circle(a,b,this.pointSizeForSeries(d)).attr("fill",c).attr("stroke-width",this.pointStrokeWidthForSeries(d)).attr("stroke",this.pointStrokeColorForSeries(d))},c.prototype.pointStrokeWidthForSeries=function(a){return this.options.pointStrokeWidths[a%this.options.pointStrokeWidths.length]},c.prototype.pointStrokeColorForSeries=function(a){return this.options.pointStrokeColors[a%this.options.pointStrokeColors.length]},c.prototype.lineWidthForSeries=function(a){return this.options.lineWidth instanceof Array?this.options.lineWidth[a%this.options.lineWidth.length]:this.options.lineWidth},c.prototype.pointSizeForSeries=function(a){return this.options.pointSize instanceof Array?this.options.pointSize[a%this.options.pointSize.length]:this.options.pointSize},c.prototype.pointGrowSeries=function(a){return Raphael.animation({r:this.pointSizeForSeries(a)+3},25,"linear")},c.prototype.pointShrinkSeries=function(a){return Raphael.animation({r:this.pointSizeForSeries(a)},25,"linear")},c}(b.Grid),b.labelSeries=function(c,d,e,f,g){var h,i,j,k,l,m,n,o,p,q,r;if(j=200*(d-c)/e,i=new Date(c),n=b.LABEL_SPECS[f],void 0===n)for(r=b.AUTO_LABEL_ORDER,p=0,q=r.length;q>p;p++)if(k=r[p],m=b.LABEL_SPECS[k],j>=m.span){n=m;break}for(void 0===n&&(n=b.LABEL_SPECS.second),g&&(n=a.extend({},n,{fmt:g})),h=n.start(i),l=[];(o=h.getTime())<=d;)o>=c&&l.push([n.fmt(h),o]),n.incr(h);return l},c=function(a){return{span:60*a*1e3,start:function(a){return new Date(a.getFullYear(),a.getMonth(),a.getDate(),a.getHours())},fmt:function(a){return""+b.pad2(a.getHours())+":"+b.pad2(a.getMinutes())},incr:function(b){return b.setUTCMinutes(b.getUTCMinutes()+a)}}},d=function(a){return{span:1e3*a,start:function(a){return new Date(a.getFullYear(),a.getMonth(),a.getDate(),a.getHours(),a.getMinutes())},fmt:function(a){return""+b.pad2(a.getHours())+":"+b.pad2(a.getMinutes())+":"+b.pad2(a.getSeconds())},incr:function(b){return b.setUTCSeconds(b.getUTCSeconds()+a)}}},b.LABEL_SPECS={decade:{span:1728e8,start:function(a){return new Date(a.getFullYear()-a.getFullYear()%10,0,1)},fmt:function(a){return""+a.getFullYear()},incr:function(a){return a.setFullYear(a.getFullYear()+10)}},year:{span:1728e7,start:function(a){return new Date(a.getFullYear(),0,1)},fmt:function(a){return""+a.getFullYear()},incr:function(a){return a.setFullYear(a.getFullYear()+1)}},month:{span:24192e5,start:function(a){return new Date(a.getFullYear(),a.getMonth(),1)},fmt:function(a){return""+a.getFullYear()+"-"+b.pad2(a.getMonth()+1)},incr:function(a){return a.setMonth(a.getMonth()+1)}},week:{span:6048e5,start:function(a){return new Date(a.getFullYear(),a.getMonth(),a.getDate())},fmt:function(a){return""+a.getFullYear()+"-"+b.pad2(a.getMonth()+1)+"-"+b.pad2(a.getDate())},incr:function(a){return a.setDate(a.getDate()+7)}},day:{span:864e5,start:function(a){return new Date(a.getFullYear(),a.getMonth(),a.getDate())},fmt:function(a){return""+a.getFullYear()+"-"+b.pad2(a.getMonth()+1)+"-"+b.pad2(a.getDate())},incr:function(a){return a.setDate(a.getDate()+1)}},hour:c(60),"30min":c(30),"15min":c(15),"10min":c(10),"5min":c(5),minute:c(1),"30sec":d(30),"15sec":d(15),"10sec":d(10),"5sec":d(5),second:d(1)},b.AUTO_LABEL_ORDER=["decade","year","month","week","day","hour","30min","15min","10min","5min","minute","30sec","15sec","10sec","5sec","second"],b.Area=function(c){function d(c){var f;return this instanceof b.Area?(f=a.extend({},e,c),this.cumulative=!f.behaveLikeLine,"auto"===f.fillOpacity&&(f.fillOpacity=f.behaveLikeLine?.8:1),d.__super__.constructor.call(this,f),void 0):new b.Area(c)}var e;return h(d,c),e={fillOpacity:"auto",behaveLikeLine:!1},d.prototype.calcPoints=function(){var a,b,c,d,e,f,g;for(f=this.data,g=[],d=0,e=f.length;e>d;d++)a=f[d],a._x=this.transX(a.x),b=0,a._y=function(){var d,e,f,g;for(f=a.y,g=[],d=0,e=f.length;e>d;d++)c=f[d],this.options.behaveLikeLine?g.push(this.transY(c)):(b+=c||0,g.push(this.transY(b)));return g}.call(this),g.push(a._ymax=Math.max.apply(Math,a._y));return g},d.prototype.drawSeries=function(){var a,b,c,d,e,f,g,h;for(this.seriesPoints=[],b=this.options.behaveLikeLine?function(){f=[];for(var a=0,b=this.options.ykeys.length-1;b>=0?b>=a:a>=b;b>=0?a++:a--)f.push(a);return f}.apply(this):function(){g=[];for(var a=e=this.options.ykeys.length-1;0>=e?0>=a:a>=0;0>=e?a++:a--)g.push(a);return g}.apply(this),h=[],c=0,d=b.length;d>c;c++)a=b[c],this._drawFillFor(a),this._drawLineFor(a),h.push(this._drawPointFor(a));return h},d.prototype._drawFillFor=function(a){var b;return b=this.paths[a],null!==b?(b+="L"+this.transX(this.xmax)+","+this.bottom+"L"+this.transX(this.xmin)+","+this.bottom+"Z",this.drawFilledPath(b,this.fillForSeries(a))):void 0},d.prototype.fillForSeries=function(a){var b;return b=Raphael.rgb2hsl(this.colorFor(this.data[a],a,"line")),Raphael.hsl(b.h,this.options.behaveLikeLine?.9*b.s:.75*b.s,Math.min(.98,this.options.behaveLikeLine?1.2*b.l:1.25*b.l))},d.prototype.drawFilledPath=function(a,b){return this.raphael.path(a).attr("fill",b).attr("fill-opacity",this.options.fillOpacity).attr("stroke","none")},d}(b.Line),b.Bar=function(c){function d(c){return this.onHoverOut=f(this.onHoverOut,this),this.onHoverMove=f(this.onHoverMove,this),this.onGridClick=f(this.onGridClick,this),this instanceof b.Bar?(d.__super__.constructor.call(this,a.extend({},c,{parseTime:!1})),void 0):new b.Bar(c)}return h(d,c),d.prototype.init=function(){return this.cumulative=this.options.stacked,"always"!==this.options.hideHover?(this.hover=new b.Hover({parent:this.el}),this.on("hovermove",this.onHoverMove),this.on("hoverout",this.onHoverOut),this.on("gridclick",this.onGridClick)):void 0},d.prototype.defaults={barSizeRatio:.75,barGap:3,barColors:["#0b62a4","#7a92a3","#4da74d","#afd8f8","#edc240","#cb4b4b","#9440ed"],barOpacity:1,barRadius:[0,0,0,0],xLabelMargin:50},d.prototype.calc=function(){var a;return this.calcBars(),this.options.hideHover===!1?(a=this.hover).update.apply(a,this.hoverContentForRow(this.data.length-1)):void 0},d.prototype.calcBars=function(){var a,b,c,d,e,f,g;for(f=this.data,g=[],a=d=0,e=f.length;e>d;a=++d)b=f[a],b._x=this.left+this.width*(a+.5)/this.data.length,g.push(b._y=function(){var a,d,e,f;for(e=b.y,f=[],a=0,d=e.length;d>a;a++)c=e[a],null!=c?f.push(this.transY(c)):f.push(null);return f}.call(this));return g},d.prototype.draw=function(){var a;return((a=this.options.axes)===!0||"both"===a||"x"===a)&&this.drawXAxis(),this.drawSeries()},d.prototype.drawXAxis=function(){var a,b,c,d,e,f,g,h,i,j,k,l,m;for(j=this.bottom+(this.options.xAxisLabelTopPadding||this.options.padding/2),g=null,f=null,m=[],a=k=0,l=this.data.length;l>=0?l>k:k>l;a=l>=0?++k:--k)h=this.data[this.data.length-1-a],b=this.drawXAxisLabel(h._x,j,h.label),i=b.getBBox(),b.transform("r"+-this.options.xLabelAngle),c=b.getBBox(),b.transform("t0,"+c.height/2+"..."),0!==this.options.xLabelAngle&&(e=-.5*i.width*Math.cos(this.options.xLabelAngle*Math.PI/180),b.transform("t"+e+",0...")),(null==g||g>=c.x+c.width||null!=f&&f>=c.x)&&c.x>=0&&c.x+c.width<this.el.width()?(0!==this.options.xLabelAngle&&(d=1.25*this.options.gridTextSize/Math.sin(this.options.xLabelAngle*Math.PI/180),f=c.x-d),m.push(g=c.x-this.options.xLabelMargin)):m.push(b.remove());return m},d.prototype.drawSeries=function(){var a,b,c,d,e,f,g,h,i,j,k,l,m,n,o;return c=this.width/this.options.data.length,h=this.options.stacked?1:this.options.ykeys.length,a=(c*this.options.barSizeRatio-this.options.barGap*(h-1))/h,this.options.barSize&&(a=Math.min(a,this.options.barSize)),l=c-a*h-this.options.barGap*(h-1),g=l/2,o=this.ymin<=0&&this.ymax>=0?this.transY(0):null,this.bars=function(){var h,l,p,q;for(p=this.data,q=[],d=h=0,l=p.length;l>h;d=++h)i=p[d],e=0,q.push(function(){var h,l,p,q;for(p=i._y,q=[],j=h=0,l=p.length;l>h;j=++h)n=p[j],null!==n?(o?(m=Math.min(n,o),b=Math.max(n,o)):(m=n,b=this.bottom),f=this.left+d*c+g,this.options.stacked||(f+=j*(a+this.options.barGap)),k=b-m,this.options.verticalGridCondition&&this.options.verticalGridCondition(i.x)&&this.drawBar(this.left+d*c,this.top,c,Math.abs(this.top-this.bottom),this.options.verticalGridColor,this.options.verticalGridOpacity,this.options.barRadius),this.options.stacked&&(m-=e),this.drawBar(f,m,a,k,this.colorFor(i,j,"bar"),this.options.barOpacity,this.options.barRadius),q.push(e+=k)):q.push(null);return q}.call(this));return q}.call(this)},d.prototype.colorFor=function(a,b,c){var d,e;return"function"==typeof this.options.barColors?(d={x:a.x,y:a.y[b],label:a.label},e={index:b,key:this.options.ykeys[b],label:this.options.labels[b]},this.options.barColors.call(this,d,e,c)):this.options.barColors[b%this.options.barColors.length]},d.prototype.hitTest=function(a){return 0===this.data.length?null:(a=Math.max(Math.min(a,this.right),this.left),Math.min(this.data.length-1,Math.floor((a-this.left)/(this.width/this.data.length))))},d.prototype.onGridClick=function(a,b){var c;return c=this.hitTest(a),this.fire("click",c,this.data[c].src,a,b)},d.prototype.onHoverMove=function(a){var b,c;return b=this.hitTest(a),(c=this.hover).update.apply(c,this.hoverContentForRow(b))},d.prototype.onHoverOut=function(){return this.options.hideHover!==!1?this.hover.hide():void 0},d.prototype.hoverContentForRow=function(a){var b,c,d,e,f,g,h,i;for(d=this.data[a],b="<div class='morris-hover-row-label'>"+d.label+"</div>",i=d.y,c=g=0,h=i.length;h>g;c=++g)f=i[c],b+="<div class='morris-hover-point' style='color: "+this.colorFor(d,c,"label")+"'>\n  "+this.options.labels[c]+":\n  "+this.yLabelFormat(f)+"\n</div>";return"function"==typeof this.options.hoverCallback&&(b=this.options.hoverCallback(a,this.options,b,d.src)),e=this.left+(a+.5)*this.width/this.data.length,[b,e]},d.prototype.drawXAxisLabel=function(a,b,c){var d;return d=this.raphael.text(a,b,c).attr("font-size",this.options.gridTextSize).attr("font-family",this.options.gridTextFamily).attr("font-weight",this.options.gridTextWeight).attr("fill",this.options.gridTextColor)},d.prototype.drawBar=function(a,b,c,d,e,f,g){var h,i;return h=Math.max.apply(Math,g),i=0===h||h>d?this.raphael.rect(a,b,c,d):this.raphael.path(this.roundedRect(a,b,c,d,g)),i.attr("fill",e).attr("fill-opacity",f).attr("stroke","none")},d.prototype.roundedRect=function(a,b,c,d,e){return null==e&&(e=[0,0,0,0]),["M",a,e[0]+b,"Q",a,b,a+e[0],b,"L",a+c-e[1],b,"Q",a+c,b,a+c,b+e[1],"L",a+c,b+d-e[2],"Q",a+c,b+d,a+c-e[2],b+d,"L",a+e[3],b+d,"Q",a,b+d,a,b+d-e[3],"Z"]},d}(b.Grid),b.Donut=function(c){function d(c){this.resizeHandler=f(this.resizeHandler,this),this.select=f(this.select,this),this.click=f(this.click,this);var d=this;if(!(this instanceof b.Donut))return new b.Donut(c);if(this.options=a.extend({},this.defaults,c),this.el="string"==typeof c.element?a(document.getElementById(c.element)):a(c.element),null===this.el||0===this.el.length)throw new Error("Graph placeholder not found.");void 0!==c.data&&0!==c.data.length&&(this.raphael=new Raphael(this.el[0]),this.options.resize&&a(window).bind("resize",function(){return null!=d.timeoutId&&window.clearTimeout(d.timeoutId),d.timeoutId=window.setTimeout(d.resizeHandler,100)}),this.setData(c.data))}return h(d,c),d.prototype.defaults={colors:["#0B62A4","#3980B5","#679DC6","#95BBD7","#B0CCE1","#095791","#095085","#083E67","#052C48","#042135"],backgroundColor:"#FFFFFF",labelColor:"#000000",formatter:b.commas,resize:!1},d.prototype.redraw=function(){var a,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x;for(this.raphael.clear(),c=this.el.width()/2,d=this.el.height()/2,n=(Math.min(c,d)-10)/3,l=0,u=this.values,o=0,r=u.length;r>o;o++)m=u[o],l+=m;for(i=5/(2*n),a=1.9999*Math.PI-i*this.data.length,g=0,f=0,this.segments=[],v=this.values,e=p=0,s=v.length;s>p;e=++p)m=v[e],j=g+i+a*(m/l),k=new b.DonutSegment(c,d,2*n,n,g,j,this.data[e].color||this.options.colors[f%this.options.colors.length],this.options.backgroundColor,f,this.raphael),k.render(),this.segments.push(k),k.on("hover",this.select),k.on("click",this.click),g=j,f+=1;for(this.text1=this.drawEmptyDonutLabel(c,d-10,this.options.labelColor,15,800),this.text2=this.drawEmptyDonutLabel(c,d+10,this.options.labelColor,14),h=Math.max.apply(Math,this.values),f=0,w=this.values,x=[],q=0,t=w.length;t>q;q++){if(m=w[q],m===h){this.select(f);
break}x.push(f+=1)}return x},d.prototype.setData=function(a){var b;return this.data=a,this.values=function(){var a,c,d,e;for(d=this.data,e=[],a=0,c=d.length;c>a;a++)b=d[a],e.push(parseFloat(b.value));return e}.call(this),this.redraw()},d.prototype.click=function(a){return this.fire("click",a,this.data[a])},d.prototype.select=function(a){var b,c,d,e,f,g;for(g=this.segments,e=0,f=g.length;f>e;e++)c=g[e],c.deselect();return d=this.segments[a],d.select(),b=this.data[a],this.setLabels(b.label,this.options.formatter(b.value,b))},d.prototype.setLabels=function(a,b){var c,d,e,f,g,h,i,j;return c=2*(Math.min(this.el.width()/2,this.el.height()/2)-10)/3,f=1.8*c,e=c/2,d=c/3,this.text1.attr({text:a,transform:""}),g=this.text1.getBBox(),h=Math.min(f/g.width,e/g.height),this.text1.attr({transform:"S"+h+","+h+","+(g.x+g.width/2)+","+(g.y+g.height)}),this.text2.attr({text:b,transform:""}),i=this.text2.getBBox(),j=Math.min(f/i.width,d/i.height),this.text2.attr({transform:"S"+j+","+j+","+(i.x+i.width/2)+","+i.y})},d.prototype.drawEmptyDonutLabel=function(a,b,c,d,e){var f;return f=this.raphael.text(a,b,"").attr("font-size",d).attr("fill",c),null!=e&&f.attr("font-weight",e),f},d.prototype.resizeHandler=function(){return this.timeoutId=null,this.raphael.setSize(this.el.width(),this.el.height()),this.redraw()},d}(b.EventEmitter),b.DonutSegment=function(a){function b(a,b,c,d,e,g,h,i,j,k){this.cx=a,this.cy=b,this.inner=c,this.outer=d,this.color=h,this.backgroundColor=i,this.index=j,this.raphael=k,this.deselect=f(this.deselect,this),this.select=f(this.select,this),this.sin_p0=Math.sin(e),this.cos_p0=Math.cos(e),this.sin_p1=Math.sin(g),this.cos_p1=Math.cos(g),this.is_long=g-e>Math.PI?1:0,this.path=this.calcSegment(this.inner+3,this.inner+this.outer-5),this.selectedPath=this.calcSegment(this.inner+3,this.inner+this.outer),this.hilight=this.calcArc(this.inner)}return h(b,a),b.prototype.calcArcPoints=function(a){return[this.cx+a*this.sin_p0,this.cy+a*this.cos_p0,this.cx+a*this.sin_p1,this.cy+a*this.cos_p1]},b.prototype.calcSegment=function(a,b){var c,d,e,f,g,h,i,j,k,l;return k=this.calcArcPoints(a),c=k[0],e=k[1],d=k[2],f=k[3],l=this.calcArcPoints(b),g=l[0],i=l[1],h=l[2],j=l[3],"M"+c+","+e+("A"+a+","+a+",0,"+this.is_long+",0,"+d+","+f)+("L"+h+","+j)+("A"+b+","+b+",0,"+this.is_long+",1,"+g+","+i)+"Z"},b.prototype.calcArc=function(a){var b,c,d,e,f;return f=this.calcArcPoints(a),b=f[0],d=f[1],c=f[2],e=f[3],"M"+b+","+d+("A"+a+","+a+",0,"+this.is_long+",0,"+c+","+e)},b.prototype.render=function(){var a=this;return this.arc=this.drawDonutArc(this.hilight,this.color),this.seg=this.drawDonutSegment(this.path,this.color,this.backgroundColor,function(){return a.fire("hover",a.index)},function(){return a.fire("click",a.index)})},b.prototype.drawDonutArc=function(a,b){return this.raphael.path(a).attr({stroke:b,"stroke-width":2,opacity:0})},b.prototype.drawDonutSegment=function(a,b,c,d,e){return this.raphael.path(a).attr({fill:b,stroke:c,"stroke-width":3}).hover(d).click(e)},b.prototype.select=function(){return this.selected?void 0:(this.seg.animate({path:this.selectedPath},150,"<>"),this.arc.animate({opacity:1},150,"<>"),this.selected=!0)},b.prototype.deselect=function(){return this.selected?(this.seg.animate({path:this.path},150,"<>"),this.arc.animate({opacity:0},150,"<>"),this.selected=!1):void 0},b}(b.EventEmitter)}).call(this);
// ┌────────────────────────────────────────────────────────────────────┐ \\
// │ Raphaël 2.1.4 - JavaScript Vector Library                          │ \\
// ├────────────────────────────────────────────────────────────────────┤ \\
// │ Copyright © 2008-2012 Dmitry Baranovskiy (http://raphaeljs.com)    │ \\
// │ Copyright © 2008-2012 Sencha Labs (http://sencha.com)              │ \\
// ├────────────────────────────────────────────────────────────────────┤ \\
// │ Licensed under the MIT (http://raphaeljs.com/license.html) license.│ \\
// └────────────────────────────────────────────────────────────────────┘ \\
!function(a){var b,c,d="0.4.2",e="hasOwnProperty",f=/[\.\/]/,g="*",h=function(){},i=function(a,b){return a-b},j={n:{}},k=function(a,d){a=String(a);var e,f=c,g=Array.prototype.slice.call(arguments,2),h=k.listeners(a),j=0,l=[],m={},n=[],o=b;b=a,c=0;for(var p=0,q=h.length;q>p;p++)"zIndex"in h[p]&&(l.push(h[p].zIndex),h[p].zIndex<0&&(m[h[p].zIndex]=h[p]));for(l.sort(i);l[j]<0;)if(e=m[l[j++]],n.push(e.apply(d,g)),c)return c=f,n;for(p=0;q>p;p++)if(e=h[p],"zIndex"in e)if(e.zIndex==l[j]){if(n.push(e.apply(d,g)),c)break;do if(j++,e=m[l[j]],e&&n.push(e.apply(d,g)),c)break;while(e)}else m[e.zIndex]=e;else if(n.push(e.apply(d,g)),c)break;return c=f,b=o,n.length?n:null};k._events=j,k.listeners=function(a){var b,c,d,e,h,i,k,l,m=a.split(f),n=j,o=[n],p=[];for(e=0,h=m.length;h>e;e++){for(l=[],i=0,k=o.length;k>i;i++)for(n=o[i].n,c=[n[m[e]],n[g]],d=2;d--;)b=c[d],b&&(l.push(b),p=p.concat(b.f||[]));o=l}return p},k.on=function(a,b){if(a=String(a),"function"!=typeof b)return function(){};for(var c=a.split(f),d=j,e=0,g=c.length;g>e;e++)d=d.n,d=d.hasOwnProperty(c[e])&&d[c[e]]||(d[c[e]]={n:{}});for(d.f=d.f||[],e=0,g=d.f.length;g>e;e++)if(d.f[e]==b)return h;return d.f.push(b),function(a){+a==+a&&(b.zIndex=+a)}},k.f=function(a){var b=[].slice.call(arguments,1);return function(){k.apply(null,[a,null].concat(b).concat([].slice.call(arguments,0)))}},k.stop=function(){c=1},k.nt=function(a){return a?new RegExp("(?:\\.|\\/|^)"+a+"(?:\\.|\\/|$)").test(b):b},k.nts=function(){return b.split(f)},k.off=k.unbind=function(a,b){if(!a)return void(k._events=j={n:{}});var c,d,h,i,l,m,n,o=a.split(f),p=[j];for(i=0,l=o.length;l>i;i++)for(m=0;m<p.length;m+=h.length-2){if(h=[m,1],c=p[m].n,o[i]!=g)c[o[i]]&&h.push(c[o[i]]);else for(d in c)c[e](d)&&h.push(c[d]);p.splice.apply(p,h)}for(i=0,l=p.length;l>i;i++)for(c=p[i];c.n;){if(b){if(c.f){for(m=0,n=c.f.length;n>m;m++)if(c.f[m]==b){c.f.splice(m,1);break}!c.f.length&&delete c.f}for(d in c.n)if(c.n[e](d)&&c.n[d].f){var q=c.n[d].f;for(m=0,n=q.length;n>m;m++)if(q[m]==b){q.splice(m,1);break}!q.length&&delete c.n[d].f}}else{delete c.f;for(d in c.n)c.n[e](d)&&c.n[d].f&&delete c.n[d].f}c=c.n}},k.once=function(a,b){var c=function(){return k.unbind(a,c),b.apply(this,arguments)};return k.on(a,c)},k.version=d,k.toString=function(){return"You are running Eve "+d},"undefined"!=typeof module&&module.exports?module.exports=k:"undefined"!=typeof define?define("eve",[],function(){return k}):a.eve=k}(window||this),function(a,b){"function"==typeof define&&define.amd?define(["eve"],function(c){return b(a,c)}):b(a,a.eve||"function"==typeof require&&require("eve"))}(this,function(a,b){function c(a){if(c.is(a,"function"))return u?a():b.on("raphael.DOMload",a);if(c.is(a,V))return c._engine.create[D](c,a.splice(0,3+c.is(a[0],T))).add(a);var d=Array.prototype.slice.call(arguments,0);if(c.is(d[d.length-1],"function")){var e=d.pop();return u?e.call(c._engine.create[D](c,d)):b.on("raphael.DOMload",function(){e.call(c._engine.create[D](c,d))})}return c._engine.create[D](c,arguments)}function d(a){if("function"==typeof a||Object(a)!==a)return a;var b=new a.constructor;for(var c in a)a[z](c)&&(b[c]=d(a[c]));return b}function e(a,b){for(var c=0,d=a.length;d>c;c++)if(a[c]===b)return a.push(a.splice(c,1)[0])}function f(a,b,c){function d(){var f=Array.prototype.slice.call(arguments,0),g=f.join("␀"),h=d.cache=d.cache||{},i=d.count=d.count||[];return h[z](g)?(e(i,g),c?c(h[g]):h[g]):(i.length>=1e3&&delete h[i.shift()],i.push(g),h[g]=a[D](b,f),c?c(h[g]):h[g])}return d}function g(){return this.hex}function h(a,b){for(var c=[],d=0,e=a.length;e-2*!b>d;d+=2){var f=[{x:+a[d-2],y:+a[d-1]},{x:+a[d],y:+a[d+1]},{x:+a[d+2],y:+a[d+3]},{x:+a[d+4],y:+a[d+5]}];b?d?e-4==d?f[3]={x:+a[0],y:+a[1]}:e-2==d&&(f[2]={x:+a[0],y:+a[1]},f[3]={x:+a[2],y:+a[3]}):f[0]={x:+a[e-2],y:+a[e-1]}:e-4==d?f[3]=f[2]:d||(f[0]={x:+a[d],y:+a[d+1]}),c.push(["C",(-f[0].x+6*f[1].x+f[2].x)/6,(-f[0].y+6*f[1].y+f[2].y)/6,(f[1].x+6*f[2].x-f[3].x)/6,(f[1].y+6*f[2].y-f[3].y)/6,f[2].x,f[2].y])}return c}function i(a,b,c,d,e){var f=-3*b+9*c-9*d+3*e,g=a*f+6*b-12*c+6*d;return a*g-3*b+3*c}function j(a,b,c,d,e,f,g,h,j){null==j&&(j=1),j=j>1?1:0>j?0:j;for(var k=j/2,l=12,m=[-.1252,.1252,-.3678,.3678,-.5873,.5873,-.7699,.7699,-.9041,.9041,-.9816,.9816],n=[.2491,.2491,.2335,.2335,.2032,.2032,.1601,.1601,.1069,.1069,.0472,.0472],o=0,p=0;l>p;p++){var q=k*m[p]+k,r=i(q,a,c,e,g),s=i(q,b,d,f,h),t=r*r+s*s;o+=n[p]*N.sqrt(t)}return k*o}function k(a,b,c,d,e,f,g,h,i){if(!(0>i||j(a,b,c,d,e,f,g,h)<i)){var k,l=1,m=l/2,n=l-m,o=.01;for(k=j(a,b,c,d,e,f,g,h,n);Q(k-i)>o;)m/=2,n+=(i>k?1:-1)*m,k=j(a,b,c,d,e,f,g,h,n);return n}}function l(a,b,c,d,e,f,g,h){if(!(O(a,c)<P(e,g)||P(a,c)>O(e,g)||O(b,d)<P(f,h)||P(b,d)>O(f,h))){var i=(a*d-b*c)*(e-g)-(a-c)*(e*h-f*g),j=(a*d-b*c)*(f-h)-(b-d)*(e*h-f*g),k=(a-c)*(f-h)-(b-d)*(e-g);if(k){var l=i/k,m=j/k,n=+l.toFixed(2),o=+m.toFixed(2);if(!(n<+P(a,c).toFixed(2)||n>+O(a,c).toFixed(2)||n<+P(e,g).toFixed(2)||n>+O(e,g).toFixed(2)||o<+P(b,d).toFixed(2)||o>+O(b,d).toFixed(2)||o<+P(f,h).toFixed(2)||o>+O(f,h).toFixed(2)))return{x:l,y:m}}}}function m(a,b,d){var e=c.bezierBBox(a),f=c.bezierBBox(b);if(!c.isBBoxIntersect(e,f))return d?0:[];for(var g=j.apply(0,a),h=j.apply(0,b),i=O(~~(g/5),1),k=O(~~(h/5),1),m=[],n=[],o={},p=d?0:[],q=0;i+1>q;q++){var r=c.findDotsAtSegment.apply(c,a.concat(q/i));m.push({x:r.x,y:r.y,t:q/i})}for(q=0;k+1>q;q++)r=c.findDotsAtSegment.apply(c,b.concat(q/k)),n.push({x:r.x,y:r.y,t:q/k});for(q=0;i>q;q++)for(var s=0;k>s;s++){var t=m[q],u=m[q+1],v=n[s],w=n[s+1],x=Q(u.x-t.x)<.001?"y":"x",y=Q(w.x-v.x)<.001?"y":"x",z=l(t.x,t.y,u.x,u.y,v.x,v.y,w.x,w.y);if(z){if(o[z.x.toFixed(4)]==z.y.toFixed(4))continue;o[z.x.toFixed(4)]=z.y.toFixed(4);var A=t.t+Q((z[x]-t[x])/(u[x]-t[x]))*(u.t-t.t),B=v.t+Q((z[y]-v[y])/(w[y]-v[y]))*(w.t-v.t);A>=0&&1.001>=A&&B>=0&&1.001>=B&&(d?p++:p.push({x:z.x,y:z.y,t1:P(A,1),t2:P(B,1)}))}}return p}function n(a,b,d){a=c._path2curve(a),b=c._path2curve(b);for(var e,f,g,h,i,j,k,l,n,o,p=d?0:[],q=0,r=a.length;r>q;q++){var s=a[q];if("M"==s[0])e=i=s[1],f=j=s[2];else{"C"==s[0]?(n=[e,f].concat(s.slice(1)),e=n[6],f=n[7]):(n=[e,f,e,f,i,j,i,j],e=i,f=j);for(var t=0,u=b.length;u>t;t++){var v=b[t];if("M"==v[0])g=k=v[1],h=l=v[2];else{"C"==v[0]?(o=[g,h].concat(v.slice(1)),g=o[6],h=o[7]):(o=[g,h,g,h,k,l,k,l],g=k,h=l);var w=m(n,o,d);if(d)p+=w;else{for(var x=0,y=w.length;y>x;x++)w[x].segment1=q,w[x].segment2=t,w[x].bez1=n,w[x].bez2=o;p=p.concat(w)}}}}}return p}function o(a,b,c,d,e,f){null!=a?(this.a=+a,this.b=+b,this.c=+c,this.d=+d,this.e=+e,this.f=+f):(this.a=1,this.b=0,this.c=0,this.d=1,this.e=0,this.f=0)}function p(){return this.x+H+this.y+H+this.width+" × "+this.height}function q(a,b,c,d,e,f){function g(a){return((l*a+k)*a+j)*a}function h(a,b){var c=i(a,b);return((o*c+n)*c+m)*c}function i(a,b){var c,d,e,f,h,i;for(e=a,i=0;8>i;i++){if(f=g(e)-a,Q(f)<b)return e;if(h=(3*l*e+2*k)*e+j,Q(h)<1e-6)break;e-=f/h}if(c=0,d=1,e=a,c>e)return c;if(e>d)return d;for(;d>c;){if(f=g(e),Q(f-a)<b)return e;a>f?c=e:d=e,e=(d-c)/2+c}return e}var j=3*b,k=3*(d-b)-j,l=1-j-k,m=3*c,n=3*(e-c)-m,o=1-m-n;return h(a,1/(200*f))}function r(a,b){var c=[],d={};if(this.ms=b,this.times=1,a){for(var e in a)a[z](e)&&(d[_(e)]=a[e],c.push(_(e)));c.sort(lb)}this.anim=d,this.top=c[c.length-1],this.percents=c}function s(a,d,e,f,g,h){e=_(e);var i,j,k,l,m,n,p=a.ms,r={},s={},t={};if(f)for(v=0,x=ic.length;x>v;v++){var u=ic[v];if(u.el.id==d.id&&u.anim==a){u.percent!=e?(ic.splice(v,1),k=1):j=u,d.attr(u.totalOrigin);break}}else f=+s;for(var v=0,x=a.percents.length;x>v;v++){if(a.percents[v]==e||a.percents[v]>f*a.top){e=a.percents[v],m=a.percents[v-1]||0,p=p/a.top*(e-m),l=a.percents[v+1],i=a.anim[e];break}f&&d.attr(a.anim[a.percents[v]])}if(i){if(j)j.initstatus=f,j.start=new Date-j.ms*f;else{for(var y in i)if(i[z](y)&&(db[z](y)||d.paper.customAttributes[z](y)))switch(r[y]=d.attr(y),null==r[y]&&(r[y]=cb[y]),s[y]=i[y],db[y]){case T:t[y]=(s[y]-r[y])/p;break;case"colour":r[y]=c.getRGB(r[y]);var A=c.getRGB(s[y]);t[y]={r:(A.r-r[y].r)/p,g:(A.g-r[y].g)/p,b:(A.b-r[y].b)/p};break;case"path":var B=Kb(r[y],s[y]),C=B[1];for(r[y]=B[0],t[y]=[],v=0,x=r[y].length;x>v;v++){t[y][v]=[0];for(var D=1,F=r[y][v].length;F>D;D++)t[y][v][D]=(C[v][D]-r[y][v][D])/p}break;case"transform":var G=d._,H=Pb(G[y],s[y]);if(H)for(r[y]=H.from,s[y]=H.to,t[y]=[],t[y].real=!0,v=0,x=r[y].length;x>v;v++)for(t[y][v]=[r[y][v][0]],D=1,F=r[y][v].length;F>D;D++)t[y][v][D]=(s[y][v][D]-r[y][v][D])/p;else{var K=d.matrix||new o,L={_:{transform:G.transform},getBBox:function(){return d.getBBox(1)}};r[y]=[K.a,K.b,K.c,K.d,K.e,K.f],Nb(L,s[y]),s[y]=L._.transform,t[y]=[(L.matrix.a-K.a)/p,(L.matrix.b-K.b)/p,(L.matrix.c-K.c)/p,(L.matrix.d-K.d)/p,(L.matrix.e-K.e)/p,(L.matrix.f-K.f)/p]}break;case"csv":var M=I(i[y])[J](w),N=I(r[y])[J](w);if("clip-rect"==y)for(r[y]=N,t[y]=[],v=N.length;v--;)t[y][v]=(M[v]-r[y][v])/p;s[y]=M;break;default:for(M=[][E](i[y]),N=[][E](r[y]),t[y]=[],v=d.paper.customAttributes[y].length;v--;)t[y][v]=((M[v]||0)-(N[v]||0))/p}var O=i.easing,P=c.easing_formulas[O];if(!P)if(P=I(O).match(Z),P&&5==P.length){var Q=P;P=function(a){return q(a,+Q[1],+Q[2],+Q[3],+Q[4],p)}}else P=nb;if(n=i.start||a.start||+new Date,u={anim:a,percent:e,timestamp:n,start:n+(a.del||0),status:0,initstatus:f||0,stop:!1,ms:p,easing:P,from:r,diff:t,to:s,el:d,callback:i.callback,prev:m,next:l,repeat:h||a.times,origin:d.attr(),totalOrigin:g},ic.push(u),f&&!j&&!k&&(u.stop=!0,u.start=new Date-p*f,1==ic.length))return kc();k&&(u.start=new Date-u.ms*f),1==ic.length&&jc(kc)}b("raphael.anim.start."+d.id,d,a)}}function t(a){for(var b=0;b<ic.length;b++)ic[b].el.paper==a&&ic.splice(b--,1)}c.version="2.1.2",c.eve=b;var u,v,w=/[, ]+/,x={circle:1,rect:1,path:1,ellipse:1,text:1,image:1},y=/\{(\d+)\}/g,z="hasOwnProperty",A={doc:document,win:a},B={was:Object.prototype[z].call(A.win,"Raphael"),is:A.win.Raphael},C=function(){this.ca=this.customAttributes={}},D="apply",E="concat",F="ontouchstart"in A.win||A.win.DocumentTouch&&A.doc instanceof DocumentTouch,G="",H=" ",I=String,J="split",K="click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend touchcancel"[J](H),L={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},M=I.prototype.toLowerCase,N=Math,O=N.max,P=N.min,Q=N.abs,R=N.pow,S=N.PI,T="number",U="string",V="array",W=Object.prototype.toString,X=(c._ISURL=/^url\(['"]?(.+?)['"]?\)$/i,/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+%?(?:\s*,\s*[\d\.]+%?)?)\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i),Y={NaN:1,Infinity:1,"-Infinity":1},Z=/^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,$=N.round,_=parseFloat,ab=parseInt,bb=I.prototype.toUpperCase,cb=c._availableAttrs={"arrow-end":"none","arrow-start":"none",blur:0,"clip-rect":"0 0 1e9 1e9",cursor:"default",cx:0,cy:0,fill:"#fff","fill-opacity":1,font:'10px "Arial"',"font-family":'"Arial"',"font-size":"10","font-style":"normal","font-weight":400,gradient:0,height:0,href:"http://raphaeljs.com/","letter-spacing":0,opacity:1,path:"M0,0",r:0,rx:0,ry:0,src:"",stroke:"#000","stroke-dasharray":"","stroke-linecap":"butt","stroke-linejoin":"butt","stroke-miterlimit":0,"stroke-opacity":1,"stroke-width":1,target:"_blank","text-anchor":"middle",title:"Raphael",transform:"",width:0,x:0,y:0},db=c._availableAnimAttrs={blur:T,"clip-rect":"csv",cx:T,cy:T,fill:"colour","fill-opacity":T,"font-size":T,height:T,opacity:T,path:"path",r:T,rx:T,ry:T,stroke:"colour","stroke-opacity":T,"stroke-width":T,transform:"transform",width:T,x:T,y:T},eb=/[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/,fb={hs:1,rg:1},gb=/,?([achlmqrstvxz]),?/gi,hb=/([achlmrqstvz])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/gi,ib=/([rstm])[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029,]*((-?\d*\.?\d*(?:e[\-+]?\d+)?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*)+)/gi,jb=/(-?\d*\.?\d*(?:e[\-+]?\d+)?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,?[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*/gi,kb=(c._radial_gradient=/^r(?:\(([^,]+?)[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*,[\x09\x0a\x0b\x0c\x0d\x20\xa0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u202f\u205f\u3000\u2028\u2029]*([^\)]+?)\))?/,{}),lb=function(a,b){return _(a)-_(b)},mb=function(){},nb=function(a){return a},ob=c._rectPath=function(a,b,c,d,e){return e?[["M",a+e,b],["l",c-2*e,0],["a",e,e,0,0,1,e,e],["l",0,d-2*e],["a",e,e,0,0,1,-e,e],["l",2*e-c,0],["a",e,e,0,0,1,-e,-e],["l",0,2*e-d],["a",e,e,0,0,1,e,-e],["z"]]:[["M",a,b],["l",c,0],["l",0,d],["l",-c,0],["z"]]},pb=function(a,b,c,d){return null==d&&(d=c),[["M",a,b],["m",0,-d],["a",c,d,0,1,1,0,2*d],["a",c,d,0,1,1,0,-2*d],["z"]]},qb=c._getPath={path:function(a){return a.attr("path")},circle:function(a){var b=a.attrs;return pb(b.cx,b.cy,b.r)},ellipse:function(a){var b=a.attrs;return pb(b.cx,b.cy,b.rx,b.ry)},rect:function(a){var b=a.attrs;return ob(b.x,b.y,b.width,b.height,b.r)},image:function(a){var b=a.attrs;return ob(b.x,b.y,b.width,b.height)},text:function(a){var b=a._getBBox();return ob(b.x,b.y,b.width,b.height)},set:function(a){var b=a._getBBox();return ob(b.x,b.y,b.width,b.height)}},rb=c.mapPath=function(a,b){if(!b)return a;var c,d,e,f,g,h,i;for(a=Kb(a),e=0,g=a.length;g>e;e++)for(i=a[e],f=1,h=i.length;h>f;f+=2)c=b.x(i[f],i[f+1]),d=b.y(i[f],i[f+1]),i[f]=c,i[f+1]=d;return a};if(c._g=A,c.type=A.win.SVGAngle||A.doc.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML","VML"==c.type){var sb,tb=A.doc.createElement("div");if(tb.innerHTML='<v:shape adj="1"/>',sb=tb.firstChild,sb.style.behavior="url(#default#VML)",!sb||"object"!=typeof sb.adj)return c.type=G;tb=null}c.svg=!(c.vml="VML"==c.type),c._Paper=C,c.fn=v=C.prototype=c.prototype,c._id=0,c._oid=0,c.is=function(a,b){return b=M.call(b),"finite"==b?!Y[z](+a):"array"==b?a instanceof Array:"null"==b&&null===a||b==typeof a&&null!==a||"object"==b&&a===Object(a)||"array"==b&&Array.isArray&&Array.isArray(a)||W.call(a).slice(8,-1).toLowerCase()==b},c.angle=function(a,b,d,e,f,g){if(null==f){var h=a-d,i=b-e;return h||i?(180+180*N.atan2(-i,-h)/S+360)%360:0}return c.angle(a,b,f,g)-c.angle(d,e,f,g)},c.rad=function(a){return a%360*S/180},c.deg=function(a){return Math.round(180*a/S%360*1e3)/1e3},c.snapTo=function(a,b,d){if(d=c.is(d,"finite")?d:10,c.is(a,V)){for(var e=a.length;e--;)if(Q(a[e]-b)<=d)return a[e]}else{a=+a;var f=b%a;if(d>f)return b-f;if(f>a-d)return b-f+a}return b};c.createUUID=function(a,b){return function(){return"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(a,b).toUpperCase()}}(/[xy]/g,function(a){var b=16*N.random()|0,c="x"==a?b:3&b|8;return c.toString(16)});c.setWindow=function(a){b("raphael.setWindow",c,A.win,a),A.win=a,A.doc=A.win.document,c._engine.initWin&&c._engine.initWin(A.win)};var ub=function(a){if(c.vml){var b,d=/^\s+|\s+$/g;try{var e=new ActiveXObject("htmlfile");e.write("<body>"),e.close(),b=e.body}catch(g){b=createPopup().document.body}var h=b.createTextRange();ub=f(function(a){try{b.style.color=I(a).replace(d,G);var c=h.queryCommandValue("ForeColor");return c=(255&c)<<16|65280&c|(16711680&c)>>>16,"#"+("000000"+c.toString(16)).slice(-6)}catch(e){return"none"}})}else{var i=A.doc.createElement("i");i.title="Raphaël Colour Picker",i.style.display="none",A.doc.body.appendChild(i),ub=f(function(a){return i.style.color=a,A.doc.defaultView.getComputedStyle(i,G).getPropertyValue("color")})}return ub(a)},vb=function(){return"hsb("+[this.h,this.s,this.b]+")"},wb=function(){return"hsl("+[this.h,this.s,this.l]+")"},xb=function(){return this.hex},yb=function(a,b,d){if(null==b&&c.is(a,"object")&&"r"in a&&"g"in a&&"b"in a&&(d=a.b,b=a.g,a=a.r),null==b&&c.is(a,U)){var e=c.getRGB(a);a=e.r,b=e.g,d=e.b}return(a>1||b>1||d>1)&&(a/=255,b/=255,d/=255),[a,b,d]},zb=function(a,b,d,e){a*=255,b*=255,d*=255;var f={r:a,g:b,b:d,hex:c.rgb(a,b,d),toString:xb};return c.is(e,"finite")&&(f.opacity=e),f};c.color=function(a){var b;return c.is(a,"object")&&"h"in a&&"s"in a&&"b"in a?(b=c.hsb2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.hex=b.hex):c.is(a,"object")&&"h"in a&&"s"in a&&"l"in a?(b=c.hsl2rgb(a),a.r=b.r,a.g=b.g,a.b=b.b,a.hex=b.hex):(c.is(a,"string")&&(a=c.getRGB(a)),c.is(a,"object")&&"r"in a&&"g"in a&&"b"in a?(b=c.rgb2hsl(a),a.h=b.h,a.s=b.s,a.l=b.l,b=c.rgb2hsb(a),a.v=b.b):(a={hex:"none"},a.r=a.g=a.b=a.h=a.s=a.v=a.l=-1)),a.toString=xb,a},c.hsb2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"b"in a&&(c=a.b,b=a.s,d=a.o,a=a.h),a*=360;var e,f,g,h,i;return a=a%360/60,i=c*b,h=i*(1-Q(a%2-1)),e=f=g=c-i,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],zb(e,f,g,d)},c.hsl2rgb=function(a,b,c,d){this.is(a,"object")&&"h"in a&&"s"in a&&"l"in a&&(c=a.l,b=a.s,a=a.h),(a>1||b>1||c>1)&&(a/=360,b/=100,c/=100),a*=360;var e,f,g,h,i;return a=a%360/60,i=2*b*(.5>c?c:1-c),h=i*(1-Q(a%2-1)),e=f=g=c-i/2,a=~~a,e+=[i,h,0,0,h,i][a],f+=[h,i,i,h,0,0][a],g+=[0,0,h,i,i,h][a],zb(e,f,g,d)},c.rgb2hsb=function(a,b,c){c=yb(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g;return f=O(a,b,c),g=f-P(a,b,c),d=0==g?null:f==a?(b-c)/g:f==b?(c-a)/g+2:(a-b)/g+4,d=(d+360)%6*60/360,e=0==g?0:g/f,{h:d,s:e,b:f,toString:vb}},c.rgb2hsl=function(a,b,c){c=yb(a,b,c),a=c[0],b=c[1],c=c[2];var d,e,f,g,h,i;return g=O(a,b,c),h=P(a,b,c),i=g-h,d=0==i?null:g==a?(b-c)/i:g==b?(c-a)/i+2:(a-b)/i+4,d=(d+360)%6*60/360,f=(g+h)/2,e=0==i?0:.5>f?i/(2*f):i/(2-2*f),{h:d,s:e,l:f,toString:wb}},c._path2string=function(){return this.join(",").replace(gb,"$1")};c._preload=function(a,b){var c=A.doc.createElement("img");c.style.cssText="position:absolute;left:-9999em;top:-9999em",c.onload=function(){b.call(this),this.onload=null,A.doc.body.removeChild(this)},c.onerror=function(){A.doc.body.removeChild(this)},A.doc.body.appendChild(c),c.src=a};c.getRGB=f(function(a){if(!a||(a=I(a)).indexOf("-")+1)return{r:-1,g:-1,b:-1,hex:"none",error:1,toString:g};if("none"==a)return{r:-1,g:-1,b:-1,hex:"none",toString:g};!(fb[z](a.toLowerCase().substring(0,2))||"#"==a.charAt())&&(a=ub(a));var b,d,e,f,h,i,j=a.match(X);return j?(j[2]&&(e=ab(j[2].substring(5),16),d=ab(j[2].substring(3,5),16),b=ab(j[2].substring(1,3),16)),j[3]&&(e=ab((h=j[3].charAt(3))+h,16),d=ab((h=j[3].charAt(2))+h,16),b=ab((h=j[3].charAt(1))+h,16)),j[4]&&(i=j[4][J](eb),b=_(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=_(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=_(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),"rgba"==j[1].toLowerCase().slice(0,4)&&(f=_(i[3])),i[3]&&"%"==i[3].slice(-1)&&(f/=100)),j[5]?(i=j[5][J](eb),b=_(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=_(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=_(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsba"==j[1].toLowerCase().slice(0,4)&&(f=_(i[3])),i[3]&&"%"==i[3].slice(-1)&&(f/=100),c.hsb2rgb(b,d,e,f)):j[6]?(i=j[6][J](eb),b=_(i[0]),"%"==i[0].slice(-1)&&(b*=2.55),d=_(i[1]),"%"==i[1].slice(-1)&&(d*=2.55),e=_(i[2]),"%"==i[2].slice(-1)&&(e*=2.55),("deg"==i[0].slice(-3)||"°"==i[0].slice(-1))&&(b/=360),"hsla"==j[1].toLowerCase().slice(0,4)&&(f=_(i[3])),i[3]&&"%"==i[3].slice(-1)&&(f/=100),c.hsl2rgb(b,d,e,f)):(j={r:b,g:d,b:e,toString:g},j.hex="#"+(16777216|e|d<<8|b<<16).toString(16).slice(1),c.is(f,"finite")&&(j.opacity=f),j)):{r:-1,g:-1,b:-1,hex:"none",error:1,toString:g}},c),c.hsb=f(function(a,b,d){return c.hsb2rgb(a,b,d).hex}),c.hsl=f(function(a,b,d){return c.hsl2rgb(a,b,d).hex}),c.rgb=f(function(a,b,c){return"#"+(16777216|c|b<<8|a<<16).toString(16).slice(1)}),c.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||.75},c=this.hsb2rgb(b.h,b.s,b.b);return b.h+=.075,b.h>1&&(b.h=0,b.s-=.2,b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b})),c.hex},c.getColor.reset=function(){delete this.start},c.parsePathString=function(a){if(!a)return null;var b=Ab(a);if(b.arr)return Cb(b.arr);var d={a:7,c:6,h:1,l:2,m:2,r:4,q:4,s:4,t:2,v:1,z:0},e=[];return c.is(a,V)&&c.is(a[0],V)&&(e=Cb(a)),e.length||I(a).replace(hb,function(a,b,c){var f=[],g=b.toLowerCase();if(c.replace(jb,function(a,b){b&&f.push(+b)}),"m"==g&&f.length>2&&(e.push([b][E](f.splice(0,2))),g="l",b="m"==b?"l":"L"),"r"==g)e.push([b][E](f));else for(;f.length>=d[g]&&(e.push([b][E](f.splice(0,d[g]))),d[g]););}),e.toString=c._path2string,b.arr=Cb(e),e},c.parseTransformString=f(function(a){if(!a)return null;var b=[];return c.is(a,V)&&c.is(a[0],V)&&(b=Cb(a)),b.length||I(a).replace(ib,function(a,c,d){{var e=[];M.call(c)}d.replace(jb,function(a,b){b&&e.push(+b)}),b.push([c][E](e))}),b.toString=c._path2string,b});var Ab=function(a){var b=Ab.ps=Ab.ps||{};return b[a]?b[a].sleep=100:b[a]={sleep:100},setTimeout(function(){for(var c in b)b[z](c)&&c!=a&&(b[c].sleep--,!b[c].sleep&&delete b[c])}),b[a]};c.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=R(j,3),l=R(j,2),m=i*i,n=m*i,o=k*a+3*l*i*c+3*j*i*i*e+n*g,p=k*b+3*l*i*d+3*j*i*i*f+n*h,q=a+2*i*(c-a)+m*(e-2*c+a),r=b+2*i*(d-b)+m*(f-2*d+b),s=c+2*i*(e-c)+m*(g-2*e+c),t=d+2*i*(f-d)+m*(h-2*f+d),u=j*a+i*c,v=j*b+i*d,w=j*e+i*g,x=j*f+i*h,y=90-180*N.atan2(q-s,r-t)/S;return(q>s||t>r)&&(y+=180),{x:o,y:p,m:{x:q,y:r},n:{x:s,y:t},start:{x:u,y:v},end:{x:w,y:x},alpha:y}},c.bezierBBox=function(a,b,d,e,f,g,h,i){c.is(a,"array")||(a=[a,b,d,e,f,g,h,i]);var j=Jb.apply(null,a);return{x:j.min.x,y:j.min.y,x2:j.max.x,y2:j.max.y,width:j.max.x-j.min.x,height:j.max.y-j.min.y}},c.isPointInsideBBox=function(a,b,c){return b>=a.x&&b<=a.x2&&c>=a.y&&c<=a.y2},c.isBBoxIntersect=function(a,b){var d=c.isPointInsideBBox;return d(b,a.x,a.y)||d(b,a.x2,a.y)||d(b,a.x,a.y2)||d(b,a.x2,a.y2)||d(a,b.x,b.y)||d(a,b.x2,b.y)||d(a,b.x,b.y2)||d(a,b.x2,b.y2)||(a.x<b.x2&&a.x>b.x||b.x<a.x2&&b.x>a.x)&&(a.y<b.y2&&a.y>b.y||b.y<a.y2&&b.y>a.y)},c.pathIntersection=function(a,b){return n(a,b)},c.pathIntersectionNumber=function(a,b){return n(a,b,1)},c.isPointInsidePath=function(a,b,d){var e=c.pathBBox(a);return c.isPointInsideBBox(e,b,d)&&n(a,[["M",b,d],["H",e.x2+10]],1)%2==1},c._removedFactory=function(a){return function(){b("raphael.log",null,"Raphaël: you are calling to method “"+a+"” of removed object",a)}};var Bb=c.pathBBox=function(a){var b=Ab(a);if(b.bbox)return d(b.bbox);if(!a)return{x:0,y:0,width:0,height:0,x2:0,y2:0};a=Kb(a);for(var c,e=0,f=0,g=[],h=[],i=0,j=a.length;j>i;i++)if(c=a[i],"M"==c[0])e=c[1],f=c[2],g.push(e),h.push(f);else{var k=Jb(e,f,c[1],c[2],c[3],c[4],c[5],c[6]);g=g[E](k.min.x,k.max.x),h=h[E](k.min.y,k.max.y),e=c[5],f=c[6]}var l=P[D](0,g),m=P[D](0,h),n=O[D](0,g),o=O[D](0,h),p=n-l,q=o-m,r={x:l,y:m,x2:n,y2:o,width:p,height:q,cx:l+p/2,cy:m+q/2};return b.bbox=d(r),r},Cb=function(a){var b=d(a);return b.toString=c._path2string,b},Db=c._pathToRelative=function(a){var b=Ab(a);if(b.rel)return Cb(b.rel);c.is(a,V)&&c.is(a&&a[0],V)||(a=c.parsePathString(a));var d=[],e=0,f=0,g=0,h=0,i=0;"M"==a[0][0]&&(e=a[0][1],f=a[0][2],g=e,h=f,i++,d.push(["M",e,f]));for(var j=i,k=a.length;k>j;j++){var l=d[j]=[],m=a[j];if(m[0]!=M.call(m[0]))switch(l[0]=M.call(m[0]),l[0]){case"a":l[1]=m[1],l[2]=m[2],l[3]=m[3],l[4]=m[4],l[5]=m[5],l[6]=+(m[6]-e).toFixed(3),l[7]=+(m[7]-f).toFixed(3);break;case"v":l[1]=+(m[1]-f).toFixed(3);break;case"m":g=m[1],h=m[2];default:for(var n=1,o=m.length;o>n;n++)l[n]=+(m[n]-(n%2?e:f)).toFixed(3)}else{l=d[j]=[],"m"==m[0]&&(g=m[1]+e,h=m[2]+f);for(var p=0,q=m.length;q>p;p++)d[j][p]=m[p]}var r=d[j].length;switch(d[j][0]){case"z":e=g,f=h;break;case"h":e+=+d[j][r-1];break;case"v":f+=+d[j][r-1];break;default:e+=+d[j][r-2],f+=+d[j][r-1]}}return d.toString=c._path2string,b.rel=Cb(d),d},Eb=c._pathToAbsolute=function(a){var b=Ab(a);if(b.abs)return Cb(b.abs);if(c.is(a,V)&&c.is(a&&a[0],V)||(a=c.parsePathString(a)),!a||!a.length)return[["M",0,0]];var d=[],e=0,f=0,g=0,i=0,j=0;"M"==a[0][0]&&(e=+a[0][1],f=+a[0][2],g=e,i=f,j++,d[0]=["M",e,f]);for(var k,l,m=3==a.length&&"M"==a[0][0]&&"R"==a[1][0].toUpperCase()&&"Z"==a[2][0].toUpperCase(),n=j,o=a.length;o>n;n++){if(d.push(k=[]),l=a[n],l[0]!=bb.call(l[0]))switch(k[0]=bb.call(l[0]),k[0]){case"A":k[1]=l[1],k[2]=l[2],k[3]=l[3],k[4]=l[4],k[5]=l[5],k[6]=+(l[6]+e),k[7]=+(l[7]+f);break;case"V":k[1]=+l[1]+f;break;case"H":k[1]=+l[1]+e;break;case"R":for(var p=[e,f][E](l.slice(1)),q=2,r=p.length;r>q;q++)p[q]=+p[q]+e,p[++q]=+p[q]+f;d.pop(),d=d[E](h(p,m));break;case"M":g=+l[1]+e,i=+l[2]+f;default:for(q=1,r=l.length;r>q;q++)k[q]=+l[q]+(q%2?e:f)}else if("R"==l[0])p=[e,f][E](l.slice(1)),d.pop(),d=d[E](h(p,m)),k=["R"][E](l.slice(-2));else for(var s=0,t=l.length;t>s;s++)k[s]=l[s];switch(k[0]){case"Z":e=g,f=i;break;case"H":e=k[1];break;case"V":f=k[1];break;case"M":g=k[k.length-2],i=k[k.length-1];default:e=k[k.length-2],f=k[k.length-1]}}return d.toString=c._path2string,b.abs=Cb(d),d},Fb=function(a,b,c,d){return[a,b,c,d,c,d]},Gb=function(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]},Hb=function(a,b,c,d,e,g,h,i,j,k){var l,m=120*S/180,n=S/180*(+e||0),o=[],p=f(function(a,b,c){var d=a*N.cos(c)-b*N.sin(c),e=a*N.sin(c)+b*N.cos(c);return{x:d,y:e}});if(k)y=k[0],z=k[1],w=k[2],x=k[3];else{l=p(a,b,-n),a=l.x,b=l.y,l=p(i,j,-n),i=l.x,j=l.y;var q=(N.cos(S/180*e),N.sin(S/180*e),(a-i)/2),r=(b-j)/2,s=q*q/(c*c)+r*r/(d*d);s>1&&(s=N.sqrt(s),c=s*c,d=s*d);var t=c*c,u=d*d,v=(g==h?-1:1)*N.sqrt(Q((t*u-t*r*r-u*q*q)/(t*r*r+u*q*q))),w=v*c*r/d+(a+i)/2,x=v*-d*q/c+(b+j)/2,y=N.asin(((b-x)/d).toFixed(9)),z=N.asin(((j-x)/d).toFixed(9));y=w>a?S-y:y,z=w>i?S-z:z,0>y&&(y=2*S+y),0>z&&(z=2*S+z),h&&y>z&&(y-=2*S),!h&&z>y&&(z-=2*S)}var A=z-y;if(Q(A)>m){var B=z,C=i,D=j;z=y+m*(h&&z>y?1:-1),i=w+c*N.cos(z),j=x+d*N.sin(z),o=Hb(i,j,c,d,e,0,h,C,D,[z,B,w,x])}A=z-y;var F=N.cos(y),G=N.sin(y),H=N.cos(z),I=N.sin(z),K=N.tan(A/4),L=4/3*c*K,M=4/3*d*K,O=[a,b],P=[a+L*G,b-M*F],R=[i+L*I,j-M*H],T=[i,j];if(P[0]=2*O[0]-P[0],P[1]=2*O[1]-P[1],k)return[P,R,T][E](o);o=[P,R,T][E](o).join()[J](",");for(var U=[],V=0,W=o.length;W>V;V++)U[V]=V%2?p(o[V-1],o[V],n).y:p(o[V],o[V+1],n).x;return U},Ib=function(a,b,c,d,e,f,g,h,i){var j=1-i;return{x:R(j,3)*a+3*R(j,2)*i*c+3*j*i*i*e+R(i,3)*g,y:R(j,3)*b+3*R(j,2)*i*d+3*j*i*i*f+R(i,3)*h}},Jb=f(function(a,b,c,d,e,f,g,h){var i,j=e-2*c+a-(g-2*e+c),k=2*(c-a)-2*(e-c),l=a-c,m=(-k+N.sqrt(k*k-4*j*l))/2/j,n=(-k-N.sqrt(k*k-4*j*l))/2/j,o=[b,h],p=[a,g];return Q(m)>"1e12"&&(m=.5),Q(n)>"1e12"&&(n=.5),m>0&&1>m&&(i=Ib(a,b,c,d,e,f,g,h,m),p.push(i.x),o.push(i.y)),n>0&&1>n&&(i=Ib(a,b,c,d,e,f,g,h,n),p.push(i.x),o.push(i.y)),j=f-2*d+b-(h-2*f+d),k=2*(d-b)-2*(f-d),l=b-d,m=(-k+N.sqrt(k*k-4*j*l))/2/j,n=(-k-N.sqrt(k*k-4*j*l))/2/j,Q(m)>"1e12"&&(m=.5),Q(n)>"1e12"&&(n=.5),m>0&&1>m&&(i=Ib(a,b,c,d,e,f,g,h,m),p.push(i.x),o.push(i.y)),n>0&&1>n&&(i=Ib(a,b,c,d,e,f,g,h,n),p.push(i.x),o.push(i.y)),{min:{x:P[D](0,p),y:P[D](0,o)},max:{x:O[D](0,p),y:O[D](0,o)}}}),Kb=c._path2curve=f(function(a,b){var c=!b&&Ab(a);if(!b&&c.curve)return Cb(c.curve);for(var d=Eb(a),e=b&&Eb(b),f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},h=(function(a,b,c){var d,e,f={T:1,Q:1};if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];switch(!(a[0]in f)&&(b.qx=b.qy=null),a[0]){case"M":b.X=a[1],b.Y=a[2];break;case"A":a=["C"][E](Hb[D](0,[b.x,b.y][E](a.slice(1))));break;case"S":"C"==c||"S"==c?(d=2*b.x-b.bx,e=2*b.y-b.by):(d=b.x,e=b.y),a=["C",d,e][E](a.slice(1));break;case"T":"Q"==c||"T"==c?(b.qx=2*b.x-b.qx,b.qy=2*b.y-b.qy):(b.qx=b.x,b.qy=b.y),a=["C"][E](Gb(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1],b.qy=a[2],a=["C"][E](Gb(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][E](Fb(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][E](Fb(b.x,b.y,a[1],b.y));break;case"V":a=["C"][E](Fb(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][E](Fb(b.x,b.y,b.X,b.Y))}return a}),i=function(a,b){if(a[b].length>7){a[b].shift();for(var c=a[b];c.length;)k[b]="A",e&&(l[b]="A"),a.splice(b++,0,["C"][E](c.splice(0,6)));a.splice(b,1),p=O(d.length,e&&e.length||0)}},j=function(a,b,c,f,g){a&&b&&"M"==a[g][0]&&"M"!=b[g][0]&&(b.splice(g,0,["M",f.x,f.y]),c.bx=0,c.by=0,c.x=a[g][1],c.y=a[g][2],p=O(d.length,e&&e.length||0))},k=[],l=[],m="",n="",o=0,p=O(d.length,e&&e.length||0);p>o;o++){d[o]&&(m=d[o][0]),"C"!=m&&(k[o]=m,o&&(n=k[o-1])),d[o]=h(d[o],f,n),"A"!=k[o]&&"C"==m&&(k[o]="C"),i(d,o),e&&(e[o]&&(m=e[o][0]),"C"!=m&&(l[o]=m,o&&(n=l[o-1])),e[o]=h(e[o],g,n),"A"!=l[o]&&"C"==m&&(l[o]="C"),i(e,o)),j(d,e,f,g,o),j(e,d,g,f,o);var q=d[o],r=e&&e[o],s=q.length,t=e&&r.length;f.x=q[s-2],f.y=q[s-1],f.bx=_(q[s-4])||f.x,f.by=_(q[s-3])||f.y,g.bx=e&&(_(r[t-4])||g.x),g.by=e&&(_(r[t-3])||g.y),g.x=e&&r[t-2],g.y=e&&r[t-1]}return e||(c.curve=Cb(d)),e?[d,e]:d},null,Cb),Lb=(c._parseDots=f(function(a){for(var b=[],d=0,e=a.length;e>d;d++){var f={},g=a[d].match(/^([^:]*):?([\d\.]*)/);if(f.color=c.getRGB(g[1]),f.color.error)return null;f.color=f.color.hex,g[2]&&(f.offset=g[2]+"%"),b.push(f)}for(d=1,e=b.length-1;e>d;d++)if(!b[d].offset){for(var h=_(b[d-1].offset||0),i=0,j=d+1;e>j;j++)if(b[j].offset){i=b[j].offset;break}i||(i=100,j=e),i=_(i);for(var k=(i-h)/(j-d+1);j>d;d++)h+=k,b[d].offset=h+"%"}return b}),c._tear=function(a,b){a==b.top&&(b.top=a.prev),a==b.bottom&&(b.bottom=a.next),a.next&&(a.next.prev=a.prev),a.prev&&(a.prev.next=a.next)}),Mb=(c._tofront=function(a,b){b.top!==a&&(Lb(a,b),a.next=null,a.prev=b.top,b.top.next=a,b.top=a)},c._toback=function(a,b){b.bottom!==a&&(Lb(a,b),a.next=b.bottom,a.prev=null,b.bottom.prev=a,b.bottom=a)},c._insertafter=function(a,b,c){Lb(a,c),b==c.top&&(c.top=a),b.next&&(b.next.prev=a),a.next=b.next,a.prev=b,b.next=a},c._insertbefore=function(a,b,c){Lb(a,c),b==c.bottom&&(c.bottom=a),b.prev&&(b.prev.next=a),a.prev=b.prev,b.prev=a,a.next=b},c.toMatrix=function(a,b){var c=Bb(a),d={_:{transform:G},getBBox:function(){return c}};return Nb(d,b),d.matrix}),Nb=(c.transformPath=function(a,b){return rb(a,Mb(a,b))},c._extractTransform=function(a,b){if(null==b)return a._.transform;b=I(b).replace(/\.{3}|\u2026/g,a._.transform||G);var d=c.parseTransformString(b),e=0,f=0,g=0,h=1,i=1,j=a._,k=new o;if(j.transform=d||[],d)for(var l=0,m=d.length;m>l;l++){var n,p,q,r,s,t=d[l],u=t.length,v=I(t[0]).toLowerCase(),w=t[0]!=v,x=w?k.invert():0;"t"==v&&3==u?w?(n=x.x(0,0),p=x.y(0,0),q=x.x(t[1],t[2]),r=x.y(t[1],t[2]),k.translate(q-n,r-p)):k.translate(t[1],t[2]):"r"==v?2==u?(s=s||a.getBBox(1),k.rotate(t[1],s.x+s.width/2,s.y+s.height/2),e+=t[1]):4==u&&(w?(q=x.x(t[2],t[3]),r=x.y(t[2],t[3]),k.rotate(t[1],q,r)):k.rotate(t[1],t[2],t[3]),e+=t[1]):"s"==v?2==u||3==u?(s=s||a.getBBox(1),k.scale(t[1],t[u-1],s.x+s.width/2,s.y+s.height/2),h*=t[1],i*=t[u-1]):5==u&&(w?(q=x.x(t[3],t[4]),r=x.y(t[3],t[4]),k.scale(t[1],t[2],q,r)):k.scale(t[1],t[2],t[3],t[4]),h*=t[1],i*=t[2]):"m"==v&&7==u&&k.add(t[1],t[2],t[3],t[4],t[5],t[6]),j.dirtyT=1,a.matrix=k}a.matrix=k,j.sx=h,j.sy=i,j.deg=e,j.dx=f=k.e,j.dy=g=k.f,1==h&&1==i&&!e&&j.bbox?(j.bbox.x+=+f,j.bbox.y+=+g):j.dirtyT=1}),Ob=function(a){var b=a[0];switch(b.toLowerCase()){case"t":return[b,0,0];case"m":return[b,1,0,0,1,0,0];case"r":return 4==a.length?[b,0,a[2],a[3]]:[b,0];case"s":return 5==a.length?[b,1,1,a[3],a[4]]:3==a.length?[b,1,1]:[b,1]}},Pb=c._equaliseTransform=function(a,b){b=I(b).replace(/\.{3}|\u2026/g,a),a=c.parseTransformString(a)||[],b=c.parseTransformString(b)||[];
for(var d,e,f,g,h=O(a.length,b.length),i=[],j=[],k=0;h>k;k++){if(f=a[k]||Ob(b[k]),g=b[k]||Ob(f),f[0]!=g[0]||"r"==f[0].toLowerCase()&&(f[2]!=g[2]||f[3]!=g[3])||"s"==f[0].toLowerCase()&&(f[3]!=g[3]||f[4]!=g[4]))return;for(i[k]=[],j[k]=[],d=0,e=O(f.length,g.length);e>d;d++)d in f&&(i[k][d]=f[d]),d in g&&(j[k][d]=g[d])}return{from:i,to:j}};c._getContainer=function(a,b,d,e){var f;return f=null!=e||c.is(a,"object")?a:A.doc.getElementById(a),null!=f?f.tagName?null==b?{container:f,width:f.style.pixelWidth||f.offsetWidth,height:f.style.pixelHeight||f.offsetHeight}:{container:f,width:b,height:d}:{container:1,x:a,y:b,width:d,height:e}:void 0},c.pathToRelative=Db,c._engine={},c.path2curve=Kb,c.matrix=function(a,b,c,d,e,f){return new o(a,b,c,d,e,f)},function(a){function b(a){return a[0]*a[0]+a[1]*a[1]}function d(a){var c=N.sqrt(b(a));a[0]&&(a[0]/=c),a[1]&&(a[1]/=c)}a.add=function(a,b,c,d,e,f){var g,h,i,j,k=[[],[],[]],l=[[this.a,this.c,this.e],[this.b,this.d,this.f],[0,0,1]],m=[[a,c,e],[b,d,f],[0,0,1]];for(a&&a instanceof o&&(m=[[a.a,a.c,a.e],[a.b,a.d,a.f],[0,0,1]]),g=0;3>g;g++)for(h=0;3>h;h++){for(j=0,i=0;3>i;i++)j+=l[g][i]*m[i][h];k[g][h]=j}this.a=k[0][0],this.b=k[1][0],this.c=k[0][1],this.d=k[1][1],this.e=k[0][2],this.f=k[1][2]},a.invert=function(){var a=this,b=a.a*a.d-a.b*a.c;return new o(a.d/b,-a.b/b,-a.c/b,a.a/b,(a.c*a.f-a.d*a.e)/b,(a.b*a.e-a.a*a.f)/b)},a.clone=function(){return new o(this.a,this.b,this.c,this.d,this.e,this.f)},a.translate=function(a,b){this.add(1,0,0,1,a,b)},a.scale=function(a,b,c,d){null==b&&(b=a),(c||d)&&this.add(1,0,0,1,c,d),this.add(a,0,0,b,0,0),(c||d)&&this.add(1,0,0,1,-c,-d)},a.rotate=function(a,b,d){a=c.rad(a),b=b||0,d=d||0;var e=+N.cos(a).toFixed(9),f=+N.sin(a).toFixed(9);this.add(e,f,-f,e,b,d),this.add(1,0,0,1,-b,-d)},a.x=function(a,b){return a*this.a+b*this.c+this.e},a.y=function(a,b){return a*this.b+b*this.d+this.f},a.get=function(a){return+this[I.fromCharCode(97+a)].toFixed(4)},a.toString=function(){return c.svg?"matrix("+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)].join()+")":[this.get(0),this.get(2),this.get(1),this.get(3),0,0].join()},a.toFilter=function(){return"progid:DXImageTransform.Microsoft.Matrix(M11="+this.get(0)+", M12="+this.get(2)+", M21="+this.get(1)+", M22="+this.get(3)+", Dx="+this.get(4)+", Dy="+this.get(5)+", sizingmethod='auto expand')"},a.offset=function(){return[this.e.toFixed(4),this.f.toFixed(4)]},a.split=function(){var a={};a.dx=this.e,a.dy=this.f;var e=[[this.a,this.c],[this.b,this.d]];a.scalex=N.sqrt(b(e[0])),d(e[0]),a.shear=e[0][0]*e[1][0]+e[0][1]*e[1][1],e[1]=[e[1][0]-e[0][0]*a.shear,e[1][1]-e[0][1]*a.shear],a.scaley=N.sqrt(b(e[1])),d(e[1]),a.shear/=a.scaley;var f=-e[0][1],g=e[1][1];return 0>g?(a.rotate=c.deg(N.acos(g)),0>f&&(a.rotate=360-a.rotate)):a.rotate=c.deg(N.asin(f)),a.isSimple=!(+a.shear.toFixed(9)||a.scalex.toFixed(9)!=a.scaley.toFixed(9)&&a.rotate),a.isSuperSimple=!+a.shear.toFixed(9)&&a.scalex.toFixed(9)==a.scaley.toFixed(9)&&!a.rotate,a.noRotation=!+a.shear.toFixed(9)&&!a.rotate,a},a.toTransformString=function(a){var b=a||this[J]();return b.isSimple?(b.scalex=+b.scalex.toFixed(4),b.scaley=+b.scaley.toFixed(4),b.rotate=+b.rotate.toFixed(4),(b.dx||b.dy?"t"+[b.dx,b.dy]:G)+(1!=b.scalex||1!=b.scaley?"s"+[b.scalex,b.scaley,0,0]:G)+(b.rotate?"r"+[b.rotate,0,0]:G)):"m"+[this.get(0),this.get(1),this.get(2),this.get(3),this.get(4),this.get(5)]}}(o.prototype);var Qb=navigator.userAgent.match(/Version\/(.*?)\s/)||navigator.userAgent.match(/Chrome\/(\d+)/);v.safari="Apple Computer, Inc."==navigator.vendor&&(Qb&&Qb[1]<4||"iP"==navigator.platform.slice(0,2))||"Google Inc."==navigator.vendor&&Qb&&Qb[1]<8?function(){var a=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});setTimeout(function(){a.remove()})}:mb;for(var Rb=function(){this.returnValue=!1},Sb=function(){return this.originalEvent.preventDefault()},Tb=function(){this.cancelBubble=!0},Ub=function(){return this.originalEvent.stopPropagation()},Vb=function(a){var b=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,c=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft;return{x:a.clientX+c,y:a.clientY+b}},Wb=function(){return A.doc.addEventListener?function(a,b,c,d){var e=function(a){var b=Vb(a);return c.call(d,a,b.x,b.y)};if(a.addEventListener(b,e,!1),F&&L[b]){var f=function(b){for(var e=Vb(b),f=b,g=0,h=b.targetTouches&&b.targetTouches.length;h>g;g++)if(b.targetTouches[g].target==a){b=b.targetTouches[g],b.originalEvent=f,b.preventDefault=Sb,b.stopPropagation=Ub;break}return c.call(d,b,e.x,e.y)};a.addEventListener(L[b],f,!1)}return function(){return a.removeEventListener(b,e,!1),F&&L[b]&&a.removeEventListener(L[b],f,!1),!0}}:A.doc.attachEvent?function(a,b,c,d){var e=function(a){a=a||A.win.event;var b=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,e=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft,f=a.clientX+e,g=a.clientY+b;return a.preventDefault=a.preventDefault||Rb,a.stopPropagation=a.stopPropagation||Tb,c.call(d,a,f,g)};a.attachEvent("on"+b,e);var f=function(){return a.detachEvent("on"+b,e),!0};return f}:void 0}(),Xb=[],Yb=function(a){for(var c,d=a.clientX,e=a.clientY,f=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,g=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft,h=Xb.length;h--;){if(c=Xb[h],F&&a.touches){for(var i,j=a.touches.length;j--;)if(i=a.touches[j],i.identifier==c.el._drag.id){d=i.clientX,e=i.clientY,(a.originalEvent?a.originalEvent:a).preventDefault();break}}else a.preventDefault();var k,l=c.el.node,m=l.nextSibling,n=l.parentNode,o=l.style.display;A.win.opera&&n.removeChild(l),l.style.display="none",k=c.el.paper.getElementByPoint(d,e),l.style.display=o,A.win.opera&&(m?n.insertBefore(l,m):n.appendChild(l)),k&&b("raphael.drag.over."+c.el.id,c.el,k),d+=g,e+=f,b("raphael.drag.move."+c.el.id,c.move_scope||c.el,d-c.el._drag.x,e-c.el._drag.y,d,e,a)}},Zb=function(a){c.unmousemove(Yb).unmouseup(Zb);for(var d,e=Xb.length;e--;)d=Xb[e],d.el._drag={},b("raphael.drag.end."+d.el.id,d.end_scope||d.start_scope||d.move_scope||d.el,a);Xb=[]},$b=c.el={},_b=K.length;_b--;)!function(a){c[a]=$b[a]=function(b,d){return c.is(b,"function")&&(this.events=this.events||[],this.events.push({name:a,f:b,unbind:Wb(this.shape||this.node||A.doc,a,b,d||this)})),this},c["un"+a]=$b["un"+a]=function(b){for(var d=this.events||[],e=d.length;e--;)d[e].name!=a||!c.is(b,"undefined")&&d[e].f!=b||(d[e].unbind(),d.splice(e,1),!d.length&&delete this.events);return this}}(K[_b]);$b.data=function(a,d){var e=kb[this.id]=kb[this.id]||{};if(0==arguments.length)return e;if(1==arguments.length){if(c.is(a,"object")){for(var f in a)a[z](f)&&this.data(f,a[f]);return this}return b("raphael.data.get."+this.id,this,e[a],a),e[a]}return e[a]=d,b("raphael.data.set."+this.id,this,d,a),this},$b.removeData=function(a){return null==a?kb[this.id]={}:kb[this.id]&&delete kb[this.id][a],this},$b.getData=function(){return d(kb[this.id]||{})},$b.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)},$b.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};var ac=[];$b.drag=function(a,d,e,f,g,h){function i(i){(i.originalEvent||i).preventDefault();var j=i.clientX,k=i.clientY,l=A.doc.documentElement.scrollTop||A.doc.body.scrollTop,m=A.doc.documentElement.scrollLeft||A.doc.body.scrollLeft;if(this._drag.id=i.identifier,F&&i.touches)for(var n,o=i.touches.length;o--;)if(n=i.touches[o],this._drag.id=n.identifier,n.identifier==this._drag.id){j=n.clientX,k=n.clientY;break}this._drag.x=j+m,this._drag.y=k+l,!Xb.length&&c.mousemove(Yb).mouseup(Zb),Xb.push({el:this,move_scope:f,start_scope:g,end_scope:h}),d&&b.on("raphael.drag.start."+this.id,d),a&&b.on("raphael.drag.move."+this.id,a),e&&b.on("raphael.drag.end."+this.id,e),b("raphael.drag.start."+this.id,g||f||this,i.clientX+m,i.clientY+l,i)}return this._drag={},ac.push({el:this,start:i}),this.mousedown(i),this},$b.onDragOver=function(a){a?b.on("raphael.drag.over."+this.id,a):b.unbind("raphael.drag.over."+this.id)},$b.undrag=function(){for(var a=ac.length;a--;)ac[a].el==this&&(this.unmousedown(ac[a].start),ac.splice(a,1),b.unbind("raphael.drag.*."+this.id));!ac.length&&c.unmousemove(Yb).unmouseup(Zb),Xb=[]},v.circle=function(a,b,d){var e=c._engine.circle(this,a||0,b||0,d||0);return this.__set__&&this.__set__.push(e),e},v.rect=function(a,b,d,e,f){var g=c._engine.rect(this,a||0,b||0,d||0,e||0,f||0);return this.__set__&&this.__set__.push(g),g},v.ellipse=function(a,b,d,e){var f=c._engine.ellipse(this,a||0,b||0,d||0,e||0);return this.__set__&&this.__set__.push(f),f},v.path=function(a){a&&!c.is(a,U)&&!c.is(a[0],V)&&(a+=G);var b=c._engine.path(c.format[D](c,arguments),this);return this.__set__&&this.__set__.push(b),b},v.image=function(a,b,d,e,f){var g=c._engine.image(this,a||"about:blank",b||0,d||0,e||0,f||0);return this.__set__&&this.__set__.push(g),g},v.text=function(a,b,d){var e=c._engine.text(this,a||0,b||0,I(d));return this.__set__&&this.__set__.push(e),e},v.set=function(a){!c.is(a,"array")&&(a=Array.prototype.splice.call(arguments,0,arguments.length));var b=new mc(a);return this.__set__&&this.__set__.push(b),b.paper=this,b.type="set",b},v.setStart=function(a){this.__set__=a||this.set()},v.setFinish=function(){var a=this.__set__;return delete this.__set__,a},v.getSize=function(){var a=this.canvas.parentNode;return{width:a.offsetWidth,height:a.offsetHeight}},v.setSize=function(a,b){return c._engine.setSize.call(this,a,b)},v.setViewBox=function(a,b,d,e,f){return c._engine.setViewBox.call(this,a,b,d,e,f)},v.top=v.bottom=null,v.raphael=c;var bc=function(a){var b=a.getBoundingClientRect(),c=a.ownerDocument,d=c.body,e=c.documentElement,f=e.clientTop||d.clientTop||0,g=e.clientLeft||d.clientLeft||0,h=b.top+(A.win.pageYOffset||e.scrollTop||d.scrollTop)-f,i=b.left+(A.win.pageXOffset||e.scrollLeft||d.scrollLeft)-g;return{y:h,x:i}};v.getElementByPoint=function(a,b){var c=this,d=c.canvas,e=A.doc.elementFromPoint(a,b);if(A.win.opera&&"svg"==e.tagName){var f=bc(d),g=d.createSVGRect();g.x=a-f.x,g.y=b-f.y,g.width=g.height=1;var h=d.getIntersectionList(g,null);h.length&&(e=h[h.length-1])}if(!e)return null;for(;e.parentNode&&e!=d.parentNode&&!e.raphael;)e=e.parentNode;return e==c.canvas.parentNode&&(e=d),e=e&&e.raphael?c.getById(e.raphaelid):null},v.getElementsByBBox=function(a){var b=this.set();return this.forEach(function(d){c.isBBoxIntersect(d.getBBox(),a)&&b.push(d)}),b},v.getById=function(a){for(var b=this.bottom;b;){if(b.id==a)return b;b=b.next}return null},v.forEach=function(a,b){for(var c=this.bottom;c;){if(a.call(b,c)===!1)return this;c=c.next}return this},v.getElementsByPoint=function(a,b){var c=this.set();return this.forEach(function(d){d.isPointInside(a,b)&&c.push(d)}),c},$b.isPointInside=function(a,b){var d=this.realPath=qb[this.type](this);return this.attr("transform")&&this.attr("transform").length&&(d=c.transformPath(d,this.attr("transform"))),c.isPointInsidePath(d,a,b)},$b.getBBox=function(a){if(this.removed)return{};var b=this._;return a?((b.dirty||!b.bboxwt)&&(this.realPath=qb[this.type](this),b.bboxwt=Bb(this.realPath),b.bboxwt.toString=p,b.dirty=0),b.bboxwt):((b.dirty||b.dirtyT||!b.bbox)&&((b.dirty||!this.realPath)&&(b.bboxwt=0,this.realPath=qb[this.type](this)),b.bbox=Bb(rb(this.realPath,this.matrix)),b.bbox.toString=p,b.dirty=b.dirtyT=0),b.bbox)},$b.clone=function(){if(this.removed)return null;var a=this.paper[this.type]().attr(this.attr());return this.__set__&&this.__set__.push(a),a},$b.glow=function(a){if("text"==this.type)return null;a=a||{};var b={width:(a.width||10)+(+this.attr("stroke-width")||1),fill:a.fill||!1,opacity:a.opacity||.5,offsetx:a.offsetx||0,offsety:a.offsety||0,color:a.color||"#000"},c=b.width/2,d=this.paper,e=d.set(),f=this.realPath||qb[this.type](this);f=this.matrix?rb(f,this.matrix):f;for(var g=1;c+1>g;g++)e.push(d.path(f).attr({stroke:b.color,fill:b.fill?b.color:"none","stroke-linejoin":"round","stroke-linecap":"round","stroke-width":+(b.width/c*g).toFixed(3),opacity:+(b.opacity/c).toFixed(3)}));return e.insertBefore(this).translate(b.offsetx,b.offsety)};var cc=function(a,b,d,e,f,g,h,i,l){return null==l?j(a,b,d,e,f,g,h,i):c.findDotsAtSegment(a,b,d,e,f,g,h,i,k(a,b,d,e,f,g,h,i,l))},dc=function(a,b){return function(d,e,f){d=Kb(d);for(var g,h,i,j,k,l="",m={},n=0,o=0,p=d.length;p>o;o++){if(i=d[o],"M"==i[0])g=+i[1],h=+i[2];else{if(j=cc(g,h,i[1],i[2],i[3],i[4],i[5],i[6]),n+j>e){if(b&&!m.start){if(k=cc(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),l+=["C"+k.start.x,k.start.y,k.m.x,k.m.y,k.x,k.y],f)return l;m.start=l,l=["M"+k.x,k.y+"C"+k.n.x,k.n.y,k.end.x,k.end.y,i[5],i[6]].join(),n+=j,g=+i[5],h=+i[6];continue}if(!a&&!b)return k=cc(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n),{x:k.x,y:k.y,alpha:k.alpha}}n+=j,g=+i[5],h=+i[6]}l+=i.shift()+i}return m.end=l,k=a?n:b?m:c.findDotsAtSegment(g,h,i[0],i[1],i[2],i[3],i[4],i[5],1),k.alpha&&(k={x:k.x,y:k.y,alpha:k.alpha}),k}},ec=dc(1),fc=dc(),gc=dc(0,1);c.getTotalLength=ec,c.getPointAtLength=fc,c.getSubpath=function(a,b,c){if(this.getTotalLength(a)-c<1e-6)return gc(a,b).end;var d=gc(a,c,1);return b?gc(d,b).end:d},$b.getTotalLength=function(){var a=this.getPath();if(a)return this.node.getTotalLength?this.node.getTotalLength():ec(a)},$b.getPointAtLength=function(a){var b=this.getPath();if(b)return fc(b,a)},$b.getPath=function(){var a,b=c._getPath[this.type];if("text"!=this.type&&"set"!=this.type)return b&&(a=b(this)),a},$b.getSubpath=function(a,b){var d=this.getPath();if(d)return c.getSubpath(d,a,b)};var hc=c.easing_formulas={linear:function(a){return a},"<":function(a){return R(a,1.7)},">":function(a){return R(a,.48)},"<>":function(a){var b=.48-a/1.04,c=N.sqrt(.1734+b*b),d=c-b,e=R(Q(d),1/3)*(0>d?-1:1),f=-c-b,g=R(Q(f),1/3)*(0>f?-1:1),h=e+g+.5;return 3*(1-h)*h*h+h*h*h},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a-=1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){return a==!!a?a:R(2,-10*a)*N.sin(2*(a-.075)*S/.3)+1},bounce:function(a){var b,c=7.5625,d=2.75;return 1/d>a?b=c*a*a:2/d>a?(a-=1.5/d,b=c*a*a+.75):2.5/d>a?(a-=2.25/d,b=c*a*a+.9375):(a-=2.625/d,b=c*a*a+.984375),b}};hc.easeIn=hc["ease-in"]=hc["<"],hc.easeOut=hc["ease-out"]=hc[">"],hc.easeInOut=hc["ease-in-out"]=hc["<>"],hc["back-in"]=hc.backIn,hc["back-out"]=hc.backOut;var ic=[],jc=a.requestAnimationFrame||a.webkitRequestAnimationFrame||a.mozRequestAnimationFrame||a.oRequestAnimationFrame||a.msRequestAnimationFrame||function(a){setTimeout(a,16)},kc=function(){for(var a=+new Date,d=0;d<ic.length;d++){var e=ic[d];if(!e.el.removed&&!e.paused){var f,g,h=a-e.start,i=e.ms,j=e.easing,k=e.from,l=e.diff,m=e.to,n=(e.t,e.el),o={},p={};if(e.initstatus?(h=(e.initstatus*e.anim.top-e.prev)/(e.percent-e.prev)*i,e.status=e.initstatus,delete e.initstatus,e.stop&&ic.splice(d--,1)):e.status=(e.prev+(e.percent-e.prev)*(h/i))/e.anim.top,!(0>h))if(i>h){var q=j(h/i);for(var r in k)if(k[z](r)){switch(db[r]){case T:f=+k[r]+q*i*l[r];break;case"colour":f="rgb("+[lc($(k[r].r+q*i*l[r].r)),lc($(k[r].g+q*i*l[r].g)),lc($(k[r].b+q*i*l[r].b))].join(",")+")";break;case"path":f=[];for(var t=0,u=k[r].length;u>t;t++){f[t]=[k[r][t][0]];for(var v=1,w=k[r][t].length;w>v;v++)f[t][v]=+k[r][t][v]+q*i*l[r][t][v];f[t]=f[t].join(H)}f=f.join(H);break;case"transform":if(l[r].real)for(f=[],t=0,u=k[r].length;u>t;t++)for(f[t]=[k[r][t][0]],v=1,w=k[r][t].length;w>v;v++)f[t][v]=k[r][t][v]+q*i*l[r][t][v];else{var x=function(a){return+k[r][a]+q*i*l[r][a]};f=[["m",x(0),x(1),x(2),x(3),x(4),x(5)]]}break;case"csv":if("clip-rect"==r)for(f=[],t=4;t--;)f[t]=+k[r][t]+q*i*l[r][t];break;default:var y=[][E](k[r]);for(f=[],t=n.paper.customAttributes[r].length;t--;)f[t]=+y[t]+q*i*l[r][t]}o[r]=f}n.attr(o),function(a,c,d){setTimeout(function(){b("raphael.anim.frame."+a,c,d)})}(n.id,n,e.anim)}else{if(function(a,d,e){setTimeout(function(){b("raphael.anim.frame."+d.id,d,e),b("raphael.anim.finish."+d.id,d,e),c.is(a,"function")&&a.call(d)})}(e.callback,n,e.anim),n.attr(m),ic.splice(d--,1),e.repeat>1&&!e.next){for(g in m)m[z](g)&&(p[g]=e.totalOrigin[g]);e.el.attr(p),s(e.anim,e.el,e.anim.percents[0],null,e.totalOrigin,e.repeat-1)}e.next&&!e.stop&&s(e.anim,e.el,e.next,null,e.totalOrigin,e.repeat)}}}c.svg&&n&&n.paper&&n.paper.safari(),ic.length&&jc(kc)},lc=function(a){return a>255?255:0>a?0:a};$b.animateWith=function(a,b,d,e,f,g){var h=this;if(h.removed)return g&&g.call(h),h;var i=d instanceof r?d:c.animation(d,e,f,g);s(i,h,i.percents[0],null,h.attr());for(var j=0,k=ic.length;k>j;j++)if(ic[j].anim==b&&ic[j].el==a){ic[k-1].start=ic[j].start;break}return h},$b.onAnimation=function(a){return a?b.on("raphael.anim.frame."+this.id,a):b.unbind("raphael.anim.frame."+this.id),this},r.prototype.delay=function(a){var b=new r(this.anim,this.ms);return b.times=this.times,b.del=+a||0,b},r.prototype.repeat=function(a){var b=new r(this.anim,this.ms);return b.del=this.del,b.times=N.floor(O(a,0))||1,b},c.animation=function(a,b,d,e){if(a instanceof r)return a;(c.is(d,"function")||!d)&&(e=e||d||null,d=null),a=Object(a),b=+b||0;var f,g,h={};for(g in a)a[z](g)&&_(g)!=g&&_(g)+"%"!=g&&(f=!0,h[g]=a[g]);if(f)return d&&(h.easing=d),e&&(h.callback=e),new r({100:h},b);if(e){var i=0;for(var j in a){var k=ab(j);a[z](j)&&k>i&&(i=k)}i+="%",!a[i].callback&&(a[i].callback=e)}return new r(a,b)},$b.animate=function(a,b,d,e){var f=this;if(f.removed)return e&&e.call(f),f;var g=a instanceof r?a:c.animation(a,b,d,e);return s(g,f,g.percents[0],null,f.attr()),f},$b.setTime=function(a,b){return a&&null!=b&&this.status(a,P(b,a.ms)/a.ms),this},$b.status=function(a,b){var c,d,e=[],f=0;if(null!=b)return s(a,this,-1,P(b,1)),this;for(c=ic.length;c>f;f++)if(d=ic[f],d.el.id==this.id&&(!a||d.anim==a)){if(a)return d.status;e.push({anim:d.anim,status:d.status})}return a?0:e},$b.pause=function(a){for(var c=0;c<ic.length;c++)ic[c].el.id!=this.id||a&&ic[c].anim!=a||b("raphael.anim.pause."+this.id,this,ic[c].anim)!==!1&&(ic[c].paused=!0);return this},$b.resume=function(a){for(var c=0;c<ic.length;c++)if(ic[c].el.id==this.id&&(!a||ic[c].anim==a)){var d=ic[c];b("raphael.anim.resume."+this.id,this,d.anim)!==!1&&(delete d.paused,this.status(d.anim,d.status))}return this},$b.stop=function(a){for(var c=0;c<ic.length;c++)ic[c].el.id!=this.id||a&&ic[c].anim!=a||b("raphael.anim.stop."+this.id,this,ic[c].anim)!==!1&&ic.splice(c--,1);return this},b.on("raphael.remove",t),b.on("raphael.clear",t),$b.toString=function(){return"Raphaël’s object"};var mc=function(a){if(this.items=[],this.length=0,this.type="set",a)for(var b=0,c=a.length;c>b;b++)!a[b]||a[b].constructor!=$b.constructor&&a[b].constructor!=mc||(this[this.items.length]=this.items[this.items.length]=a[b],this.length++)},nc=mc.prototype;nc.push=function(){for(var a,b,c=0,d=arguments.length;d>c;c++)a=arguments[c],!a||a.constructor!=$b.constructor&&a.constructor!=mc||(b=this.items.length,this[b]=this.items[b]=a,this.length++);return this},nc.pop=function(){return this.length&&delete this[this.length--],this.items.pop()},nc.forEach=function(a,b){for(var c=0,d=this.items.length;d>c;c++)if(a.call(b,this.items[c],c)===!1)return this;return this};for(var oc in $b)$b[z](oc)&&(nc[oc]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a][D](c,b)})}}(oc));return nc.attr=function(a,b){if(a&&c.is(a,V)&&c.is(a[0],"object"))for(var d=0,e=a.length;e>d;d++)this.items[d].attr(a[d]);else for(var f=0,g=this.items.length;g>f;f++)this.items[f].attr(a,b);return this},nc.clear=function(){for(;this.length;)this.pop()},nc.splice=function(a,b){a=0>a?O(this.length+a,0):a,b=O(0,P(this.length-a,b));var c,d=[],e=[],f=[];for(c=2;c<arguments.length;c++)f.push(arguments[c]);for(c=0;b>c;c++)e.push(this[a+c]);for(;c<this.length-a;c++)d.push(this[a+c]);var g=f.length;for(c=0;c<g+d.length;c++)this.items[a+c]=this[a+c]=g>c?f[c]:d[c-g];for(c=this.items.length=this.length-=b-g;this[c];)delete this[c++];return new mc(e)},nc.exclude=function(a){for(var b=0,c=this.length;c>b;b++)if(this[b]==a)return this.splice(b,1),!0},nc.animate=function(a,b,d,e){(c.is(d,"function")||!d)&&(e=d||null);var f,g,h=this.items.length,i=h,j=this;if(!h)return this;e&&(g=function(){!--h&&e.call(j)}),d=c.is(d,U)?d:g;var k=c.animation(a,b,d,g);for(f=this.items[--i].animate(k);i--;)this.items[i]&&!this.items[i].removed&&this.items[i].animateWith(f,k,k),this.items[i]&&!this.items[i].removed||h--;return this},nc.insertAfter=function(a){for(var b=this.items.length;b--;)this.items[b].insertAfter(a);return this},nc.getBBox=function(){for(var a=[],b=[],c=[],d=[],e=this.items.length;e--;)if(!this.items[e].removed){var f=this.items[e].getBBox();a.push(f.x),b.push(f.y),c.push(f.x+f.width),d.push(f.y+f.height)}return a=P[D](0,a),b=P[D](0,b),c=O[D](0,c),d=O[D](0,d),{x:a,y:b,x2:c,y2:d,width:c-a,height:d-b}},nc.clone=function(a){a=this.paper.set();for(var b=0,c=this.items.length;c>b;b++)a.push(this.items[b].clone());return a},nc.toString=function(){return"Raphaël‘s set"},nc.glow=function(a){var b=this.paper.set();return this.forEach(function(c){var d=c.glow(a);null!=d&&d.forEach(function(a){b.push(a)})}),b},nc.isPointInside=function(a,b){var c=!1;return this.forEach(function(d){return d.isPointInside(a,b)?(c=!0,!1):void 0}),c},c.registerFont=function(a){if(!a.face)return a;this.fonts=this.fonts||{};var b={w:a.w,face:{},glyphs:{}},c=a.face["font-family"];for(var d in a.face)a.face[z](d)&&(b.face[d]=a.face[d]);if(this.fonts[c]?this.fonts[c].push(b):this.fonts[c]=[b],!a.svg){b.face["units-per-em"]=ab(a.face["units-per-em"],10);for(var e in a.glyphs)if(a.glyphs[z](e)){var f=a.glyphs[e];if(b.glyphs[e]={w:f.w,k:{},d:f.d&&"M"+f.d.replace(/[mlcxtrv]/g,function(a){return{l:"L",c:"C",x:"z",t:"m",r:"l",v:"c"}[a]||"M"})+"z"},f.k)for(var g in f.k)f[z](g)&&(b.glyphs[e].k[g]=f.k[g])}}return a},v.getFont=function(a,b,d,e){if(e=e||"normal",d=d||"normal",b=+b||{normal:400,bold:700,lighter:300,bolder:800}[b]||400,c.fonts){var f=c.fonts[a];if(!f){var g=new RegExp("(^|\\s)"+a.replace(/[^\w\d\s+!~.:_-]/g,G)+"(\\s|$)","i");for(var h in c.fonts)if(c.fonts[z](h)&&g.test(h)){f=c.fonts[h];break}}var i;if(f)for(var j=0,k=f.length;k>j&&(i=f[j],i.face["font-weight"]!=b||i.face["font-style"]!=d&&i.face["font-style"]||i.face["font-stretch"]!=e);j++);return i}},v.print=function(a,b,d,e,f,g,h,i){g=g||"middle",h=O(P(h||0,1),-1),i=O(P(i||1,3),1);var j,k=I(d)[J](G),l=0,m=0,n=G;if(c.is(e,"string")&&(e=this.getFont(e)),e){j=(f||16)/e.face["units-per-em"];for(var o=e.face.bbox[J](w),p=+o[0],q=o[3]-o[1],r=0,s=+o[1]+("baseline"==g?q+ +e.face.descent:q/2),t=0,u=k.length;u>t;t++){if("\n"==k[t])l=0,x=0,m=0,r+=q*i;else{var v=m&&e.glyphs[k[t-1]]||{},x=e.glyphs[k[t]];l+=m?(v.w||e.w)+(v.k&&v.k[k[t]]||0)+e.w*h:0,m=1}x&&x.d&&(n+=c.transformPath(x.d,["t",l*j,r*j,"s",j,j,p,s,"t",(a-p)/j,(b-s)/j]))}}return this.path(n).attr({fill:"#000",stroke:"none"})},v.add=function(a){if(c.is(a,"array"))for(var b,d=this.set(),e=0,f=a.length;f>e;e++)b=a[e]||{},x[z](b.type)&&d.push(this[b.type]().attr(b));return d},c.format=function(a,b){var d=c.is(b,V)?[0][E](b):arguments;return a&&c.is(a,U)&&d.length-1&&(a=a.replace(y,function(a,b){return null==d[++b]?G:d[b]})),a||G},c.fullfill=function(){var a=/\{([^\}]+)\}/g,b=/(?:(?:^|\.)(.+?)(?=\[|\.|$|\()|\[('|")(.+?)\2\])(\(\))?/g,c=function(a,c,d){var e=d;return c.replace(b,function(a,b,c,d,f){b=b||d,e&&(b in e&&(e=e[b]),"function"==typeof e&&f&&(e=e()))}),e=(null==e||e==d?a:e)+""};return function(b,d){return String(b).replace(a,function(a,b){return c(a,b,d)})}}(),c.ninja=function(){return B.was?A.win.Raphael=B.is:delete Raphael,c},c.st=nc,b.on("raphael.DOMload",function(){u=!0}),function(a,b,d){function e(){/in/.test(a.readyState)?setTimeout(e,9):c.eve("raphael.DOMload")}null==a.readyState&&a.addEventListener&&(a.addEventListener(b,d=function(){a.removeEventListener(b,d,!1),a.readyState="complete"},!1),a.readyState="loading"),e()}(document,"DOMContentLoaded"),function(){if(c.svg){var a="hasOwnProperty",b=String,d=parseFloat,e=parseInt,f=Math,g=f.max,h=f.abs,i=f.pow,j=/[, ]+/,k=c.eve,l="",m=" ",n="http://www.w3.org/1999/xlink",o={block:"M5,0 0,2.5 5,5z",classic:"M5,0 0,2.5 5,5 3.5,3 3.5,2z",diamond:"M2.5,0 5,2.5 2.5,5 0,2.5z",open:"M6,1 1,3.5 6,6",oval:"M2.5,0A2.5,2.5,0,0,1,2.5,5 2.5,2.5,0,0,1,2.5,0z"},p={};c.toString=function(){return"Your browser supports SVG.\nYou are running Raphaël "+this.version};var q=function(d,e){if(e){"string"==typeof d&&(d=q(d));for(var f in e)e[a](f)&&("xlink:"==f.substring(0,6)?d.setAttributeNS(n,f.substring(6),b(e[f])):d.setAttribute(f,b(e[f])))}else d=c._g.doc.createElementNS("http://www.w3.org/2000/svg",d),d.style&&(d.style.webkitTapHighlightColor="rgba(0,0,0,0)");return d},r=function(a,e){var j="linear",k=a.id+e,m=.5,n=.5,o=a.node,p=a.paper,r=o.style,s=c._g.doc.getElementById(k);if(!s){if(e=b(e).replace(c._radial_gradient,function(a,b,c){if(j="radial",b&&c){m=d(b),n=d(c);var e=2*(n>.5)-1;i(m-.5,2)+i(n-.5,2)>.25&&(n=f.sqrt(.25-i(m-.5,2))*e+.5)&&.5!=n&&(n=n.toFixed(5)-1e-5*e)}return l}),e=e.split(/\s*\-\s*/),"linear"==j){var t=e.shift();if(t=-d(t),isNaN(t))return null;var u=[0,0,f.cos(c.rad(t)),f.sin(c.rad(t))],v=1/(g(h(u[2]),h(u[3]))||1);u[2]*=v,u[3]*=v,u[2]<0&&(u[0]=-u[2],u[2]=0),u[3]<0&&(u[1]=-u[3],u[3]=0)}var w=c._parseDots(e);if(!w)return null;if(k=k.replace(/[\(\)\s,\xb0#]/g,"_"),a.gradient&&k!=a.gradient.id&&(p.defs.removeChild(a.gradient),delete a.gradient),!a.gradient){s=q(j+"Gradient",{id:k}),a.gradient=s,q(s,"radial"==j?{fx:m,fy:n}:{x1:u[0],y1:u[1],x2:u[2],y2:u[3],gradientTransform:a.matrix.invert()}),p.defs.appendChild(s);for(var x=0,y=w.length;y>x;x++)s.appendChild(q("stop",{offset:w[x].offset?w[x].offset:x?"100%":"0%","stop-color":w[x].color||"#fff"}))}}return q(o,{fill:"url('"+document.location+"#"+k+"')",opacity:1,"fill-opacity":1}),r.fill=l,r.opacity=1,r.fillOpacity=1,1},s=function(a){var b=a.getBBox(1);q(a.pattern,{patternTransform:a.matrix.invert()+" translate("+b.x+","+b.y+")"})},t=function(d,e,f){if("path"==d.type){for(var g,h,i,j,k,m=b(e).toLowerCase().split("-"),n=d.paper,r=f?"end":"start",s=d.node,t=d.attrs,u=t["stroke-width"],v=m.length,w="classic",x=3,y=3,z=5;v--;)switch(m[v]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":w=m[v];break;case"wide":y=5;break;case"narrow":y=2;break;case"long":x=5;break;case"short":x=2}if("open"==w?(x+=2,y+=2,z+=2,i=1,j=f?4:1,k={fill:"none",stroke:t.stroke}):(j=i=x/2,k={fill:t.stroke,stroke:"none"}),d._.arrows?f?(d._.arrows.endPath&&p[d._.arrows.endPath]--,d._.arrows.endMarker&&p[d._.arrows.endMarker]--):(d._.arrows.startPath&&p[d._.arrows.startPath]--,d._.arrows.startMarker&&p[d._.arrows.startMarker]--):d._.arrows={},"none"!=w){var A="raphael-marker-"+w,B="raphael-marker-"+r+w+x+y+"-obj"+d.id;c._g.doc.getElementById(A)?p[A]++:(n.defs.appendChild(q(q("path"),{"stroke-linecap":"round",d:o[w],id:A})),p[A]=1);var C,D=c._g.doc.getElementById(B);D?(p[B]++,C=D.getElementsByTagName("use")[0]):(D=q(q("marker"),{id:B,markerHeight:y,markerWidth:x,orient:"auto",refX:j,refY:y/2}),C=q(q("use"),{"xlink:href":"#"+A,transform:(f?"rotate(180 "+x/2+" "+y/2+") ":l)+"scale("+x/z+","+y/z+")","stroke-width":(1/((x/z+y/z)/2)).toFixed(4)}),D.appendChild(C),n.defs.appendChild(D),p[B]=1),q(C,k);var E=i*("diamond"!=w&&"oval"!=w);f?(g=d._.arrows.startdx*u||0,h=c.getTotalLength(t.path)-E*u):(g=E*u,h=c.getTotalLength(t.path)-(d._.arrows.enddx*u||0)),k={},k["marker-"+r]="url(#"+B+")",(h||g)&&(k.d=c.getSubpath(t.path,g,h)),q(s,k),d._.arrows[r+"Path"]=A,d._.arrows[r+"Marker"]=B,d._.arrows[r+"dx"]=E,d._.arrows[r+"Type"]=w,d._.arrows[r+"String"]=e}else f?(g=d._.arrows.startdx*u||0,h=c.getTotalLength(t.path)-g):(g=0,h=c.getTotalLength(t.path)-(d._.arrows.enddx*u||0)),d._.arrows[r+"Path"]&&q(s,{d:c.getSubpath(t.path,g,h)}),delete d._.arrows[r+"Path"],delete d._.arrows[r+"Marker"],delete d._.arrows[r+"dx"],delete d._.arrows[r+"Type"],delete d._.arrows[r+"String"];for(k in p)if(p[a](k)&&!p[k]){var F=c._g.doc.getElementById(k);F&&F.parentNode.removeChild(F)}}},u={"":[0],none:[0],"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},v=function(a,c,d){if(c=u[b(c).toLowerCase()]){for(var e=a.attrs["stroke-width"]||"1",f={round:e,square:e,butt:0}[a.attrs["stroke-linecap"]||d["stroke-linecap"]]||0,g=[],h=c.length;h--;)g[h]=c[h]*e+(h%2?1:-1)*f;q(a.node,{"stroke-dasharray":g.join(",")})}},w=function(d,f){var i=d.node,k=d.attrs,m=i.style.visibility;i.style.visibility="hidden";for(var o in f)if(f[a](o)){if(!c._availableAttrs[a](o))continue;var p=f[o];switch(k[o]=p,o){case"blur":d.blur(p);break;case"title":var u=i.getElementsByTagName("title");if(u.length&&(u=u[0]))u.firstChild.nodeValue=p;else{u=q("title");var w=c._g.doc.createTextNode(p);u.appendChild(w),i.appendChild(u)}break;case"href":case"target":var x=i.parentNode;if("a"!=x.tagName.toLowerCase()){var z=q("a");x.insertBefore(z,i),z.appendChild(i),x=z}"target"==o?x.setAttributeNS(n,"show","blank"==p?"new":p):x.setAttributeNS(n,o,p);break;case"cursor":i.style.cursor=p;break;case"transform":d.transform(p);break;case"arrow-start":t(d,p);break;case"arrow-end":t(d,p,1);break;case"clip-rect":var A=b(p).split(j);if(4==A.length){d.clip&&d.clip.parentNode.parentNode.removeChild(d.clip.parentNode);var B=q("clipPath"),C=q("rect");B.id=c.createUUID(),q(C,{x:A[0],y:A[1],width:A[2],height:A[3]}),B.appendChild(C),d.paper.defs.appendChild(B),q(i,{"clip-path":"url(#"+B.id+")"}),d.clip=C}if(!p){var D=i.getAttribute("clip-path");if(D){var E=c._g.doc.getElementById(D.replace(/(^url\(#|\)$)/g,l));E&&E.parentNode.removeChild(E),q(i,{"clip-path":l}),delete d.clip}}break;case"path":"path"==d.type&&(q(i,{d:p?k.path=c._pathToAbsolute(p):"M0,0"}),d._.dirty=1,d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1)));break;case"width":if(i.setAttribute(o,p),d._.dirty=1,!k.fx)break;o="x",p=k.x;case"x":k.fx&&(p=-k.x-(k.width||0));case"rx":if("rx"==o&&"rect"==d.type)break;case"cx":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"height":if(i.setAttribute(o,p),d._.dirty=1,!k.fy)break;o="y",p=k.y;case"y":k.fy&&(p=-k.y-(k.height||0));case"ry":if("ry"==o&&"rect"==d.type)break;case"cy":i.setAttribute(o,p),d.pattern&&s(d),d._.dirty=1;break;case"r":"rect"==d.type?q(i,{rx:p,ry:p}):i.setAttribute(o,p),d._.dirty=1;break;case"src":"image"==d.type&&i.setAttributeNS(n,"href",p);break;case"stroke-width":(1!=d._.sx||1!=d._.sy)&&(p/=g(h(d._.sx),h(d._.sy))||1),i.setAttribute(o,p),k["stroke-dasharray"]&&v(d,k["stroke-dasharray"],f),d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"stroke-dasharray":v(d,p,f);break;case"fill":var F=b(p).match(c._ISURL);if(F){B=q("pattern");var G=q("image");B.id=c.createUUID(),q(B,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1}),q(G,{x:0,y:0,"xlink:href":F[1]}),B.appendChild(G),function(a){c._preload(F[1],function(){var b=this.offsetWidth,c=this.offsetHeight;q(a,{width:b,height:c}),q(G,{width:b,height:c}),d.paper.safari()})}(B),d.paper.defs.appendChild(B),q(i,{fill:"url(#"+B.id+")"}),d.pattern=B,d.pattern&&s(d);break}var H=c.getRGB(p);if(H.error){if(("circle"==d.type||"ellipse"==d.type||"r"!=b(p).charAt())&&r(d,p)){if("opacity"in k||"fill-opacity"in k){var I=c._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l));if(I){var J=I.getElementsByTagName("stop");q(J[J.length-1],{"stop-opacity":("opacity"in k?k.opacity:1)*("fill-opacity"in k?k["fill-opacity"]:1)})}}k.gradient=p,k.fill="none";break}}else delete f.gradient,delete k.gradient,!c.is(k.opacity,"undefined")&&c.is(f.opacity,"undefined")&&q(i,{opacity:k.opacity}),!c.is(k["fill-opacity"],"undefined")&&c.is(f["fill-opacity"],"undefined")&&q(i,{"fill-opacity":k["fill-opacity"]});H[a]("opacity")&&q(i,{"fill-opacity":H.opacity>1?H.opacity/100:H.opacity});case"stroke":H=c.getRGB(p),i.setAttribute(o,H.hex),"stroke"==o&&H[a]("opacity")&&q(i,{"stroke-opacity":H.opacity>1?H.opacity/100:H.opacity}),"stroke"==o&&d._.arrows&&("startString"in d._.arrows&&t(d,d._.arrows.startString),"endString"in d._.arrows&&t(d,d._.arrows.endString,1));break;case"gradient":("circle"==d.type||"ellipse"==d.type||"r"!=b(p).charAt())&&r(d,p);break;
case"opacity":k.gradient&&!k[a]("stroke-opacity")&&q(i,{"stroke-opacity":p>1?p/100:p});case"fill-opacity":if(k.gradient){I=c._g.doc.getElementById(i.getAttribute("fill").replace(/^url\(#|\)$/g,l)),I&&(J=I.getElementsByTagName("stop"),q(J[J.length-1],{"stop-opacity":p}));break}default:"font-size"==o&&(p=e(p,10)+"px");var K=o.replace(/(\-.)/g,function(a){return a.substring(1).toUpperCase()});i.style[K]=p,d._.dirty=1,i.setAttribute(o,p)}}y(d,f),i.style.visibility=m},x=1.2,y=function(d,f){if("text"==d.type&&(f[a]("text")||f[a]("font")||f[a]("font-size")||f[a]("x")||f[a]("y"))){var g=d.attrs,h=d.node,i=h.firstChild?e(c._g.doc.defaultView.getComputedStyle(h.firstChild,l).getPropertyValue("font-size"),10):10;if(f[a]("text")){for(g.text=f.text;h.firstChild;)h.removeChild(h.firstChild);for(var j,k=b(f.text).split("\n"),m=[],n=0,o=k.length;o>n;n++)j=q("tspan"),n&&q(j,{dy:i*x,x:g.x}),j.appendChild(c._g.doc.createTextNode(k[n])),h.appendChild(j),m[n]=j}else for(m=h.getElementsByTagName("tspan"),n=0,o=m.length;o>n;n++)n?q(m[n],{dy:i*x,x:g.x}):q(m[0],{dy:0});q(h,{x:g.x,y:g.y}),d._.dirty=1;var p=d._getBBox(),r=g.y-(p.y+p.height/2);r&&c.is(r,"finite")&&q(m[0],{dy:r})}},z=function(a){return a.parentNode&&"a"===a.parentNode.tagName.toLowerCase()?a.parentNode:a},A=function(a,b){this[0]=this.node=a,a.raphael=!0,this.id=c._oid++,a.raphaelid=this.id,this.matrix=c.matrix(),this.realPath=null,this.paper=b,this.attrs=this.attrs||{},this._={transform:[],sx:1,sy:1,deg:0,dx:0,dy:0,dirty:1},!b.bottom&&(b.bottom=this),this.prev=b.top,b.top&&(b.top.next=this),b.top=this,this.next=null},B=c.el;A.prototype=B,B.constructor=A,c._engine.path=function(a,b){var c=q("path");b.canvas&&b.canvas.appendChild(c);var d=new A(c,b);return d.type="path",w(d,{fill:"none",stroke:"#000",path:a}),d},B.rotate=function(a,c,e){if(this.removed)return this;if(a=b(a).split(j),a.length-1&&(c=d(a[1]),e=d(a[2])),a=d(a[0]),null==e&&(c=e),null==c||null==e){var f=this.getBBox(1);c=f.x+f.width/2,e=f.y+f.height/2}return this.transform(this._.transform.concat([["r",a,c,e]])),this},B.scale=function(a,c,e,f){if(this.removed)return this;if(a=b(a).split(j),a.length-1&&(c=d(a[1]),e=d(a[2]),f=d(a[3])),a=d(a[0]),null==c&&(c=a),null==f&&(e=f),null==e||null==f)var g=this.getBBox(1);return e=null==e?g.x+g.width/2:e,f=null==f?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,c,e,f]])),this},B.translate=function(a,c){return this.removed?this:(a=b(a).split(j),a.length-1&&(c=d(a[1])),a=d(a[0])||0,c=+c||0,this.transform(this._.transform.concat([["t",a,c]])),this)},B.transform=function(b){var d=this._;if(null==b)return d.transform;if(c._extractTransform(this,b),this.clip&&q(this.clip,{transform:this.matrix.invert()}),this.pattern&&s(this),this.node&&q(this.node,{transform:this.matrix}),1!=d.sx||1!=d.sy){var e=this.attrs[a]("stroke-width")?this.attrs["stroke-width"]:1;this.attr({"stroke-width":e})}return this},B.hide=function(){return!this.removed&&this.paper.safari(this.node.style.display="none"),this},B.show=function(){return!this.removed&&this.paper.safari(this.node.style.display=""),this},B.remove=function(){var a=z(this.node);if(!this.removed&&a.parentNode){var b=this.paper;b.__set__&&b.__set__.exclude(this),k.unbind("raphael.*.*."+this.id),this.gradient&&b.defs.removeChild(this.gradient),c._tear(this,b),a.parentNode.removeChild(a),this.removeData();for(var d in this)this[d]="function"==typeof this[d]?c._removedFactory(d):null;this.removed=!0}},B._getBBox=function(){if("none"==this.node.style.display){this.show();var a=!0}var b,c=!1;this.paper.canvas.parentElement?b=this.paper.canvas.parentElement.style:this.paper.canvas.parentNode&&(b=this.paper.canvas.parentNode.style),b&&"none"==b.display&&(c=!0,b.display="");var d={};try{d=this.node.getBBox()}catch(e){d={x:this.node.clientLeft,y:this.node.clientTop,width:this.node.clientWidth,height:this.node.clientHeight}}finally{d=d||{},c&&(b.display="none")}return a&&this.hide(),d},B.attr=function(b,d){if(this.removed)return this;if(null==b){var e={};for(var f in this.attrs)this.attrs[a](f)&&(e[f]=this.attrs[f]);return e.gradient&&"none"==e.fill&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform,e}if(null==d&&c.is(b,"string")){if("fill"==b&&"none"==this.attrs.fill&&this.attrs.gradient)return this.attrs.gradient;if("transform"==b)return this._.transform;for(var g=b.split(j),h={},i=0,l=g.length;l>i;i++)b=g[i],h[b]=b in this.attrs?this.attrs[b]:c.is(this.paper.customAttributes[b],"function")?this.paper.customAttributes[b].def:c._availableAttrs[b];return l-1?h:h[g[0]]}if(null==d&&c.is(b,"array")){for(h={},i=0,l=b.length;l>i;i++)h[b[i]]=this.attr(b[i]);return h}if(null!=d){var m={};m[b]=d}else null!=b&&c.is(b,"object")&&(m=b);for(var n in m)k("raphael.attr."+n+"."+this.id,this,m[n]);for(n in this.paper.customAttributes)if(this.paper.customAttributes[a](n)&&m[a](n)&&c.is(this.paper.customAttributes[n],"function")){var o=this.paper.customAttributes[n].apply(this,[].concat(m[n]));this.attrs[n]=m[n];for(var p in o)o[a](p)&&(m[p]=o[p])}return w(this,m),this},B.toFront=function(){if(this.removed)return this;var a=z(this.node);a.parentNode.appendChild(a);var b=this.paper;return b.top!=this&&c._tofront(this,b),this},B.toBack=function(){if(this.removed)return this;var a=z(this.node),b=a.parentNode;b.insertBefore(a,b.firstChild),c._toback(this,this.paper);this.paper;return this},B.insertAfter=function(a){if(this.removed||!a)return this;var b=z(this.node),d=z(a.node||a[a.length-1].node);return d.nextSibling?d.parentNode.insertBefore(b,d.nextSibling):d.parentNode.appendChild(b),c._insertafter(this,a,this.paper),this},B.insertBefore=function(a){if(this.removed||!a)return this;var b=z(this.node),d=z(a.node||a[0].node);return d.parentNode.insertBefore(b,d),c._insertbefore(this,a,this.paper),this},B.blur=function(a){var b=this;if(0!==+a){var d=q("filter"),e=q("feGaussianBlur");b.attrs.blur=a,d.id=c.createUUID(),q(e,{stdDeviation:+a||1.5}),d.appendChild(e),b.paper.defs.appendChild(d),b._blur=d,q(b.node,{filter:"url(#"+d.id+")"})}else b._blur&&(b._blur.parentNode.removeChild(b._blur),delete b._blur,delete b.attrs.blur),b.node.removeAttribute("filter");return b},c._engine.circle=function(a,b,c,d){var e=q("circle");a.canvas&&a.canvas.appendChild(e);var f=new A(e,a);return f.attrs={cx:b,cy:c,r:d,fill:"none",stroke:"#000"},f.type="circle",q(e,f.attrs),f},c._engine.rect=function(a,b,c,d,e,f){var g=q("rect");a.canvas&&a.canvas.appendChild(g);var h=new A(g,a);return h.attrs={x:b,y:c,width:d,height:e,rx:f||0,ry:f||0,fill:"none",stroke:"#000"},h.type="rect",q(g,h.attrs),h},c._engine.ellipse=function(a,b,c,d,e){var f=q("ellipse");a.canvas&&a.canvas.appendChild(f);var g=new A(f,a);return g.attrs={cx:b,cy:c,rx:d,ry:e,fill:"none",stroke:"#000"},g.type="ellipse",q(f,g.attrs),g},c._engine.image=function(a,b,c,d,e,f){var g=q("image");q(g,{x:c,y:d,width:e,height:f,preserveAspectRatio:"none"}),g.setAttributeNS(n,"href",b),a.canvas&&a.canvas.appendChild(g);var h=new A(g,a);return h.attrs={x:c,y:d,width:e,height:f,src:b},h.type="image",h},c._engine.text=function(a,b,d,e){var f=q("text");a.canvas&&a.canvas.appendChild(f);var g=new A(f,a);return g.attrs={x:b,y:d,"text-anchor":"middle",text:e,"font-family":c._availableAttrs["font-family"],"font-size":c._availableAttrs["font-size"],stroke:"none",fill:"#000"},g.type="text",w(g,g.attrs),g},c._engine.setSize=function(a,b){return this.width=a||this.width,this.height=b||this.height,this.canvas.setAttribute("width",this.width),this.canvas.setAttribute("height",this.height),this._viewBox&&this.setViewBox.apply(this,this._viewBox),this},c._engine.create=function(){var a=c._getContainer.apply(0,arguments),b=a&&a.container,d=a.x,e=a.y,f=a.width,g=a.height;if(!b)throw new Error("SVG container not found.");var h,i=q("svg"),j="overflow:hidden;";return d=d||0,e=e||0,f=f||512,g=g||342,q(i,{height:g,version:1.1,width:f,xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink"}),1==b?(i.style.cssText=j+"position:absolute;left:"+d+"px;top:"+e+"px",c._g.doc.body.appendChild(i),h=1):(i.style.cssText=j+"position:relative",b.firstChild?b.insertBefore(i,b.firstChild):b.appendChild(i)),b=new c._Paper,b.width=f,b.height=g,b.canvas=i,b.clear(),b._left=b._top=0,h&&(b.renderfix=function(){}),b.renderfix(),b},c._engine.setViewBox=function(a,b,c,d,e){k("raphael.setViewBox",this,this._viewBox,[a,b,c,d,e]);var f,h,i=this.getSize(),j=g(c/i.width,d/i.height),l=this.top,n=e?"xMidYMid meet":"xMinYMin";for(null==a?(this._vbSize&&(j=1),delete this._vbSize,f="0 0 "+this.width+m+this.height):(this._vbSize=j,f=a+m+b+m+c+m+d),q(this.canvas,{viewBox:f,preserveAspectRatio:n});j&&l;)h="stroke-width"in l.attrs?l.attrs["stroke-width"]:1,l.attr({"stroke-width":h}),l._.dirty=1,l._.dirtyT=1,l=l.prev;return this._viewBox=[a,b,c,d,!!e],this},c.prototype.renderfix=function(){var a,b=this.canvas,c=b.style;try{a=b.getScreenCTM()||b.createSVGMatrix()}catch(d){a=b.createSVGMatrix()}var e=-a.e%1,f=-a.f%1;(e||f)&&(e&&(this._left=(this._left+e)%1,c.left=this._left+"px"),f&&(this._top=(this._top+f)%1,c.top=this._top+"px"))},c.prototype.clear=function(){c.eve("raphael.clear",this);for(var a=this.canvas;a.firstChild;)a.removeChild(a.firstChild);this.bottom=this.top=null,(this.desc=q("desc")).appendChild(c._g.doc.createTextNode("Created with Raphaël "+c.version)),a.appendChild(this.desc),a.appendChild(this.defs=q("defs"))},c.prototype.remove=function(){k("raphael.remove",this),this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]="function"==typeof this[a]?c._removedFactory(a):null};var C=c.st;for(var D in B)B[a](D)&&!C[a](D)&&(C[D]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(D))}}(),function(){if(c.vml){var a="hasOwnProperty",b=String,d=parseFloat,e=Math,f=e.round,g=e.max,h=e.min,i=e.abs,j="fill",k=/[, ]+/,l=c.eve,m=" progid:DXImageTransform.Microsoft",n=" ",o="",p={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},q=/([clmz]),?([^clmz]*)/gi,r=/ progid:\S+Blur\([^\)]+\)/g,s=/-?[^,\s-]+/g,t="position:absolute;left:0;top:0;width:1px;height:1px;behavior:url(#default#VML)",u=21600,v={path:1,rect:1,image:1},w={circle:1,ellipse:1},x=function(a){var d=/[ahqstv]/gi,e=c._pathToAbsolute;if(b(a).match(d)&&(e=c._path2curve),d=/[clmz]/g,e==c._pathToAbsolute&&!b(a).match(d)){var g=b(a).replace(q,function(a,b,c){var d=[],e="m"==b.toLowerCase(),g=p[b];return c.replace(s,function(a){e&&2==d.length&&(g+=d+p["m"==b?"l":"L"],d=[]),d.push(f(a*u))}),g+d});return g}var h,i,j=e(a);g=[];for(var k=0,l=j.length;l>k;k++){h=j[k],i=j[k][0].toLowerCase(),"z"==i&&(i="x");for(var m=1,r=h.length;r>m;m++)i+=f(h[m]*u)+(m!=r-1?",":o);g.push(i)}return g.join(n)},y=function(a,b,d){var e=c.matrix();return e.rotate(-a,.5,.5),{dx:e.x(b,d),dy:e.y(b,d)}},z=function(a,b,c,d,e,f){var g=a._,h=a.matrix,k=g.fillpos,l=a.node,m=l.style,o=1,p="",q=u/b,r=u/c;if(m.visibility="hidden",b&&c){if(l.coordsize=i(q)+n+i(r),m.rotation=f*(0>b*c?-1:1),f){var s=y(f,d,e);d=s.dx,e=s.dy}if(0>b&&(p+="x"),0>c&&(p+=" y")&&(o=-1),m.flip=p,l.coordorigin=d*-q+n+e*-r,k||g.fillsize){var t=l.getElementsByTagName(j);t=t&&t[0],l.removeChild(t),k&&(s=y(f,h.x(k[0],k[1]),h.y(k[0],k[1])),t.position=s.dx*o+n+s.dy*o),g.fillsize&&(t.size=g.fillsize[0]*i(b)+n+g.fillsize[1]*i(c)),l.appendChild(t)}m.visibility="visible"}};c.toString=function(){return"Your browser doesn’t support SVG. Falling down to VML.\nYou are running Raphaël "+this.version};var A=function(a,c,d){for(var e=b(c).toLowerCase().split("-"),f=d?"end":"start",g=e.length,h="classic",i="medium",j="medium";g--;)switch(e[g]){case"block":case"classic":case"oval":case"diamond":case"open":case"none":h=e[g];break;case"wide":case"narrow":j=e[g];break;case"long":case"short":i=e[g]}var k=a.node.getElementsByTagName("stroke")[0];k[f+"arrow"]=h,k[f+"arrowlength"]=i,k[f+"arrowwidth"]=j},B=function(e,i){e.attrs=e.attrs||{};var l=e.node,m=e.attrs,p=l.style,q=v[e.type]&&(i.x!=m.x||i.y!=m.y||i.width!=m.width||i.height!=m.height||i.cx!=m.cx||i.cy!=m.cy||i.rx!=m.rx||i.ry!=m.ry||i.r!=m.r),r=w[e.type]&&(m.cx!=i.cx||m.cy!=i.cy||m.r!=i.r||m.rx!=i.rx||m.ry!=i.ry),s=e;for(var t in i)i[a](t)&&(m[t]=i[t]);if(q&&(m.path=c._getPath[e.type](e),e._.dirty=1),i.href&&(l.href=i.href),i.title&&(l.title=i.title),i.target&&(l.target=i.target),i.cursor&&(p.cursor=i.cursor),"blur"in i&&e.blur(i.blur),(i.path&&"path"==e.type||q)&&(l.path=x(~b(m.path).toLowerCase().indexOf("r")?c._pathToAbsolute(m.path):m.path),e._.dirty=1,"image"==e.type&&(e._.fillpos=[m.x,m.y],e._.fillsize=[m.width,m.height],z(e,1,1,0,0,0))),"transform"in i&&e.transform(i.transform),r){var y=+m.cx,B=+m.cy,D=+m.rx||+m.r||0,E=+m.ry||+m.r||0;l.path=c.format("ar{0},{1},{2},{3},{4},{1},{4},{1}x",f((y-D)*u),f((B-E)*u),f((y+D)*u),f((B+E)*u),f(y*u)),e._.dirty=1}if("clip-rect"in i){var G=b(i["clip-rect"]).split(k);if(4==G.length){G[2]=+G[2]+ +G[0],G[3]=+G[3]+ +G[1];var H=l.clipRect||c._g.doc.createElement("div"),I=H.style;I.clip=c.format("rect({1}px {2}px {3}px {0}px)",G),l.clipRect||(I.position="absolute",I.top=0,I.left=0,I.width=e.paper.width+"px",I.height=e.paper.height+"px",l.parentNode.insertBefore(H,l),H.appendChild(l),l.clipRect=H)}i["clip-rect"]||l.clipRect&&(l.clipRect.style.clip="auto")}if(e.textpath){var J=e.textpath.style;i.font&&(J.font=i.font),i["font-family"]&&(J.fontFamily='"'+i["font-family"].split(",")[0].replace(/^['"]+|['"]+$/g,o)+'"'),i["font-size"]&&(J.fontSize=i["font-size"]),i["font-weight"]&&(J.fontWeight=i["font-weight"]),i["font-style"]&&(J.fontStyle=i["font-style"])}if("arrow-start"in i&&A(s,i["arrow-start"]),"arrow-end"in i&&A(s,i["arrow-end"],1),null!=i.opacity||null!=i["stroke-width"]||null!=i.fill||null!=i.src||null!=i.stroke||null!=i["stroke-width"]||null!=i["stroke-opacity"]||null!=i["fill-opacity"]||null!=i["stroke-dasharray"]||null!=i["stroke-miterlimit"]||null!=i["stroke-linejoin"]||null!=i["stroke-linecap"]){var K=l.getElementsByTagName(j),L=!1;if(K=K&&K[0],!K&&(L=K=F(j)),"image"==e.type&&i.src&&(K.src=i.src),i.fill&&(K.on=!0),(null==K.on||"none"==i.fill||null===i.fill)&&(K.on=!1),K.on&&i.fill){var M=b(i.fill).match(c._ISURL);if(M){K.parentNode==l&&l.removeChild(K),K.rotate=!0,K.src=M[1],K.type="tile";var N=e.getBBox(1);K.position=N.x+n+N.y,e._.fillpos=[N.x,N.y],c._preload(M[1],function(){e._.fillsize=[this.offsetWidth,this.offsetHeight]})}else K.color=c.getRGB(i.fill).hex,K.src=o,K.type="solid",c.getRGB(i.fill).error&&(s.type in{circle:1,ellipse:1}||"r"!=b(i.fill).charAt())&&C(s,i.fill,K)&&(m.fill="none",m.gradient=i.fill,K.rotate=!1)}if("fill-opacity"in i||"opacity"in i){var O=((+m["fill-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+c.getRGB(i.fill).o+1||2)-1);O=h(g(O,0),1),K.opacity=O,K.src&&(K.color="none")}l.appendChild(K);var P=l.getElementsByTagName("stroke")&&l.getElementsByTagName("stroke")[0],Q=!1;!P&&(Q=P=F("stroke")),(i.stroke&&"none"!=i.stroke||i["stroke-width"]||null!=i["stroke-opacity"]||i["stroke-dasharray"]||i["stroke-miterlimit"]||i["stroke-linejoin"]||i["stroke-linecap"])&&(P.on=!0),("none"==i.stroke||null===i.stroke||null==P.on||0==i.stroke||0==i["stroke-width"])&&(P.on=!1);var R=c.getRGB(i.stroke);P.on&&i.stroke&&(P.color=R.hex),O=((+m["stroke-opacity"]+1||2)-1)*((+m.opacity+1||2)-1)*((+R.o+1||2)-1);var S=.75*(d(i["stroke-width"])||1);if(O=h(g(O,0),1),null==i["stroke-width"]&&(S=m["stroke-width"]),i["stroke-width"]&&(P.weight=S),S&&1>S&&(O*=S)&&(P.weight=1),P.opacity=O,i["stroke-linejoin"]&&(P.joinstyle=i["stroke-linejoin"]||"miter"),P.miterlimit=i["stroke-miterlimit"]||8,i["stroke-linecap"]&&(P.endcap="butt"==i["stroke-linecap"]?"flat":"square"==i["stroke-linecap"]?"square":"round"),"stroke-dasharray"in i){var T={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};P.dashstyle=T[a](i["stroke-dasharray"])?T[i["stroke-dasharray"]]:o}Q&&l.appendChild(P)}if("text"==s.type){s.paper.canvas.style.display=o;var U=s.paper.span,V=100,W=m.font&&m.font.match(/\d+(?:\.\d*)?(?=px)/);p=U.style,m.font&&(p.font=m.font),m["font-family"]&&(p.fontFamily=m["font-family"]),m["font-weight"]&&(p.fontWeight=m["font-weight"]),m["font-style"]&&(p.fontStyle=m["font-style"]),W=d(m["font-size"]||W&&W[0])||10,p.fontSize=W*V+"px",s.textpath.string&&(U.innerHTML=b(s.textpath.string).replace(/</g,"&#60;").replace(/&/g,"&#38;").replace(/\n/g,"<br>"));var X=U.getBoundingClientRect();s.W=m.w=(X.right-X.left)/V,s.H=m.h=(X.bottom-X.top)/V,s.X=m.x,s.Y=m.y+s.H/2,("x"in i||"y"in i)&&(s.path.v=c.format("m{0},{1}l{2},{1}",f(m.x*u),f(m.y*u),f(m.x*u)+1));for(var Y=["x","y","text","font","font-family","font-weight","font-style","font-size"],Z=0,$=Y.length;$>Z;Z++)if(Y[Z]in i){s._.dirty=1;break}switch(m["text-anchor"]){case"start":s.textpath.style["v-text-align"]="left",s.bbx=s.W/2;break;case"end":s.textpath.style["v-text-align"]="right",s.bbx=-s.W/2;break;default:s.textpath.style["v-text-align"]="center",s.bbx=0}s.textpath.style["v-text-kern"]=!0}},C=function(a,f,g){a.attrs=a.attrs||{};var h=(a.attrs,Math.pow),i="linear",j=".5 .5";if(a.attrs.gradient=f,f=b(f).replace(c._radial_gradient,function(a,b,c){return i="radial",b&&c&&(b=d(b),c=d(c),h(b-.5,2)+h(c-.5,2)>.25&&(c=e.sqrt(.25-h(b-.5,2))*(2*(c>.5)-1)+.5),j=b+n+c),o}),f=f.split(/\s*\-\s*/),"linear"==i){var k=f.shift();if(k=-d(k),isNaN(k))return null}var l=c._parseDots(f);if(!l)return null;if(a=a.shape||a.node,l.length){a.removeChild(g),g.on=!0,g.method="none",g.color=l[0].color,g.color2=l[l.length-1].color;for(var m=[],p=0,q=l.length;q>p;p++)l[p].offset&&m.push(l[p].offset+n+l[p].color);g.colors=m.length?m.join():"0% "+g.color,"radial"==i?(g.type="gradientTitle",g.focus="100%",g.focussize="0 0",g.focusposition=j,g.angle=0):(g.type="gradient",g.angle=(270-k)%360),a.appendChild(g)}return 1},D=function(a,b){this[0]=this.node=a,a.raphael=!0,this.id=c._oid++,a.raphaelid=this.id,this.X=0,this.Y=0,this.attrs={},this.paper=b,this.matrix=c.matrix(),this._={transform:[],sx:1,sy:1,dx:0,dy:0,deg:0,dirty:1,dirtyT:1},!b.bottom&&(b.bottom=this),this.prev=b.top,b.top&&(b.top.next=this),b.top=this,this.next=null},E=c.el;D.prototype=E,E.constructor=D,E.transform=function(a){if(null==a)return this._.transform;var d,e=this.paper._viewBoxShift,f=e?"s"+[e.scale,e.scale]+"-1-1t"+[e.dx,e.dy]:o;e&&(d=a=b(a).replace(/\.{3}|\u2026/g,this._.transform||o)),c._extractTransform(this,f+a);var g,h=this.matrix.clone(),i=this.skew,j=this.node,k=~b(this.attrs.fill).indexOf("-"),l=!b(this.attrs.fill).indexOf("url(");if(h.translate(1,1),l||k||"image"==this.type)if(i.matrix="1 0 0 1",i.offset="0 0",g=h.split(),k&&g.noRotation||!g.isSimple){j.style.filter=h.toFilter();var m=this.getBBox(),p=this.getBBox(1),q=m.x-p.x,r=m.y-p.y;j.coordorigin=q*-u+n+r*-u,z(this,1,1,q,r,0)}else j.style.filter=o,z(this,g.scalex,g.scaley,g.dx,g.dy,g.rotate);else j.style.filter=o,i.matrix=b(h),i.offset=h.offset();return null!==d&&(this._.transform=d,c._extractTransform(this,d)),this},E.rotate=function(a,c,e){if(this.removed)return this;if(null!=a){if(a=b(a).split(k),a.length-1&&(c=d(a[1]),e=d(a[2])),a=d(a[0]),null==e&&(c=e),null==c||null==e){var f=this.getBBox(1);c=f.x+f.width/2,e=f.y+f.height/2}return this._.dirtyT=1,this.transform(this._.transform.concat([["r",a,c,e]])),this}},E.translate=function(a,c){return this.removed?this:(a=b(a).split(k),a.length-1&&(c=d(a[1])),a=d(a[0])||0,c=+c||0,this._.bbox&&(this._.bbox.x+=a,this._.bbox.y+=c),this.transform(this._.transform.concat([["t",a,c]])),this)},E.scale=function(a,c,e,f){if(this.removed)return this;if(a=b(a).split(k),a.length-1&&(c=d(a[1]),e=d(a[2]),f=d(a[3]),isNaN(e)&&(e=null),isNaN(f)&&(f=null)),a=d(a[0]),null==c&&(c=a),null==f&&(e=f),null==e||null==f)var g=this.getBBox(1);return e=null==e?g.x+g.width/2:e,f=null==f?g.y+g.height/2:f,this.transform(this._.transform.concat([["s",a,c,e,f]])),this._.dirtyT=1,this},E.hide=function(){return!this.removed&&(this.node.style.display="none"),this},E.show=function(){return!this.removed&&(this.node.style.display=o),this},E.auxGetBBox=c.el.getBBox,E.getBBox=function(){var a=this.auxGetBBox();if(this.paper&&this.paper._viewBoxShift){var b={},c=1/this.paper._viewBoxShift.scale;return b.x=a.x-this.paper._viewBoxShift.dx,b.x*=c,b.y=a.y-this.paper._viewBoxShift.dy,b.y*=c,b.width=a.width*c,b.height=a.height*c,b.x2=b.x+b.width,b.y2=b.y+b.height,b}return a},E._getBBox=function(){return this.removed?{}:{x:this.X+(this.bbx||0)-this.W/2,y:this.Y-this.H,width:this.W,height:this.H}},E.remove=function(){if(!this.removed&&this.node.parentNode){this.paper.__set__&&this.paper.__set__.exclude(this),c.eve.unbind("raphael.*.*."+this.id),c._tear(this,this.paper),this.node.parentNode.removeChild(this.node),this.shape&&this.shape.parentNode.removeChild(this.shape);for(var a in this)this[a]="function"==typeof this[a]?c._removedFactory(a):null;this.removed=!0}},E.attr=function(b,d){if(this.removed)return this;if(null==b){var e={};for(var f in this.attrs)this.attrs[a](f)&&(e[f]=this.attrs[f]);return e.gradient&&"none"==e.fill&&(e.fill=e.gradient)&&delete e.gradient,e.transform=this._.transform,e}if(null==d&&c.is(b,"string")){if(b==j&&"none"==this.attrs.fill&&this.attrs.gradient)return this.attrs.gradient;for(var g=b.split(k),h={},i=0,m=g.length;m>i;i++)b=g[i],h[b]=b in this.attrs?this.attrs[b]:c.is(this.paper.customAttributes[b],"function")?this.paper.customAttributes[b].def:c._availableAttrs[b];return m-1?h:h[g[0]]}if(this.attrs&&null==d&&c.is(b,"array")){for(h={},i=0,m=b.length;m>i;i++)h[b[i]]=this.attr(b[i]);return h}var n;null!=d&&(n={},n[b]=d),null==d&&c.is(b,"object")&&(n=b);for(var o in n)l("raphael.attr."+o+"."+this.id,this,n[o]);if(n){for(o in this.paper.customAttributes)if(this.paper.customAttributes[a](o)&&n[a](o)&&c.is(this.paper.customAttributes[o],"function")){var p=this.paper.customAttributes[o].apply(this,[].concat(n[o]));this.attrs[o]=n[o];for(var q in p)p[a](q)&&(n[q]=p[q])}n.text&&"text"==this.type&&(this.textpath.string=n.text),B(this,n)}return this},E.toFront=function(){return!this.removed&&this.node.parentNode.appendChild(this.node),this.paper&&this.paper.top!=this&&c._tofront(this,this.paper),this},E.toBack=function(){return this.removed?this:(this.node.parentNode.firstChild!=this.node&&(this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild),c._toback(this,this.paper)),this)},E.insertAfter=function(a){return this.removed?this:(a.constructor==c.st.constructor&&(a=a[a.length-1]),a.node.nextSibling?a.node.parentNode.insertBefore(this.node,a.node.nextSibling):a.node.parentNode.appendChild(this.node),c._insertafter(this,a,this.paper),this)},E.insertBefore=function(a){return this.removed?this:(a.constructor==c.st.constructor&&(a=a[0]),a.node.parentNode.insertBefore(this.node,a.node),c._insertbefore(this,a,this.paper),this)},E.blur=function(a){var b=this.node.runtimeStyle,d=b.filter;return d=d.replace(r,o),0!==+a?(this.attrs.blur=a,b.filter=d+n+m+".Blur(pixelradius="+(+a||1.5)+")",b.margin=c.format("-{0}px 0 0 -{0}px",f(+a||1.5))):(b.filter=d,b.margin=0,delete this.attrs.blur),this},c._engine.path=function(a,b){var c=F("shape");c.style.cssText=t,c.coordsize=u+n+u,c.coordorigin=b.coordorigin;var d=new D(c,b),e={fill:"none",stroke:"#000"};a&&(e.path=a),d.type="path",d.path=[],d.Path=o,B(d,e),b.canvas.appendChild(c);var f=F("skew");return f.on=!0,c.appendChild(f),d.skew=f,d.transform(o),d},c._engine.rect=function(a,b,d,e,f,g){var h=c._rectPath(b,d,e,f,g),i=a.path(h),j=i.attrs;return i.X=j.x=b,i.Y=j.y=d,i.W=j.width=e,i.H=j.height=f,j.r=g,j.path=h,i.type="rect",i},c._engine.ellipse=function(a,b,c,d,e){{var f=a.path();f.attrs}return f.X=b-d,f.Y=c-e,f.W=2*d,f.H=2*e,f.type="ellipse",B(f,{cx:b,cy:c,rx:d,ry:e}),f},c._engine.circle=function(a,b,c,d){{var e=a.path();e.attrs}return e.X=b-d,e.Y=c-d,e.W=e.H=2*d,e.type="circle",B(e,{cx:b,cy:c,r:d}),e},c._engine.image=function(a,b,d,e,f,g){var h=c._rectPath(d,e,f,g),i=a.path(h).attr({stroke:"none"}),k=i.attrs,l=i.node,m=l.getElementsByTagName(j)[0];return k.src=b,i.X=k.x=d,i.Y=k.y=e,i.W=k.width=f,i.H=k.height=g,k.path=h,i.type="image",m.parentNode==l&&l.removeChild(m),m.rotate=!0,m.src=b,m.type="tile",i._.fillpos=[d,e],i._.fillsize=[f,g],l.appendChild(m),z(i,1,1,0,0,0),i},c._engine.text=function(a,d,e,g){var h=F("shape"),i=F("path"),j=F("textpath");d=d||0,e=e||0,g=g||"",i.v=c.format("m{0},{1}l{2},{1}",f(d*u),f(e*u),f(d*u)+1),i.textpathok=!0,j.string=b(g),j.on=!0,h.style.cssText=t,h.coordsize=u+n+u,h.coordorigin="0 0";var k=new D(h,a),l={fill:"#000",stroke:"none",font:c._availableAttrs.font,text:g};k.shape=h,k.path=i,k.textpath=j,k.type="text",k.attrs.text=b(g),k.attrs.x=d,k.attrs.y=e,k.attrs.w=1,k.attrs.h=1,B(k,l),h.appendChild(j),h.appendChild(i),a.canvas.appendChild(h);var m=F("skew");return m.on=!0,h.appendChild(m),k.skew=m,k.transform(o),k},c._engine.setSize=function(a,b){var d=this.canvas.style;return this.width=a,this.height=b,a==+a&&(a+="px"),b==+b&&(b+="px"),d.width=a,d.height=b,d.clip="rect(0 "+a+" "+b+" 0)",this._viewBox&&c._engine.setViewBox.apply(this,this._viewBox),this},c._engine.setViewBox=function(a,b,d,e,f){c.eve("raphael.setViewBox",this,this._viewBox,[a,b,d,e,f]);var g,h,i=this.getSize(),j=i.width,k=i.height;return f&&(g=k/e,h=j/d,j>d*g&&(a-=(j-d*g)/2/g),k>e*h&&(b-=(k-e*h)/2/h)),this._viewBox=[a,b,d,e,!!f],this._viewBoxShift={dx:-a,dy:-b,scale:i},this.forEach(function(a){a.transform("...")}),this};var F;c._engine.initWin=function(a){var b=a.document;b.styleSheets.length<31?b.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)"):b.styleSheets[0].addRule(".rvml","behavior:url(#default#VML)");try{!b.namespaces.rvml&&b.namespaces.add("rvml","urn:schemas-microsoft-com:vml"),F=function(a){return b.createElement("<rvml:"+a+' class="rvml">')}}catch(c){F=function(a){return b.createElement("<"+a+' xmlns="urn:schemas-microsoft.com:vml" class="rvml">')}}},c._engine.initWin(c._g.win),c._engine.create=function(){var a=c._getContainer.apply(0,arguments),b=a.container,d=a.height,e=a.width,f=a.x,g=a.y;if(!b)throw new Error("VML container not found.");var h=new c._Paper,i=h.canvas=c._g.doc.createElement("div"),j=i.style;return f=f||0,g=g||0,e=e||512,d=d||342,h.width=e,h.height=d,e==+e&&(e+="px"),d==+d&&(d+="px"),h.coordsize=1e3*u+n+1e3*u,h.coordorigin="0 0",h.span=c._g.doc.createElement("span"),h.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;",i.appendChild(h.span),j.cssText=c.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",e,d),1==b?(c._g.doc.body.appendChild(i),j.left=f+"px",j.top=g+"px",j.position="absolute"):b.firstChild?b.insertBefore(i,b.firstChild):b.appendChild(i),h.renderfix=function(){},h},c.prototype.clear=function(){c.eve("raphael.clear",this),this.canvas.innerHTML=o,this.span=c._g.doc.createElement("span"),this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;",this.canvas.appendChild(this.span),this.bottom=this.top=null},c.prototype.remove=function(){c.eve("raphael.remove",this),this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]="function"==typeof this[a]?c._removedFactory(a):null;return!0};var G=c.st;for(var H in E)E[a](H)&&!G[a](H)&&(G[H]=function(a){return function(){var b=arguments;return this.forEach(function(c){c[a].apply(c,b)})}}(H))}}(),B.was?A.win.Raphael=c:Raphael=c,"object"==typeof exports&&(module.exports=c),c});
$(function() {
  $('#side-menu').metisMenu();
});

// Loads the correct sidebar on window load,
// collapses the sidebar on window resize.
// Sets the min-height of #page-wrapper to window size
$(function() {
  $(window).bind("load resize", function() {
    topOffset = 50;
    width = (this.window.innerWidth > 0) ? this.window.innerWidth : this.screen.width;
    if (width < 768) {
      $('div.navbar-collapse').addClass('collapse');
      topOffset = 100; // 2-row-menu
    } else {
      $('div.navbar-collapse').removeClass('collapse');
    }

    height = ((this.window.innerHeight > 0) ? this.window.innerHeight : this.screen.height) - 1;
    height = height - topOffset;
    if (height < 1) height = 1;
    if (height > topOffset) {
      $("#page-wrapper").css("min-height", (height) + "px");
    }
  });

  var url = window.location;
  var element = $('ul.nav a').filter(function() {
    return this.href == url;
  }).addClass('active').parents('ul').addClass('in');
  if (element.is('li')) {
    element.addClass('active');
  }
});
// This is a manifest file that'll be compiled into bootstrap_sb_admin_base_v2.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file.
//
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
// about supported directives.
//









$(document).ready(function(){ });
/*!
 * Chartkick.js v5.0.1
 * Create beautiful charts with one line of JavaScript
 * https://github.com/ankane/chartkick.js
 * MIT License
 */


(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Chartkick = factory());
})(this, (function () { 'use strict';

  function isArray(variable) {
    return Object.prototype.toString.call(variable) === "[object Array]";
  }

  function isFunction(variable) {
    return variable instanceof Function;
  }

  function isPlainObject(variable) {
    // protect against prototype pollution, defense 2
    return Object.prototype.toString.call(variable) === "[object Object]" && !isFunction(variable) && variable instanceof Object;
  }

  // https://github.com/madrobby/zepto/blob/master/src/zepto.js
  function extend(target, source) {
    for (var key in source) {
      // protect against prototype pollution, defense 1
      if (key === "__proto__") { continue; }

      if (isPlainObject(source[key]) || isArray(source[key])) {
        if (isPlainObject(source[key]) && !isPlainObject(target[key])) {
          target[key] = {};
        }
        if (isArray(source[key]) && !isArray(target[key])) {
          target[key] = [];
        }
        extend(target[key], source[key]);
      } else if (source[key] !== undefined) {
        target[key] = source[key];
      }
    }
  }

  function merge(obj1, obj2) {
    var target = {};
    extend(target, obj1);
    extend(target, obj2);
    return target;
  }

  var DATE_PATTERN = /^(\d\d\d\d)(?:-)?(\d\d)(?:-)?(\d\d)$/i;

  function negativeValues(series) {
    for (var i = 0; i < series.length; i++) {
      var data = series[i].data;
      for (var j = 0; j < data.length; j++) {
        if (data[j][1] < 0) {
          return true;
        }
      }
    }
    return false;
  }

  function toStr(obj) {
    return "" + obj;
  }

  function toFloat(obj) {
    return parseFloat(obj);
  }

  function toDate(obj) {
    if (obj instanceof Date) {
      return obj;
    } else if (typeof obj === "number") {
      return new Date(obj * 1000); // ms
    } else {
      var s = toStr(obj);
      var matches = s.match(DATE_PATTERN);
      if (matches) {
        var year = parseInt(matches[1], 10);
        var month = parseInt(matches[2], 10) - 1;
        var day = parseInt(matches[3], 10);
        return new Date(year, month, day);
      } else {
        // try our best to get the str into iso8601
        // TODO be smarter about this
        var str = s.replace(/ /, "T").replace(" ", "").replace("UTC", "Z");
        // Date.parse returns milliseconds if valid and NaN if invalid
        return new Date(Date.parse(str) || s);
      }
    }
  }

  function toArr(obj) {
    if (isArray(obj)) {
      return obj;
    } else {
      var arr = [];
      for (var i in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, i)) {
          arr.push([i, obj[i]]);
        }
      }
      return arr;
    }
  }

  function jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle) {
    return function (chart, opts, chartOptions) {
      var series = chart.data;
      var options = merge({}, defaultOptions);
      options = merge(options, chartOptions || {});

      if (chart.singleSeriesFormat || "legend" in opts) {
        hideLegend(options, opts.legend, chart.singleSeriesFormat);
      }

      if (opts.title) {
        setTitle(options, opts.title);
      }

      // min
      if ("min" in opts) {
        setMin(options, opts.min);
      } else if (!negativeValues(series)) {
        setMin(options, 0);
      }

      // max
      if (opts.max) {
        setMax(options, opts.max);
      }

      if ("stacked" in opts) {
        setStacked(options, opts.stacked);
      }

      if (opts.colors) {
        options.colors = opts.colors;
      }

      if (opts.xtitle) {
        setXtitle(options, opts.xtitle);
      }

      if (opts.ytitle) {
        setYtitle(options, opts.ytitle);
      }

      // merge library last
      options = merge(options, opts.library || {});

      return options;
    };
  }

  function sortByTime(a, b) {
    return a[0].getTime() - b[0].getTime();
  }

  function sortByNumberSeries(a, b) {
    return a[0] - b[0];
  }

  // needed since sort() without arguments does string comparison
  function sortByNumber(a, b) {
    return a - b;
  }

  function every(values, fn) {
    for (var i = 0; i < values.length; i++) {
      if (!fn(values[i])) {
        return false;
      }
    }
    return true;
  }

  function isDay(timeUnit) {
    return timeUnit === "day" || timeUnit === "week" || timeUnit === "month" || timeUnit === "year";
  }

  function calculateTimeUnit(values, maxDay) {
    if ( maxDay === void 0 ) maxDay = false;

    if (values.length === 0) {
      return null;
    }

    var minute = every(values, function (d) { return d.getMilliseconds() === 0 && d.getSeconds() === 0; });
    if (!minute) {
      return null;
    }

    var hour = every(values, function (d) { return d.getMinutes() === 0; });
    if (!hour) {
      return "minute";
    }

    var day = every(values, function (d) { return d.getHours() === 0; });
    if (!day) {
      return "hour";
    }

    if (maxDay) {
      return "day";
    }

    var month = every(values, function (d) { return d.getDate() === 1; });
    if (!month) {
      var dayOfWeek = values[0].getDay();
      var week = every(values, function (d) { return d.getDay() === dayOfWeek; });
      return (week ? "week" : "day");
    }

    var year = every(values, function (d) { return d.getMonth() === 0; });
    if (!year) {
      return "month";
    }

    return "year";
  }

  function isDate(obj) {
    return !isNaN(toDate(obj)) && toStr(obj).length >= 6;
  }

  function isNumber(obj) {
    return typeof obj === "number";
  }

  var byteSuffixes = ["bytes", "KB", "MB", "GB", "TB", "PB", "EB"];

  function formatValue(pre, value, options, axis) {
    pre = pre || "";
    if (options.prefix) {
      if (value < 0) {
        value = value * -1;
        pre += "-";
      }
      pre += options.prefix;
    }

    var suffix = options.suffix || "";
    var precision = options.precision;
    var round = options.round;

    if (options.byteScale) {
      var positive = value >= 0;
      if (!positive) {
        value *= -1;
      }

      var baseValue = axis ? options.byteScale : value;

      var suffixIdx;
      if (baseValue >= 1152921504606846976) {
        value /= 1152921504606846976;
        suffixIdx = 6;
      } else if (baseValue >= 1125899906842624) {
        value /= 1125899906842624;
        suffixIdx = 5;
      } else if (baseValue >= 1099511627776) {
        value /= 1099511627776;
        suffixIdx = 4;
      } else if (baseValue >= 1073741824) {
        value /= 1073741824;
        suffixIdx = 3;
      } else if (baseValue >= 1048576) {
        value /= 1048576;
        suffixIdx = 2;
      } else if (baseValue >= 1024) {
        value /= 1024;
        suffixIdx = 1;
      } else {
        suffixIdx = 0;
      }

      // TODO handle manual precision case
      if (precision === undefined && round === undefined) {
        if (value >= 1023.5) {
          if (suffixIdx < byteSuffixes.length - 1) {
            value = 1.0;
            suffixIdx += 1;
          }
        }
        precision = value >= 1000 ? 4 : 3;
      }
      suffix = " " + byteSuffixes[suffixIdx];

      // flip value back
      if (!positive) {
        value *= -1;
      }
    }

    if (precision !== undefined && round !== undefined) {
      throw Error("Use either round or precision, not both");
    }

    if (!axis) {
      if (precision !== undefined) {
        value = value.toPrecision(precision);
        if (!options.zeros) {
          value = parseFloat(value);
        }
      }

      if (round !== undefined) {
        if (round < 0) {
          var num = Math.pow(10, -1 * round);
          value = parseInt((1.0 * value / num).toFixed(0)) * num;
        } else {
          value = value.toFixed(round);
          if (!options.zeros) {
            value = parseFloat(value);
          }
        }
      }
    }

    if (options.thousands || options.decimal) {
      value = toStr(value);
      var parts = value.split(".");
      value = parts[0];
      if (options.thousands) {
        value = value.replace(/\B(?=(\d{3})+(?!\d))/g, options.thousands);
      }
      if (parts.length > 1) {
        value += (options.decimal || ".") + parts[1];
      }
    }

    return pre + value + suffix;
  }

  function seriesOption(chart, series, option) {
    if (option in series) {
      return series[option];
    } else if (option in chart.options) {
      return chart.options[option];
    }
    return null;
  }

  var baseOptions = {
    maintainAspectRatio: false,
    animation: false,
    plugins: {
      legend: {},
      tooltip: {
        displayColors: false,
        callbacks: {}
      },
      title: {
        font: {
          size: 20
        },
        color: "#333"
      }
    },
    interaction: {}
  };

  var defaultOptions$2 = {
    scales: {
      y: {
        ticks: {
          maxTicksLimit: 4
        },
        title: {
          font: {
            size: 16
          },
          color: "#333"
        },
        grid: {}
      },
      x: {
        grid: {
          drawOnChartArea: false
        },
        title: {
          font: {
            size: 16
          },
          color: "#333"
        },
        time: {},
        ticks: {}
      }
    }
  };

  // http://there4.io/2012/05/02/google-chart-color-list/
  var defaultColors = [
    "#3366CC", "#DC3912", "#FF9900", "#109618", "#990099", "#3B3EAC", "#0099C6",
    "#DD4477", "#66AA00", "#B82E2E", "#316395", "#994499", "#22AA99", "#AAAA11",
    "#6633CC", "#E67300", "#8B0707", "#329262", "#5574A6", "#651067"
  ];

  function hideLegend$2(options, legend, hideLegend) {
    if (legend !== undefined) {
      options.plugins.legend.display = !!legend;
      if (legend && legend !== true) {
        options.plugins.legend.position = legend;
      }
    } else if (hideLegend) {
      options.plugins.legend.display = false;
    }
  }

  function setTitle$2(options, title) {
    options.plugins.title.display = true;
    options.plugins.title.text = title;
  }

  function setMin$2(options, min) {
    if (min !== null) {
      options.scales.y.min = toFloat(min);
    }
  }

  function setMax$2(options, max) {
    options.scales.y.max = toFloat(max);
  }

  function setBarMin$1(options, min) {
    if (min !== null) {
      options.scales.x.min = toFloat(min);
    }
  }

  function setBarMax$1(options, max) {
    options.scales.x.max = toFloat(max);
  }

  function setStacked$2(options, stacked) {
    options.scales.x.stacked = !!stacked;
    options.scales.y.stacked = !!stacked;
  }

  function setXtitle$2(options, title) {
    options.scales.x.title.display = true;
    options.scales.x.title.text = title;
  }

  function setYtitle$2(options, title) {
    options.scales.y.title.display = true;
    options.scales.y.title.text = title;
  }

  // https://stackoverflow.com/questions/5623838/rgb-to-hex-and-hex-to-rgb
  function addOpacity(hex, opacity) {
    var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
    return result ? "rgba(" + parseInt(result[1], 16) + ", " + parseInt(result[2], 16) + ", " + parseInt(result[3], 16) + ", " + opacity + ")" : hex;
  }

  function notnull(x) {
    return x !== null && x !== undefined;
  }

  function setLabelSize(chart, data, options) {
    var maxLabelSize = Math.ceil(chart.element.offsetWidth / 4.0 / data.labels.length);
    if (maxLabelSize > 25) {
      maxLabelSize = 25;
    } else if (maxLabelSize < 10) {
      maxLabelSize = 10;
    }
    if (!options.scales.x.ticks.callback) {
      options.scales.x.ticks.callback = function (value) {
        value = toStr(this.getLabelForValue(value));
        if (value.length > maxLabelSize) {
          return value.substring(0, maxLabelSize - 2) + "...";
        } else {
          return value;
        }
      };
    }
  }

  function calculateScale(series) {
    var scale = 1;
    var max = maxAbsY(series);
    while (max >= 1024) {
      scale *= 1024;
      max /= 1024;
    }
    return scale;
  }

  function setFormatOptions$1(chart, options, chartType) {
    // options to apply to x and r values for scatter and bubble
    var numericOptions = {
      thousands: chart.options.thousands,
      decimal: chart.options.decimal
    };

    // options to apply to y value
    var formatOptions = merge({
      prefix: chart.options.prefix,
      suffix: chart.options.suffix,
      precision: chart.options.precision,
      round: chart.options.round,
      zeros: chart.options.zeros
    }, numericOptions);

    if (chart.options.bytes) {
      var series = chart.data;
      if (chartType === "pie") {
        series = [{data: series}];
      }

      // set step size
      formatOptions.byteScale = calculateScale(series);
    }

    if (chartType !== "pie") {
      var axis = options.scales.y;
      if (chartType === "bar") {
        axis = options.scales.x;
      }

      if (formatOptions.byteScale) {
        if (!axis.ticks.stepSize) {
          axis.ticks.stepSize = formatOptions.byteScale / 2;
        }
        if (!axis.ticks.maxTicksLimit) {
          axis.ticks.maxTicksLimit = 4;
        }
      }

      if (!axis.ticks.callback) {
        axis.ticks.callback = function (value) {
          return formatValue("", value, formatOptions, true);
        };
      }

      if ((chartType === "scatter" || chartType === "bubble") && !options.scales.x.ticks.callback) {
        options.scales.x.ticks.callback = function (value) {
          return formatValue("", value, numericOptions, true);
        };
      }
    }

    if (!options.plugins.tooltip.callbacks.label) {
      if (chartType === "scatter") {
        options.plugins.tooltip.callbacks.label = function (context) {
          var label = context.dataset.label || '';
          if (label) {
            label += ': ';
          }

          var dataPoint = context.parsed;
          return label + '(' + formatValue('', dataPoint.x, numericOptions) + ', ' + formatValue('', dataPoint.y, formatOptions) + ')';
        };
      } else if (chartType === "bubble") {
        options.plugins.tooltip.callbacks.label = function (context) {
          var label = context.dataset.label || '';
          if (label) {
            label += ': ';
          }
          var dataPoint = context.raw;
          return label + '(' + formatValue('', dataPoint.x, numericOptions) + ', ' + formatValue('', dataPoint.y, formatOptions) + ', ' + formatValue('', dataPoint.v, numericOptions) + ')';
        };
      } else if (chartType === "pie") {
        // need to use separate label for pie charts
        options.plugins.tooltip.callbacks.label = function (context) {
          return formatValue('', context.parsed, formatOptions);
        };
      } else {
        var valueLabel = chartType === "bar" ? "x" : "y";
        options.plugins.tooltip.callbacks.label = function (context) {
          // don't show null values for stacked charts
          if (context.parsed[valueLabel] === null) {
            return;
          }

          var label = context.dataset.label || '';
          if (label) {
            label += ': ';
          }
          return formatValue(label, context.parsed[valueLabel], formatOptions);
        };
      }
    }

    // avoid formatting x-axis labels
    // by default, Chart.js applies locale
    if ((chartType === "line" || chartType === "area") && chart.xtype === "number") {
      if (!options.scales.x.ticks.callback) {
        options.scales.x.ticks.callback = function (value) {
          return toStr(value);
        };
      }

      if (!options.plugins.tooltip.callbacks.title) {
        options.plugins.tooltip.callbacks.title = function (context) {
          return toStr(context[0].parsed.x);
        };
      }
    }
  }

  function maxAbsY(series) {
    var max = 0;
    for (var i = 0; i < series.length; i++) {
      var data = series[i].data;
      for (var j = 0; j < data.length; j++) {
        var v = Math.abs(data[j][1]);
        if (v > max) {
          max = v;
        }
      }
    }
    return max;
  }

  function maxR(series) {
    // start at zero since radius must be positive
    var max = 0;
    for (var i = 0; i < series.length; i++) {
      var data = series[i].data;
      for (var j = 0; j < data.length; j++) {
        var v = data[j][2];
        if (v > max) {
          max = v;
        }
      }
    }
    return max;
  }

  var jsOptions$2 = jsOptionsFunc(merge(baseOptions, defaultOptions$2), hideLegend$2, setTitle$2, setMin$2, setMax$2, setStacked$2, setXtitle$2, setYtitle$2);

  function prepareDefaultData(chart) {
    var series = chart.data;
    var rows = {};
    var keys = [];
    var labels = [];
    var values = [];

    for (var i = 0; i < series.length; i++) {
      var data = series[i].data;

      for (var j = 0; j < data.length; j++) {
        var d = data[j];
        var key = chart.xtype === "datetime" ? d[0].getTime() : d[0];
        if (!rows[key]) {
          rows[key] = new Array(series.length);
          keys.push(key);
        }
        rows[key][i] = d[1];
      }
    }

    if (chart.xtype === "datetime" || chart.xtype === "number") {
      keys.sort(sortByNumber);
    }

    for (var i$1 = 0; i$1 < series.length; i$1++) {
      values.push([]);
    }

    for (var i$2 = 0; i$2 < keys.length; i$2++) {
      var key$1 = keys[i$2];

      var label = chart.xtype === "datetime" ? new Date(key$1) : key$1;
      labels.push(label);

      var row = rows[key$1];
      for (var j$1 = 0; j$1 < series.length; j$1++) {
        var v = row[j$1];
        // Chart.js doesn't like undefined
        values[j$1].push(v === undefined ? null : v);
      }
    }

    return {
      labels: labels,
      values: values
    };
  }

  function prepareBubbleData(chart) {
    var series = chart.data;
    var values = [];
    var max = maxR(series);

    for (var i = 0; i < series.length; i++) {
      var data = series[i].data;
      var points = [];
      for (var j = 0; j < data.length; j++) {
        var v = data[j];
        points.push({
          x: v[0],
          y: v[1],
          r: v[2] * 20 / max,
          // custom attribute, for tooltip
          v: v[2]
        });
      }
      values.push(points);
    }

    return {
      labels: [],
      values: values
    };
  }

  // scatter or numeric line/area
  function prepareNumberData(chart) {
    var series = chart.data;
    var values = [];

    for (var i = 0; i < series.length; i++) {
      var data = series[i].data;

      data.sort(sortByNumberSeries);

      var points = [];
      for (var j = 0; j < data.length; j++) {
        var v = data[j];
        points.push({
          x: v[0],
          y: v[1]
        });
      }
      values.push(points);
    }

    return {
      labels: [],
      values: values
    };
  }

  function prepareData(chart, chartType) {
    if (chartType === "bubble") {
      return prepareBubbleData(chart);
    } else if (chart.xtype === "number" && chartType !== "bar" && chartType !== "column") {
      return prepareNumberData(chart);
    } else {
      return prepareDefaultData(chart);
    }
  }

  function createDataTable(chart, options, chartType) {
    var ref = prepareData(chart, chartType);
    var labels = ref.labels;
    var values = ref.values;

    var series = chart.data;
    var datasets = [];
    var colors = chart.options.colors || defaultColors;
    for (var i = 0; i < series.length; i++) {
      var s = series[i];

      // use colors for each bar for single series format
      var color = (void 0);
      var backgroundColor = (void 0);
      if (chart.options.colors && chart.singleSeriesFormat && (chartType === "bar" || chartType === "column") && !s.color && isArray(chart.options.colors) && !isArray(chart.options.colors[0])) {
        color = colors;
        backgroundColor = [];
        for (var j = 0; j < colors.length; j++) {
          backgroundColor[j] = addOpacity(color[j], 0.5);
        }
      } else {
        color = s.color || colors[i];
        backgroundColor = chartType !== "line" ? addOpacity(color, 0.5) : color;
      }

      var dataset = {
        label: s.name || "",
        data: values[i],
        fill: chartType === "area",
        borderColor: color,
        backgroundColor: backgroundColor,
        borderWidth: 2
      };

      var pointChart = chartType === "line" || chartType === "area" || chartType === "scatter" || chartType === "bubble";
      if (pointChart) {
        dataset.pointBackgroundColor = color;
        dataset.pointHoverBackgroundColor = color;
        dataset.pointHitRadius = 50;
      }

      if (chartType === "bubble") {
        dataset.pointBackgroundColor = backgroundColor;
        dataset.pointHoverBackgroundColor = backgroundColor;
        dataset.pointHoverBorderWidth = 2;
      }

      if (s.stack) {
        dataset.stack = s.stack;
      }

      var curve = seriesOption(chart, s, "curve");
      if (curve === false) {
        dataset.tension = 0;
      } else if (pointChart) {
        dataset.tension = 0.4;
      }

      var points = seriesOption(chart, s, "points");
      if (points === false) {
        dataset.pointRadius = 0;
        dataset.pointHoverRadius = 0;
      }

      dataset = merge(dataset, chart.options.dataset || {});
      dataset = merge(dataset, s.library || {});
      dataset = merge(dataset, s.dataset || {});

      datasets.push(dataset);
    }

    var xmin = chart.options.xmin;
    var xmax = chart.options.xmax;

    if (chart.xtype === "datetime") {
      if (notnull(xmin)) {
        options.scales.x.min = toDate(xmin).getTime();
      }
      if (notnull(xmax)) {
        options.scales.x.max = toDate(xmax).getTime();
      }
    } else if (chart.xtype === "number") {
      if (notnull(xmin)) {
        options.scales.x.min = xmin;
      }
      if (notnull(xmax)) {
        options.scales.x.max = xmax;
      }
    }

    if (chart.xtype === "datetime") {
      var timeUnit = calculateTimeUnit(labels);

      // for empty datetime chart
      if (labels.length === 0) {
        if (notnull(xmin)) {
          labels.push(toDate(xmin));
        }
        if (notnull(xmax)) {
          labels.push(toDate(xmax));
        }
      }

      if (labels.length > 0) {
        var minTime = (notnull(xmin) ? toDate(xmin) : labels[0]).getTime();
        var maxTime = (notnull(xmax) ? toDate(xmax) : labels[0]).getTime();

        for (var i$1 = 1; i$1 < labels.length; i$1++) {
          var value = labels[i$1].getTime();
          if (value < minTime) {
            minTime = value;
          }
          if (value > maxTime) {
            maxTime = value;
          }
        }

        var timeDiff = (maxTime - minTime) / (86400 * 1000.0);

        if (!options.scales.x.time.unit) {
          var step;
          if (timeUnit === "year" || timeDiff > 365 * 10) {
            options.scales.x.time.unit = "year";
            step = 365;
          } else if (timeUnit === "month" || timeDiff > 30 * 10) {
            options.scales.x.time.unit = "month";
            step = 30;
          } else if (timeUnit === "week" || timeUnit === "day" || timeDiff > 10) {
            options.scales.x.time.unit = "day";
            step = 1;
          } else if (timeUnit === "hour" || timeDiff > 0.5) {
            options.scales.x.time.displayFormats = {hour: "MMM d, h a"};
            options.scales.x.time.unit = "hour";
            step = 1 / 24.0;
          } else if (timeUnit === "minute") {
            options.scales.x.time.displayFormats = {minute: "h:mm a"};
            options.scales.x.time.unit = "minute";
            step = 1 / 24.0 / 60.0;
          }

          if (step && timeDiff > 0) {
            // width not available for hidden elements
            var width = chart.element.offsetWidth;
            if (width > 0) {
              var unitStepSize = Math.ceil(timeDiff / step / (width / 100.0));
              if (timeUnit === "week" && step === 1) {
                unitStepSize = Math.ceil(unitStepSize / 7.0) * 7;
              }
              options.scales.x.ticks.stepSize = unitStepSize;
            }
          }
        }

        if (!options.scales.x.time.tooltipFormat) {
          if (timeUnit === "year") {
            options.scales.x.time.tooltipFormat = "yyyy";
          } else if (timeUnit === "month") {
            options.scales.x.time.tooltipFormat = "MMM yyyy";
          } else if (timeUnit === "week" || timeUnit === "day") {
            options.scales.x.time.tooltipFormat = "PP";
          } else if (timeUnit === "hour") {
            options.scales.x.time.tooltipFormat = "MMM d, h a";
          } else if (timeUnit === "minute") {
            options.scales.x.time.tooltipFormat = "h:mm a";
          }
        }
      }
    }

    return {
      labels: labels,
      datasets: datasets
    };
  }

  var defaultExport$2 = function defaultExport(library) {
    this.name = "chartjs";
    this.library = library;
  };

  defaultExport$2.prototype.renderLineChart = function renderLineChart (chart, chartType) {
    if (!chartType) {
      chartType = "line";
    }

    var chartOptions = {};

    var options = jsOptions$2(chart, merge(chartOptions, chart.options));
    setFormatOptions$1(chart, options, chartType);

    var data = createDataTable(chart, options, chartType);

    if (chart.xtype === "number") {
      options.scales.x.type = options.scales.x.type || "linear";
      options.scales.x.position = options.scales.x.position || "bottom";
    } else {
      options.scales.x.type = chart.xtype === "string" ? "category" : "time";
    }

    this.drawChart(chart, "line", data, options);
  };

  defaultExport$2.prototype.renderPieChart = function renderPieChart (chart) {
    var options = merge({}, baseOptions);
    if (chart.options.donut) {
      options.cutout = "50%";
    }

    if ("legend" in chart.options) {
      hideLegend$2(options, chart.options.legend);
    }

    if (chart.options.title) {
      setTitle$2(options, chart.options.title);
    }

    options = merge(options, chart.options.library || {});
    setFormatOptions$1(chart, options, "pie");

    var labels = [];
    var values = [];
    for (var i = 0; i < chart.data.length; i++) {
      var point = chart.data[i];
      labels.push(point[0]);
      values.push(point[1]);
    }

    var dataset = {
      data: values,
      backgroundColor: chart.options.colors || defaultColors
    };
    dataset = merge(dataset, chart.options.dataset || {});

    var data = {
      labels: labels,
      datasets: [dataset]
    };

    this.drawChart(chart, "pie", data, options);
  };

  defaultExport$2.prototype.renderColumnChart = function renderColumnChart (chart, chartType) {
    var options;
    if (chartType === "bar") {
      var barOptions = merge(baseOptions, defaultOptions$2);
      barOptions.indexAxis = "y";

      // ensure gridlines have proper orientation
      barOptions.scales.x.grid.drawOnChartArea = true;
      barOptions.scales.y.grid.drawOnChartArea = false;
      delete barOptions.scales.y.ticks.maxTicksLimit;

      options = jsOptionsFunc(barOptions, hideLegend$2, setTitle$2, setBarMin$1, setBarMax$1, setStacked$2, setXtitle$2, setYtitle$2)(chart, chart.options);
    } else {
      options = jsOptions$2(chart, chart.options);
    }
    setFormatOptions$1(chart, options, chartType);
    var data = createDataTable(chart, options, "column");
    if (chartType !== "bar") {
      setLabelSize(chart, data, options);
    }
    if (!("mode" in options.interaction)) {
      options.interaction.mode = "index";
    }
    this.drawChart(chart, "bar", data, options);
  };

  defaultExport$2.prototype.renderAreaChart = function renderAreaChart (chart) {
    this.renderLineChart(chart, "area");
  };

  defaultExport$2.prototype.renderBarChart = function renderBarChart (chart) {
    this.renderColumnChart(chart, "bar");
  };

  defaultExport$2.prototype.renderScatterChart = function renderScatterChart (chart, chartType) {
    chartType = chartType || "scatter";

    var options = jsOptions$2(chart, chart.options);
    setFormatOptions$1(chart, options, chartType);

    if (!("showLine" in options)) {
      options.showLine = false;
    }

    var data = createDataTable(chart, options, chartType);

    options.scales.x.type = options.scales.x.type || "linear";
    options.scales.x.position = options.scales.x.position || "bottom";

    // prevent grouping hover and tooltips
    if (!("mode" in options.interaction)) {
      options.interaction.mode = "nearest";
    }

    this.drawChart(chart, chartType, data, options);
  };

  defaultExport$2.prototype.renderBubbleChart = function renderBubbleChart (chart) {
    this.renderScatterChart(chart, "bubble");
  };

  defaultExport$2.prototype.destroy = function destroy (chart) {
    if (chart.chart) {
      chart.chart.destroy();
    }
  };

  defaultExport$2.prototype.drawChart = function drawChart (chart, type, data, options) {
    this.destroy(chart);
    if (chart.destroyed) { return; }

    var chartOptions = {
      type: type,
      data: data,
      options: options
    };

    if (chart.options.code) {
      window.console.log("new Chart(ctx, " + JSON.stringify(chartOptions) + ");");
    }

    chart.element.innerHTML = "<canvas></canvas>";
    var ctx = chart.element.getElementsByTagName("CANVAS")[0];
    chart.chart = new this.library(ctx, chartOptions);
  };

  var defaultOptions$1 = {
    chart: {},
    xAxis: {
      title: {
        text: null
      },
      labels: {
        style: {
          fontSize: "12px"
        }
      }
    },
    yAxis: {
      title: {
        text: null
      },
      labels: {
        style: {
          fontSize: "12px"
        }
      }
    },
    title: {
      text: null
    },
    credits: {
      enabled: false
    },
    legend: {
      borderWidth: 0
    },
    tooltip: {
      style: {
        fontSize: "12px"
      }
    },
    plotOptions: {
      areaspline: {},
      area: {},
      series: {
        marker: {}
      }
    },
    time: {
      useUTC: false
    }
  };

  function hideLegend$1(options, legend, hideLegend) {
    if (legend !== undefined) {
      options.legend.enabled = !!legend;
      if (legend && legend !== true) {
        if (legend === "top" || legend === "bottom") {
          options.legend.verticalAlign = legend;
        } else {
          options.legend.layout = "vertical";
          options.legend.verticalAlign = "middle";
          options.legend.align = legend;
        }
      }
    } else if (hideLegend) {
      options.legend.enabled = false;
    }
  }

  function setTitle$1(options, title) {
    options.title.text = title;
  }

  function setMin$1(options, min) {
    options.yAxis.min = min;
  }

  function setMax$1(options, max) {
    options.yAxis.max = max;
  }

  function setStacked$1(options, stacked) {
    var stackedValue = stacked ? (stacked === true ? "normal" : stacked) : null;
    options.plotOptions.series.stacking = stackedValue;
    options.plotOptions.area.stacking = stackedValue;
    options.plotOptions.areaspline.stacking = stackedValue;
  }

  function setXtitle$1(options, title) {
    options.xAxis.title.text = title;
  }

  function setYtitle$1(options, title) {
    options.yAxis.title.text = title;
  }

  var jsOptions$1 = jsOptionsFunc(defaultOptions$1, hideLegend$1, setTitle$1, setMin$1, setMax$1, setStacked$1, setXtitle$1, setYtitle$1);

  function setFormatOptions(chart, options, chartType) {
    var formatOptions = {
      prefix: chart.options.prefix,
      suffix: chart.options.suffix,
      thousands: chart.options.thousands,
      decimal: chart.options.decimal,
      precision: chart.options.precision,
      round: chart.options.round,
      zeros: chart.options.zeros
    };

    // skip when axis is an array (like with min/max)
    if (chartType !== "pie" && !isArray(options.yAxis) && !options.yAxis.labels.formatter) {
      options.yAxis.labels.formatter = function () {
        return formatValue("", this.value, formatOptions);
      };
    }

    if (!options.tooltip.pointFormatter && !options.tooltip.pointFormat) {
      options.tooltip.pointFormatter = function () {
        return '<span style="color:' + this.color + '">\u25CF</span> ' + formatValue(this.series.name + ': <b>', this.y, formatOptions) + '</b><br/>';
      };
    }
  }

  var defaultExport$1 = function defaultExport(library) {
    this.name = "highcharts";
    this.library = library;
  };

  defaultExport$1.prototype.renderLineChart = function renderLineChart (chart, chartType) {
    chartType = chartType || "spline";
    var chartOptions = {};
    if (chartType === "areaspline") {
      chartOptions = {
        plotOptions: {
          areaspline: {
            stacking: "normal"
          },
          area: {
            stacking: "normal"
          },
          series: {
            marker: {
              enabled: false
            }
          }
        }
      };
    }

    if (chart.options.curve === false) {
      if (chartType === "areaspline") {
        chartType = "area";
      } else if (chartType === "spline") {
        chartType = "line";
      }
    }

    var options = jsOptions$1(chart, chart.options, chartOptions);
    if (chart.xtype === "number") {
      options.xAxis.type = options.xAxis.type || "linear";
    } else {
      options.xAxis.type = chart.xtype === "string" ? "category" : "datetime";
    }
    if (!options.chart.type) {
      options.chart.type = chartType;
    }
    setFormatOptions(chart, options, chartType);

    var series = chart.data;
    for (var i = 0; i < series.length; i++) {
      series[i].name = series[i].name || "Value";
      var data = series[i].data;
      if (chart.xtype === "datetime") {
        for (var j = 0; j < data.length; j++) {
          data[j][0] = data[j][0].getTime();
        }
      } else if (chart.xtype === "number") {
        data.sort(sortByNumberSeries);
      }
      series[i].marker = {symbol: "circle"};
      if (chart.options.points === false) {
        series[i].marker.enabled = false;
      }
    }

    this.drawChart(chart, series, options);
  };

  defaultExport$1.prototype.renderScatterChart = function renderScatterChart (chart) {
    var options = jsOptions$1(chart, chart.options, {});
    options.chart.type = "scatter";
    this.drawChart(chart, chart.data, options);
  };

  defaultExport$1.prototype.renderPieChart = function renderPieChart (chart) {
    var chartOptions = merge(defaultOptions$1, {});

    if (chart.options.colors) {
      chartOptions.colors = chart.options.colors;
    }
    if (chart.options.donut) {
      chartOptions.plotOptions = {pie: {innerSize: "50%"}};
    }

    if ("legend" in chart.options) {
      hideLegend$1(chartOptions, chart.options.legend);
    }

    if (chart.options.title) {
      setTitle$1(chartOptions, chart.options.title);
    }

    var options = merge(chartOptions, chart.options.library || {});
    setFormatOptions(chart, options, "pie");
    var series = [{
      type: "pie",
      name: chart.options.label || "Value",
      data: chart.data
    }];

    this.drawChart(chart, series, options);
  };

  defaultExport$1.prototype.renderColumnChart = function renderColumnChart (chart, chartType) {
    chartType = chartType || "column";
    var series = chart.data;
    var options = jsOptions$1(chart, chart.options);
    var rows = [];
    var categories = [];
    options.chart.type = chartType;
    setFormatOptions(chart, options, chartType);

    for (var i = 0; i < series.length; i++) {
      var s = series[i];

      for (var j = 0; j < s.data.length; j++) {
        var d = s.data[j];
        if (!rows[d[0]]) {
          rows[d[0]] = new Array(series.length);
          categories.push(d[0]);
        }
        rows[d[0]][i] = d[1];
      }
    }

    if (chart.xtype === "number") {
      categories.sort(sortByNumber);
    }

    options.xAxis.categories = categories;

    var newSeries = [];
    for (var i$1 = 0; i$1 < series.length; i$1++) {
      var d$1 = [];
      for (var j$1 = 0; j$1 < categories.length; j$1++) {
        d$1.push(rows[categories[j$1]][i$1] || 0);
      }

      var d2 = {
        name: series[i$1].name || "Value",
        data: d$1
      };
      if (series[i$1].stack) {
        d2.stack = series[i$1].stack;
      }

      newSeries.push(d2);
    }

    this.drawChart(chart, newSeries, options);
  };

  defaultExport$1.prototype.renderBarChart = function renderBarChart (chart) {
    this.renderColumnChart(chart, "bar");
  };

  defaultExport$1.prototype.renderAreaChart = function renderAreaChart (chart) {
    this.renderLineChart(chart, "areaspline");
  };

  defaultExport$1.prototype.destroy = function destroy (chart) {
    if (chart.chart) {
      chart.chart.destroy();
    }
  };

  defaultExport$1.prototype.drawChart = function drawChart (chart, data, options) {
    this.destroy(chart);
    if (chart.destroyed) { return; }

    options.chart.renderTo = chart.element.id;
    options.series = data;

    if (chart.options.code) {
      window.console.log("new Highcharts.Chart(" + JSON.stringify(options) + ");");
    }

    chart.chart = new this.library.Chart(options);
  };

  var loaded = {};
  var callbacks = [];

  // Set chart options
  var defaultOptions = {
    chartArea: {},
    fontName: "'Lucida Grande', 'Lucida Sans Unicode', Verdana, Arial, Helvetica, sans-serif",
    pointSize: 6,
    legend: {
      textStyle: {
        fontSize: 12,
        color: "#444"
      },
      alignment: "center",
      position: "right"
    },
    curveType: "function",
    hAxis: {
      textStyle: {
        color: "#666",
        fontSize: 12
      },
      titleTextStyle: {},
      gridlines: {
        color: "transparent"
      },
      baselineColor: "#ccc",
      viewWindow: {}
    },
    vAxis: {
      textStyle: {
        color: "#666",
        fontSize: 12
      },
      titleTextStyle: {},
      baselineColor: "#ccc",
      viewWindow: {}
    },
    tooltip: {
      textStyle: {
        color: "#666",
        fontSize: 12
      }
    }
  };

  function hideLegend(options, legend, hideLegend) {
    if (legend !== undefined) {
      var position;
      if (!legend) {
        position = "none";
      } else if (legend === true) {
        position = "right";
      } else {
        position = legend;
      }
      options.legend.position = position;
    } else if (hideLegend) {
      options.legend.position = "none";
    }
  }

  function setTitle(options, title) {
    options.title = title;
    options.titleTextStyle = {color: "#333", fontSize: "20px"};
  }

  function setMin(options, min) {
    options.vAxis.viewWindow.min = min;
  }

  function setMax(options, max) {
    options.vAxis.viewWindow.max = max;
  }

  function setBarMin(options, min) {
    options.hAxis.viewWindow.min = min;
  }

  function setBarMax(options, max) {
    options.hAxis.viewWindow.max = max;
  }

  function setStacked(options, stacked) {
    options.isStacked = stacked || false;
  }

  function setXtitle(options, title) {
    options.hAxis.title = title;
    options.hAxis.titleTextStyle.italic = false;
  }

  function setYtitle(options, title) {
    options.vAxis.title = title;
    options.vAxis.titleTextStyle.italic = false;
  }

  var jsOptions = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setMin, setMax, setStacked, setXtitle, setYtitle);

  function resize(callback) {
    if (window.attachEvent) {
      window.attachEvent("onresize", callback);
    } else if (window.addEventListener) {
      window.addEventListener("resize", callback, true);
    }
    callback();
  }

  var defaultExport = function defaultExport(library) {
    this.name = "google";
    this.library = library;
  };

  defaultExport.prototype.renderLineChart = function renderLineChart (chart) {
      var this$1$1 = this;

    this.waitForLoaded(chart, function () {
      var chartOptions = {};

      if (chart.options.curve === false) {
        chartOptions.curveType = "none";
      }

      if (chart.options.points === false) {
        chartOptions.pointSize = 0;
      }

      var options = jsOptions(chart, chart.options, chartOptions);
      var data = this$1$1.createDataTable(chart.data, chart.xtype);

      this$1$1.drawChart(chart, "LineChart", data, options);
    });
  };

  defaultExport.prototype.renderPieChart = function renderPieChart (chart) {
      var this$1$1 = this;

    this.waitForLoaded(chart, function () {
      var chartOptions = {
        chartArea: {
          top: "10%",
          height: "80%"
        },
        legend: {}
      };
      if (chart.options.colors) {
        chartOptions.colors = chart.options.colors;
      }
      if (chart.options.donut) {
        chartOptions.pieHole = 0.5;
      }
      if ("legend" in chart.options) {
        hideLegend(chartOptions, chart.options.legend);
      }
      if (chart.options.title) {
        setTitle(chartOptions, chart.options.title);
      }
      var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});

      var data = new this$1$1.library.visualization.DataTable();
      data.addColumn("string", "");
      data.addColumn("number", "Value");
      data.addRows(chart.data);

      this$1$1.drawChart(chart, "PieChart", data, options);
    });
  };

  defaultExport.prototype.renderColumnChart = function renderColumnChart (chart) {
      var this$1$1 = this;

    this.waitForLoaded(chart, function () {
      var options = jsOptions(chart, chart.options);
      var data = this$1$1.createDataTable(chart.data, chart.xtype);

      this$1$1.drawChart(chart, "ColumnChart", data, options);
    });
  };

  defaultExport.prototype.renderBarChart = function renderBarChart (chart) {
      var this$1$1 = this;

    this.waitForLoaded(chart, function () {
      var chartOptions = {
        hAxis: {
          gridlines: {
            color: "#ccc"
          }
        }
      };
      var options = jsOptionsFunc(defaultOptions, hideLegend, setTitle, setBarMin, setBarMax, setStacked, setXtitle, setYtitle)(chart, chart.options, chartOptions);
      var data = this$1$1.createDataTable(chart.data, chart.xtype);

      this$1$1.drawChart(chart, "BarChart", data, options);
    });
  };

  defaultExport.prototype.renderAreaChart = function renderAreaChart (chart) {
      var this$1$1 = this;

    this.waitForLoaded(chart, function () {
      var chartOptions = {
        isStacked: true,
        pointSize: 0,
        areaOpacity: 0.5
      };

      var options = jsOptions(chart, chart.options, chartOptions);
      var data = this$1$1.createDataTable(chart.data, chart.xtype);

      this$1$1.drawChart(chart, "AreaChart", data, options);
    });
  };

  defaultExport.prototype.renderGeoChart = function renderGeoChart (chart) {
      var this$1$1 = this;

    this.waitForLoaded(chart, "geochart", function () {
      var chartOptions = {
        legend: "none",
        colorAxis: {
          colors: chart.options.colors || ["#f6c7b6", "#ce502d"]
        }
      };
      var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});

      var data = new this$1$1.library.visualization.DataTable();
      data.addColumn("string", "");
      data.addColumn("number", chart.options.label || "Value");
      data.addRows(chart.data);

      this$1$1.drawChart(chart, "GeoChart", data, options);
    });
  };

  defaultExport.prototype.renderScatterChart = function renderScatterChart (chart) {
      var this$1$1 = this;

    this.waitForLoaded(chart, function () {
      var chartOptions = {};
      var options = jsOptions(chart, chart.options, chartOptions);

      var series = chart.data;
      var rows2 = [];
      for (var i = 0; i < series.length; i++) {
        series[i].name = series[i].name || "Value";
        var d = series[i].data;
        for (var j = 0; j < d.length; j++) {
          var row = new Array(series.length + 1);
          row[0] = d[j][0];
          row[i + 1] = d[j][1];
          rows2.push(row);
        }
      }

      var data = new this$1$1.library.visualization.DataTable();
      data.addColumn("number", "");
      for (var i$1 = 0; i$1 < series.length; i$1++) {
        data.addColumn("number", series[i$1].name);
      }
      data.addRows(rows2);

      this$1$1.drawChart(chart, "ScatterChart", data, options);
    });
  };

  defaultExport.prototype.renderTimeline = function renderTimeline (chart) {
      var this$1$1 = this;

    this.waitForLoaded(chart, "timeline", function () {
      var chartOptions = {
        legend: "none"
      };

      if (chart.options.colors) {
        chartOptions.colors = chart.options.colors;
      }
      var options = merge(merge(defaultOptions, chartOptions), chart.options.library || {});

      var data = new this$1$1.library.visualization.DataTable();
      data.addColumn({type: "string", id: "Name"});
      data.addColumn({type: "date", id: "Start"});
      data.addColumn({type: "date", id: "End"});
      data.addRows(chart.data);

      chart.element.style.lineHeight = "normal";

      this$1$1.drawChart(chart, "Timeline", data, options);
    });
  };

  // TODO remove resize events
  defaultExport.prototype.destroy = function destroy (chart) {
    if (chart.chart) {
      chart.chart.clearChart();
    }
  };

  defaultExport.prototype.drawChart = function drawChart (chart, type, data, options) {
    this.destroy(chart);
    if (chart.destroyed) { return; }

    if (chart.options.code) {
      window.console.log("var data = new google.visualization.DataTable(" + data.toJSON() + ");\nvar chart = new google.visualization." + type + "(element);\nchart.draw(data, " + JSON.stringify(options) + ");");
    }

    chart.chart = new this.library.visualization[type](chart.element);
    resize(function () {
      chart.chart.draw(data, options);
    });
  };

  defaultExport.prototype.waitForLoaded = function waitForLoaded (chart, pack, callback) {
      var this$1$1 = this;

    if (!callback) {
      callback = pack;
      pack = "corechart";
    }

    callbacks.push({pack: pack, callback: callback});

    if (loaded[pack]) {
      this.runCallbacks();
    } else {
      loaded[pack] = true;

      // https://groups.google.com/forum/#!topic/google-visualization-api/fMKJcyA2yyI
      var loadOptions = {
        packages: [pack],
        callback: function () { this$1$1.runCallbacks(); }
      };
      var config = chart.__config();
      if (config.language) {
        loadOptions.language = config.language;
      }
      if (pack === "geochart" && config.mapsApiKey) {
        loadOptions.mapsApiKey = config.mapsApiKey;
      }

      this.library.charts.load("current", loadOptions);
    }
  };

  defaultExport.prototype.runCallbacks = function runCallbacks () {
    for (var i = 0; i < callbacks.length; i++) {
      var cb = callbacks[i];
      var call = this.library.visualization && ((cb.pack === "corechart" && this.library.visualization.LineChart) || (cb.pack === "timeline" && this.library.visualization.Timeline) || (cb.pack === "geochart" && this.library.visualization.GeoChart));
      if (call) {
        cb.callback();
        callbacks.splice(i, 1);
        i--;
      }
    }
  };

  // cant use object as key
  defaultExport.prototype.createDataTable = function createDataTable (series, columnType) {
    var rows = [];
    var sortedLabels = [];
    for (var i = 0; i < series.length; i++) {
      var s = series[i];
      series[i].name = series[i].name || "Value";

      for (var j = 0; j < s.data.length; j++) {
        var d = s.data[j];
        var key = columnType === "datetime" ? d[0].getTime() : d[0];
        if (!rows[key]) {
          rows[key] = new Array(series.length);
          sortedLabels.push(key);
        }
        rows[key][i] = d[1];
      }
    }

    var rows2 = [];
    var values = [];
    for (var j$1 = 0; j$1 < sortedLabels.length; j$1++) {
      var i$1 = sortedLabels[j$1];
      var value = (void 0);
      if (columnType === "datetime") {
        value = new Date(i$1);
        values.push(value);
      } else {
        value = i$1;
      }
      rows2.push([value].concat(rows[i$1]));
    }

    var day = true;
    if (columnType === "datetime") {
      rows2.sort(sortByTime);

      var timeUnit = calculateTimeUnit(values, true);
      day = isDay(timeUnit);
    } else if (columnType === "number") {
      rows2.sort(sortByNumberSeries);

      for (var i$2 = 0; i$2 < rows2.length; i$2++) {
        rows2[i$2][0] = toStr(rows2[i$2][0]);
      }

      columnType = "string";
    }

    // create datatable
    var data = new this.library.visualization.DataTable();
    columnType = columnType === "datetime" && day ? "date" : columnType;
    data.addColumn(columnType, "");
    for (var i$3 = 0; i$3 < series.length; i$3++) {
      data.addColumn("number", series[i$3].name);
    }
    data.addRows(rows2);

    return data;
  };

  var adapters = [];

  function getAdapterType(library) {
    if (library) {
      if (library.product === "Highcharts") {
        return defaultExport$1;
      } else if (library.charts) {
        return defaultExport;
      } else if (isFunction(library)) {
        return defaultExport$2;
      }
    }
    throw new Error("Unknown adapter");
  }

  function addAdapter(library) {
    var adapterType = getAdapterType(library);

    for (var i = 0; i < adapters.length; i++) {
      if (adapters[i].library === library) {
        return;
      }
    }

    adapters.push(new adapterType(library));
  }

  function loadAdapters() {
    if ("Chart" in window) {
      addAdapter(window.Chart);
    }

    if ("Highcharts" in window) {
      addAdapter(window.Highcharts);
    }

    if (window.google && window.google.charts) {
      addAdapter(window.google);
    }
  }

  // TODO remove chartType if cross-browser way
  // to get the name of the chart class
  function callAdapter(chartType, chart) {
    var fnName = "render" + chartType;
    var adapterName = chart.options.adapter;

    loadAdapters();

    for (var i = 0; i < adapters.length; i++) {
      var adapter = adapters[i];
      if ((!adapterName || adapterName === adapter.name) && isFunction(adapter[fnName])) {
        chart.adapter = adapter.name;
        chart.__adapterObject = adapter;
        return adapter[fnName](chart);
      }
    }

    if (adapters.length > 0) {
      throw new Error("No charting library found for " + chartType);
    } else {
      throw new Error("No charting libraries found - be sure to include one before your charts");
    }
  }

  var Chartkick = {
    charts: {},
    configure: function (options) {
      for (var key in options) {
        if (Object.prototype.hasOwnProperty.call(options, key)) {
          Chartkick.config[key] = options[key];
        }
      }
    },
    setDefaultOptions: function (opts) {
      Chartkick.options = opts;
    },
    eachChart: function (callback) {
      for (var chartId in Chartkick.charts) {
        if (Object.prototype.hasOwnProperty.call(Chartkick.charts, chartId)) {
          callback(Chartkick.charts[chartId]);
        }
      }
    },
    destroyAll: function () {
      for (var chartId in Chartkick.charts) {
        if (Object.prototype.hasOwnProperty.call(Chartkick.charts, chartId)) {
          Chartkick.charts[chartId].destroy();
          delete Chartkick.charts[chartId];
        }
      }
    },
    config: {},
    options: {},
    adapters: adapters,
    addAdapter: addAdapter,
    use: function (adapter) {
      addAdapter(adapter);
      return Chartkick;
    }
  };

  function formatSeriesBubble(data) {
    var r = [];
    for (var i = 0; i < data.length; i++) {
      r.push([toFloat(data[i][0]), toFloat(data[i][1]), toFloat(data[i][2])]);
    }
    return r;
  }

  // casts data to proper type
  // sorting is left to adapters
  function formatSeriesData(data, keyType) {
    if (keyType === "bubble") {
      return formatSeriesBubble(data);
    }

    var keyFunc;
    if (keyType === "number") {
      keyFunc = toFloat;
    } else if (keyType === "datetime") {
      keyFunc = toDate;
    } else {
      keyFunc = toStr;
    }

    var r = [];
    for (var i = 0; i < data.length; i++) {
      r.push([keyFunc(data[i][0]), toFloat(data[i][1])]);
    }
    return r;
  }

  function detectXType(series, noDatetime, options) {
    if (dataEmpty(series)) {
      if ((options.xmin || options.xmax) && (!options.xmin || isDate(options.xmin)) && (!options.xmax || isDate(options.xmax))) {
        return "datetime";
      } else {
        return "number";
      }
    } else if (detectXTypeWithFunction(series, isNumber)) {
      return "number";
    } else if (!noDatetime && detectXTypeWithFunction(series, isDate)) {
      return "datetime";
    } else {
      return "string";
    }
  }

  function detectXTypeWithFunction(series, func) {
    for (var i = 0; i < series.length; i++) {
      var data = toArr(series[i].data);
      for (var j = 0; j < data.length; j++) {
        if (!func(data[j][0])) {
          return false;
        }
      }
    }
    return true;
  }

  // creates a shallow copy of each element of the array
  // elements are expected to be objects
  function copySeries(series) {
    var newSeries = [];
    for (var i = 0; i < series.length; i++) {
      var copy = {};
      for (var j in series[i]) {
        if (Object.prototype.hasOwnProperty.call(series[i], j)) {
          copy[j] = series[i][j];
        }
      }
      newSeries.push(copy);
    }
    return newSeries;
  }

  function processSeries(chart, keyType, noDatetime) {
    var opts = chart.options;
    var series = chart.rawData;

    // see if one series or multiple
    chart.singleSeriesFormat = !isArray(series) || !isPlainObject(series[0]);
    if (chart.singleSeriesFormat) {
      series = [{name: opts.label, data: series}];
    }

    // convert to array
    // must come before dataEmpty check
    series = copySeries(series);
    for (var i = 0; i < series.length; i++) {
      series[i].data = toArr(series[i].data);
    }

    chart.xtype = keyType || (opts.discrete ? "string" : detectXType(series, noDatetime, opts));

    // right format
    for (var i$1 = 0; i$1 < series.length; i$1++) {
      series[i$1].data = formatSeriesData(series[i$1].data, chart.xtype);
    }

    return series;
  }

  function processSimple(chart) {
    var perfectData = toArr(chart.rawData);
    for (var i = 0; i < perfectData.length; i++) {
      perfectData[i] = [toStr(perfectData[i][0]), toFloat(perfectData[i][1])];
    }
    return perfectData;
  }

  function dataEmpty(data, chartType) {
    if (chartType === "PieChart" || chartType === "GeoChart" || chartType === "Timeline") {
      return data.length === 0;
    } else {
      for (var i = 0; i < data.length; i++) {
        if (data[i].data.length > 0) {
          return false;
        }
      }
      return true;
    }
  }

  function addDownloadButton(chart) {
    var download = chart.options.download;
    if (download === true) {
      download = {};
    } else if (typeof download === "string") {
      download = {filename: download};
    }

    var link = document.createElement("a");
    link.download = download.filename || "chart.png";
    link.style.position = "absolute";
    link.style.top = "20px";
    link.style.right = "20px";
    link.style.zIndex = 1000;
    link.style.lineHeight = "20px";
    link.target = "_blank"; // for safari

    var image = document.createElement("img");
    // icon from Font Awesome, modified to set fill color
    var svg = "<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 512 512\"><!--! Font Awesome Free 6.2.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. --><path fill=\"#CCCCCC\" d=\"M344 240h-56L287.1 152c0-13.25-10.75-24-24-24h-16C234.7 128 223.1 138.8 223.1 152L224 240h-56c-9.531 0-18.16 5.656-22 14.38C142.2 263.1 143.9 273.3 150.4 280.3l88.75 96C243.7 381.2 250.1 384 256.8 384c7.781-.3125 13.25-2.875 17.75-7.844l87.25-96c6.406-7.031 8.031-17.19 4.188-25.88S353.5 240 344 240zM256 0C114.6 0 0 114.6 0 256s114.6 256 256 256s256-114.6 256-256S397.4 0 256 0zM256 464c-114.7 0-208-93.31-208-208S141.3 48 256 48s208 93.31 208 208S370.7 464 256 464z\"/></svg>";
    image.src = "data:image/svg+xml;utf8," + (encodeURIComponent(svg));
    image.alt = "Download";
    image.style.width = "20px";
    image.style.height = "20px";
    image.style.border = "none";
    link.appendChild(image);

    var element = chart.element;
    element.style.position = "relative";

    chart.__downloadAttached = true;

    // mouseenter
    chart.__enterEvent = element.addEventListener("mouseover", function (e) {
      var related = e.relatedTarget;
      // check download option again to ensure it wasn't changed
      if ((!related || (related !== this && !this.contains(related))) && chart.options.download) {
        link.href = chart.toImage(download);
        element.appendChild(link);
      }
    });

    // mouseleave
    chart.__leaveEvent = element.addEventListener("mouseout", function (e) {
      var related = e.relatedTarget;
      if (!related || (related !== this && !this.contains(related))) {
        if (link.parentNode) {
          link.parentNode.removeChild(link);
        }
      }
    });
  }

  var pendingRequests = [];
  var runningRequests = 0;
  var maxRequests = 4;

  function pushRequest(url, success, error) {
    pendingRequests.push([url, success, error]);
    runNext();
  }

  function runNext() {
    if (runningRequests < maxRequests) {
      var request = pendingRequests.shift();
      if (request) {
        runningRequests++;
        getJSON(request[0], request[1], request[2]);
        runNext();
      }
    }
  }

  function requestComplete() {
    runningRequests--;
    runNext();
  }

  function getJSON(url, success, error) {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", url, true);
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.onload = function () {
      requestComplete();
      if (xhr.status === 200) {
        success(JSON.parse(xhr.responseText));
      } else {
        error(xhr.statusText);
      }
    };
    xhr.send();
  }

  // helpers

  function setText(element, text) {
    element.textContent = text;
  }

  // TODO remove prefix for all messages
  function chartError(element, message, noPrefix) {
    if (!noPrefix) {
      message = "Error Loading Chart: " + message;
    }
    setText(element, message);
    element.style.color = "#ff0000";
  }

  function errorCatcher(chart) {
    try {
      chart.__render();
    } catch (err) {
      chartError(chart.element, err.message);
      throw err;
    }
  }

  function fetchDataSource(chart, dataSource, showLoading) {
    // only show loading message for urls and callbacks
    if (showLoading && chart.options.loading && (typeof dataSource === "string" || typeof dataSource === "function")) {
      setText(chart.element, chart.options.loading);
    }

    if (typeof dataSource === "string") {
      pushRequest(dataSource, function (data) {
        chart.rawData = data;
        errorCatcher(chart);
      }, function (message) {
        chartError(chart.element, message);
      });
    } else if (typeof dataSource === "function") {
      try {
        dataSource(function (data) {
          chart.rawData = data;
          errorCatcher(chart);
        }, function (message) {
          chartError(chart.element, message, true);
        });
      } catch (err) {
        chartError(chart.element, err, true);
      }
    } else {
      chart.rawData = dataSource;
      errorCatcher(chart);
    }
  }

  function renderChart(chartType, chart) {
    if (dataEmpty(chart.data, chartType)) {
      var message = chart.options.empty || (chart.options.messages && chart.options.messages.empty) || "No data";
      setText(chart.element, message);
    } else {
      callAdapter(chartType, chart);
      // TODO add downloadSupported method to adapter
      if (chart.options.download && !chart.__downloadAttached && chart.adapter === "chartjs") {
        addDownloadButton(chart);
      }
    }
  }

  function getElement(element) {
    if (typeof element === "string") {
      var elementId = element;
      element = document.getElementById(element);
      if (!element) {
        throw new Error("No element with id " + elementId);
      }
    }
    return element;
  }

  // define classes

  var Chart = function Chart(element, dataSource, options) {
    this.element = getElement(element);
    this.options = merge(Chartkick.options, options || {});
    this.dataSource = dataSource;

    // TODO handle charts without an id for eachChart and destroyAll
    if (this.element.id) {
      Chartkick.charts[this.element.id] = this;
    }

    fetchDataSource(this, dataSource, true);

    if (this.options.refresh) {
      this.startRefresh();
    }
  };

  Chart.prototype.getElement = function getElement () {
    return this.element;
  };

  Chart.prototype.getDataSource = function getDataSource () {
    return this.dataSource;
  };

  Chart.prototype.getData = function getData () {
    return this.data;
  };

  Chart.prototype.getOptions = function getOptions () {
    return this.options;
  };

  Chart.prototype.getChartObject = function getChartObject () {
    return this.chart;
  };

  Chart.prototype.getAdapter = function getAdapter () {
    return this.adapter;
  };

  Chart.prototype.updateData = function updateData (dataSource, options) {
    this.dataSource = dataSource;
    if (options) {
      this.__updateOptions(options);
    }
    fetchDataSource(this, dataSource, true);
  };

  Chart.prototype.setOptions = function setOptions (options) {
    this.__updateOptions(options);
    this.redraw();
  };

  Chart.prototype.redraw = function redraw () {
    fetchDataSource(this, this.rawData);
  };

  Chart.prototype.refreshData = function refreshData () {
    if (typeof this.dataSource === "string") {
      // prevent browser from caching
      var sep = this.dataSource.indexOf("?") === -1 ? "?" : "&";
      var url = this.dataSource + sep + "_=" + (new Date()).getTime();
      fetchDataSource(this, url);
    } else if (typeof this.dataSource === "function") {
      fetchDataSource(this, this.dataSource);
    }
  };

  Chart.prototype.startRefresh = function startRefresh () {
      var this$1$1 = this;

    var refresh = this.options.refresh;

    if (refresh && typeof this.dataSource !== "string" && typeof this.dataSource !== "function") {
      throw new Error("Data source must be a URL or callback for refresh");
    }

    if (!this.intervalId) {
      if (refresh) {
        this.intervalId = setInterval(function () {
          this$1$1.refreshData();
        }, refresh * 1000);
      } else {
        throw new Error("No refresh interval");
      }
    }
  };

  Chart.prototype.stopRefresh = function stopRefresh () {
    if (this.intervalId) {
      clearInterval(this.intervalId);
      this.intervalId = null;
    }
  };

  Chart.prototype.toImage = function toImage (download) {
    // TODO move logic to adapter
    if (this.adapter === "chartjs") {
      if (download && download.background && download.background !== "transparent") {
        // https://stackoverflow.com/questions/30464750/chartjs-line-chart-set-background-color
        var canvas = this.chart.canvas;
        var ctx = this.chart.ctx;
        var tmpCanvas = document.createElement("canvas");
        var tmpCtx = tmpCanvas.getContext("2d");
        tmpCanvas.width = ctx.canvas.width;
        tmpCanvas.height = ctx.canvas.height;
        tmpCtx.fillStyle = download.background;
        tmpCtx.fillRect(0, 0, tmpCanvas.width, tmpCanvas.height);
        tmpCtx.drawImage(canvas, 0, 0);
        return tmpCanvas.toDataURL("image/png");
      } else {
        return this.chart.toBase64Image();
      }
    } else {
      throw new Error("Feature only available for Chart.js");
    }
  };

  Chart.prototype.destroy = function destroy () {
    this.destroyed = true;
    this.stopRefresh();

    if (this.__adapterObject) {
      this.__adapterObject.destroy(this);
    }

    if (this.__enterEvent) {
      this.element.removeEventListener("mouseover", this.__enterEvent);
    }

    if (this.__leaveEvent) {
      this.element.removeEventListener("mouseout", this.__leaveEvent);
    }
  };

  Chart.prototype.__updateOptions = function __updateOptions (options) {
    var updateRefresh = options.refresh && options.refresh !== this.options.refresh;
    this.options = merge(Chartkick.options, options);
    if (updateRefresh) {
      this.stopRefresh();
      this.startRefresh();
    }
  };

  Chart.prototype.__render = function __render () {
    this.data = this.__processData();
    renderChart(this.__chartName(), this);
  };

  Chart.prototype.__config = function __config () {
    return Chartkick.config;
  };

  var LineChart = /*@__PURE__*/(function (Chart) {
    function LineChart () {
      Chart.apply(this, arguments);
    }

    if ( Chart ) LineChart.__proto__ = Chart;
    LineChart.prototype = Object.create( Chart && Chart.prototype );
    LineChart.prototype.constructor = LineChart;

    LineChart.prototype.__processData = function __processData () {
      return processSeries(this);
    };

    LineChart.prototype.__chartName = function __chartName () {
      return "LineChart";
    };

    return LineChart;
  }(Chart));

  var PieChart = /*@__PURE__*/(function (Chart) {
    function PieChart () {
      Chart.apply(this, arguments);
    }

    if ( Chart ) PieChart.__proto__ = Chart;
    PieChart.prototype = Object.create( Chart && Chart.prototype );
    PieChart.prototype.constructor = PieChart;

    PieChart.prototype.__processData = function __processData () {
      return processSimple(this);
    };

    PieChart.prototype.__chartName = function __chartName () {
      return "PieChart";
    };

    return PieChart;
  }(Chart));

  var ColumnChart = /*@__PURE__*/(function (Chart) {
    function ColumnChart () {
      Chart.apply(this, arguments);
    }

    if ( Chart ) ColumnChart.__proto__ = Chart;
    ColumnChart.prototype = Object.create( Chart && Chart.prototype );
    ColumnChart.prototype.constructor = ColumnChart;

    ColumnChart.prototype.__processData = function __processData () {
      return processSeries(this, null, true);
    };

    ColumnChart.prototype.__chartName = function __chartName () {
      return "ColumnChart";
    };

    return ColumnChart;
  }(Chart));

  var BarChart = /*@__PURE__*/(function (Chart) {
    function BarChart () {
      Chart.apply(this, arguments);
    }

    if ( Chart ) BarChart.__proto__ = Chart;
    BarChart.prototype = Object.create( Chart && Chart.prototype );
    BarChart.prototype.constructor = BarChart;

    BarChart.prototype.__processData = function __processData () {
      return processSeries(this, null, true);
    };

    BarChart.prototype.__chartName = function __chartName () {
      return "BarChart";
    };

    return BarChart;
  }(Chart));

  var AreaChart = /*@__PURE__*/(function (Chart) {
    function AreaChart () {
      Chart.apply(this, arguments);
    }

    if ( Chart ) AreaChart.__proto__ = Chart;
    AreaChart.prototype = Object.create( Chart && Chart.prototype );
    AreaChart.prototype.constructor = AreaChart;

    AreaChart.prototype.__processData = function __processData () {
      return processSeries(this);
    };

    AreaChart.prototype.__chartName = function __chartName () {
      return "AreaChart";
    };

    return AreaChart;
  }(Chart));

  var GeoChart = /*@__PURE__*/(function (Chart) {
    function GeoChart () {
      Chart.apply(this, arguments);
    }

    if ( Chart ) GeoChart.__proto__ = Chart;
    GeoChart.prototype = Object.create( Chart && Chart.prototype );
    GeoChart.prototype.constructor = GeoChart;

    GeoChart.prototype.__processData = function __processData () {
      return processSimple(this);
    };

    GeoChart.prototype.__chartName = function __chartName () {
      return "GeoChart";
    };

    return GeoChart;
  }(Chart));

  var ScatterChart = /*@__PURE__*/(function (Chart) {
    function ScatterChart () {
      Chart.apply(this, arguments);
    }

    if ( Chart ) ScatterChart.__proto__ = Chart;
    ScatterChart.prototype = Object.create( Chart && Chart.prototype );
    ScatterChart.prototype.constructor = ScatterChart;

    ScatterChart.prototype.__processData = function __processData () {
      return processSeries(this, "number");
    };

    ScatterChart.prototype.__chartName = function __chartName () {
      return "ScatterChart";
    };

    return ScatterChart;
  }(Chart));

  var BubbleChart = /*@__PURE__*/(function (Chart) {
    function BubbleChart () {
      Chart.apply(this, arguments);
    }

    if ( Chart ) BubbleChart.__proto__ = Chart;
    BubbleChart.prototype = Object.create( Chart && Chart.prototype );
    BubbleChart.prototype.constructor = BubbleChart;

    BubbleChart.prototype.__processData = function __processData () {
      return processSeries(this, "bubble");
    };

    BubbleChart.prototype.__chartName = function __chartName () {
      return "BubbleChart";
    };

    return BubbleChart;
  }(Chart));

  var Timeline = /*@__PURE__*/(function (Chart) {
    function Timeline () {
      Chart.apply(this, arguments);
    }

    if ( Chart ) Timeline.__proto__ = Chart;
    Timeline.prototype = Object.create( Chart && Chart.prototype );
    Timeline.prototype.constructor = Timeline;

    Timeline.prototype.__processData = function __processData () {
      var data = this.rawData;
      for (var i = 0; i < data.length; i++) {
        data[i][1] = toDate(data[i][1]);
        data[i][2] = toDate(data[i][2]);
      }
      return data;
    };

    Timeline.prototype.__chartName = function __chartName () {
      return "Timeline";
    };

    return Timeline;
  }(Chart));

  Chartkick.LineChart = LineChart;
  Chartkick.PieChart = PieChart;
  Chartkick.ColumnChart = ColumnChart;
  Chartkick.BarChart = BarChart;
  Chartkick.AreaChart = AreaChart;
  Chartkick.GeoChart = GeoChart;
  Chartkick.ScatterChart = ScatterChart;
  Chartkick.BubbleChart = BubbleChart;
  Chartkick.Timeline = Timeline;

  // not ideal, but allows for simpler integration
  if (typeof window !== "undefined" && !window.Chartkick) {
    window.Chartkick = Chartkick;

    // clean up previous charts before Turbolinks loads new page
    document.addEventListener("turbolinks:before-render", function () {
      if (Chartkick.config.autoDestroy !== false) {
        Chartkick.destroyAll();
      }
    });

    // clean up previous charts before Turbo loads new page
    document.addEventListener("turbo:before-render", function () {
      if (Chartkick.config.autoDestroy !== false) {
        Chartkick.destroyAll();
      }
    });

    // use setTimeout so charting library can come later in same JS file
    setTimeout(function () {
      window.dispatchEvent(new Event("chartkick:load"));
    }, 0);
  }

  // backwards compatibility for esm require
  Chartkick.default = Chartkick;

  return Chartkick;

}));
/*!
 * Chart.js v4.4.3
 * https://www.chartjs.org
 * (c) 2024 Chart.js Contributors
 * Released under the MIT License
 *
 * @kurkle/color v0.3.2
 * https://github.com/kurkle/color#readme
 * (c) 2023 Jukka Kurkela
 * Released under the MIT License
 *
 * chartjs-adapter-date-fns v3.0.0
 * https://www.chartjs.org
 * (c) 2022 chartjs-adapter-date-fns Contributors
 * Released under the MIT license
 *
 * date-fns v2.30.0
 * https://date-fns.org
 * (c) 2021 Sasha Koss and Lesha Koss
 * Released under the MIT License
 */


(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, global.Chart = factory());
})(this, (function () { 'use strict';

  function _callSuper(t, o, e) {
    return o = _getPrototypeOf$1(o), _possibleConstructorReturn$1(t, _isNativeReflectConstruct$1() ? Reflect.construct(o, e || [], _getPrototypeOf$1(t).constructor) : o.apply(t, e));
  }
  function _isNativeReflectConstruct$1() {
    try {
      var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
    } catch (t) {}
    return (_isNativeReflectConstruct$1 = function () {
      return !!t;
    })();
  }
  function _iterableToArrayLimit(r, l) {
    var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"];
    if (null != t) {
      var e,
        n,
        i,
        u,
        a = [],
        f = !0,
        o = !1;
      try {
        if (i = (t = t.call(r)).next, 0 === l) {
          if (Object(t) !== t) return;
          f = !1;
        } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0);
      } catch (r) {
        o = !0, n = r;
      } finally {
        try {
          if (!f && null != t.return && (u = t.return(), Object(u) !== u)) return;
        } finally {
          if (o) throw n;
        }
      }
      return a;
    }
  }
  function ownKeys(e, r) {
    var t = Object.keys(e);
    if (Object.getOwnPropertySymbols) {
      var o = Object.getOwnPropertySymbols(e);
      r && (o = o.filter(function (r) {
        return Object.getOwnPropertyDescriptor(e, r).enumerable;
      })), t.push.apply(t, o);
    }
    return t;
  }
  function _objectSpread2(e) {
    for (var r = 1; r < arguments.length; r++) {
      var t = null != arguments[r] ? arguments[r] : {};
      r % 2 ? ownKeys(Object(t), !0).forEach(function (r) {
        _defineProperty$1(e, r, t[r]);
      }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) {
        Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r));
      });
    }
    return e;
  }
  function _toPrimitive(t, r) {
    if ("object" != typeof t || !t) return t;
    var e = t[Symbol.toPrimitive];
    if (void 0 !== e) {
      var i = e.call(t, r || "default");
      if ("object" != typeof i) return i;
      throw new TypeError("@@toPrimitive must return a primitive value.");
    }
    return ("string" === r ? String : Number)(t);
  }
  function _toPropertyKey(t) {
    var i = _toPrimitive(t, "string");
    return "symbol" == typeof i ? i : i + "";
  }
  function _typeof$1(o) {
    "@babel/helpers - typeof";

    return _typeof$1 = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
      return typeof o;
    } : function (o) {
      return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
    }, _typeof$1(o);
  }
  function _classCallCheck$1(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  }
  function _defineProperties$1(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor);
    }
  }
  function _createClass$1(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties$1(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties$1(Constructor, staticProps);
    Object.defineProperty(Constructor, "prototype", {
      writable: false
    });
    return Constructor;
  }
  function _defineProperty$1(obj, key, value) {
    key = _toPropertyKey(key);
    if (key in obj) {
      Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
      });
    } else {
      obj[key] = value;
    }
    return obj;
  }
  function _inherits$1(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
      throw new TypeError("Super expression must either be null or a function");
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass,
        writable: true,
        configurable: true
      }
    });
    Object.defineProperty(subClass, "prototype", {
      writable: false
    });
    if (superClass) _setPrototypeOf$1(subClass, superClass);
  }
  function _getPrototypeOf$1(o) {
    _getPrototypeOf$1 = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) {
      return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf$1(o);
  }
  function _setPrototypeOf$1(o, p) {
    _setPrototypeOf$1 = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };
    return _setPrototypeOf$1(o, p);
  }
  function _assertThisInitialized$1(self) {
    if (self === void 0) {
      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }
    return self;
  }
  function _possibleConstructorReturn$1(self, call) {
    if (call && (typeof call === "object" || typeof call === "function")) {
      return call;
    } else if (call !== void 0) {
      throw new TypeError("Derived constructors may only return object or undefined");
    }
    return _assertThisInitialized$1(self);
  }
  function _superPropBase(object, property) {
    while (!Object.prototype.hasOwnProperty.call(object, property)) {
      object = _getPrototypeOf$1(object);
      if (object === null) break;
    }
    return object;
  }
  function _get() {
    if (typeof Reflect !== "undefined" && Reflect.get) {
      _get = Reflect.get.bind();
    } else {
      _get = function _get(target, property, receiver) {
        var base = _superPropBase(target, property);
        if (!base) return;
        var desc = Object.getOwnPropertyDescriptor(base, property);
        if (desc.get) {
          return desc.get.call(arguments.length < 3 ? target : receiver);
        }
        return desc.value;
      };
    }
    return _get.apply(this, arguments);
  }
  function _slicedToArray(arr, i) {
    return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _unsupportedIterableToArray$1(arr, i) || _nonIterableRest();
  }
  function _toConsumableArray(arr) {
    return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray$1(arr) || _nonIterableSpread();
  }
  function _arrayWithoutHoles(arr) {
    if (Array.isArray(arr)) return _arrayLikeToArray$1(arr);
  }
  function _arrayWithHoles(arr) {
    if (Array.isArray(arr)) return arr;
  }
  function _iterableToArray(iter) {
    if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
  }
  function _unsupportedIterableToArray$1(o, minLen) {
    if (!o) return;
    if (typeof o === "string") return _arrayLikeToArray$1(o, minLen);
    var n = Object.prototype.toString.call(o).slice(8, -1);
    if (n === "Object" && o.constructor) n = o.constructor.name;
    if (n === "Map" || n === "Set") return Array.from(o);
    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray$1(o, minLen);
  }
  function _arrayLikeToArray$1(arr, len) {
    if (len == null || len > arr.length) len = arr.length;
    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
    return arr2;
  }
  function _nonIterableSpread() {
    throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
  }
  function _nonIterableRest() {
    throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
  }
  function _createForOfIteratorHelper$1(o, allowArrayLike) {
    var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
    if (!it) {
      if (Array.isArray(o) || (it = _unsupportedIterableToArray$1(o)) || allowArrayLike && o && typeof o.length === "number") {
        if (it) o = it;
        var i = 0;
        var F = function () {};
        return {
          s: F,
          n: function () {
            if (i >= o.length) return {
              done: true
            };
            return {
              done: false,
              value: o[i++]
            };
          },
          e: function (e) {
            throw e;
          },
          f: F
        };
      }
      throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
    }
    var normalCompletion = true,
      didErr = false,
      err;
    return {
      s: function () {
        it = it.call(o);
      },
      n: function () {
        var step = it.next();
        normalCompletion = step.done;
        return step;
      },
      e: function (e) {
        didErr = true;
        err = e;
      },
      f: function () {
        try {
          if (!normalCompletion && it.return != null) it.return();
        } finally {
          if (didErr) throw err;
        }
      }
    };
  }

  /*!
   * @kurkle/color v0.3.2
   * https://github.com/kurkle/color#readme
   * (c) 2023 Jukka Kurkela
   * Released under the MIT License
   */
  function round(v) {
    return v + 0.5 | 0;
  }
  var lim = function lim(v, l, h) {
    return Math.max(Math.min(v, h), l);
  };
  function p2b(v) {
    return lim(round(v * 2.55), 0, 255);
  }
  function n2b(v) {
    return lim(round(v * 255), 0, 255);
  }
  function b2n(v) {
    return lim(round(v / 2.55) / 100, 0, 1);
  }
  function n2p(v) {
    return lim(round(v * 100), 0, 100);
  }
  var map$1 = {
    0: 0,
    1: 1,
    2: 2,
    3: 3,
    4: 4,
    5: 5,
    6: 6,
    7: 7,
    8: 8,
    9: 9,
    A: 10,
    B: 11,
    C: 12,
    D: 13,
    E: 14,
    F: 15,
    a: 10,
    b: 11,
    c: 12,
    d: 13,
    e: 14,
    f: 15
  };
  var hex = _toConsumableArray('0123456789ABCDEF');
  var h1 = function h1(b) {
    return hex[b & 0xF];
  };
  var h2 = function h2(b) {
    return hex[(b & 0xF0) >> 4] + hex[b & 0xF];
  };
  var eq = function eq(b) {
    return (b & 0xF0) >> 4 === (b & 0xF);
  };
  var isShort = function isShort(v) {
    return eq(v.r) && eq(v.g) && eq(v.b) && eq(v.a);
  };
  function hexParse(str) {
    var len = str.length;
    var ret;
    if (str[0] === '#') {
      if (len === 4 || len === 5) {
        ret = {
          r: 255 & map$1[str[1]] * 17,
          g: 255 & map$1[str[2]] * 17,
          b: 255 & map$1[str[3]] * 17,
          a: len === 5 ? map$1[str[4]] * 17 : 255
        };
      } else if (len === 7 || len === 9) {
        ret = {
          r: map$1[str[1]] << 4 | map$1[str[2]],
          g: map$1[str[3]] << 4 | map$1[str[4]],
          b: map$1[str[5]] << 4 | map$1[str[6]],
          a: len === 9 ? map$1[str[7]] << 4 | map$1[str[8]] : 255
        };
      }
    }
    return ret;
  }
  var alpha = function alpha(a, f) {
    return a < 255 ? f(a) : '';
  };
  function _hexString(v) {
    var f = isShort(v) ? h1 : h2;
    return v ? '#' + f(v.r) + f(v.g) + f(v.b) + alpha(v.a, f) : undefined;
  }
  var HUE_RE = /^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/;
  function hsl2rgbn(h, s, l) {
    var a = s * Math.min(l, 1 - l);
    var f = function f(n) {
      var k = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : (n + h / 30) % 12;
      return l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
    };
    return [f(0), f(8), f(4)];
  }
  function hsv2rgbn(h, s, v) {
    var f = function f(n) {
      var k = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : (n + h / 60) % 6;
      return v - v * s * Math.max(Math.min(k, 4 - k, 1), 0);
    };
    return [f(5), f(3), f(1)];
  }
  function hwb2rgbn(h, w, b) {
    var rgb = hsl2rgbn(h, 1, 0.5);
    var i;
    if (w + b > 1) {
      i = 1 / (w + b);
      w *= i;
      b *= i;
    }
    for (i = 0; i < 3; i++) {
      rgb[i] *= 1 - w - b;
      rgb[i] += w;
    }
    return rgb;
  }
  function hueValue(r, g, b, d, max) {
    if (r === max) {
      return (g - b) / d + (g < b ? 6 : 0);
    }
    if (g === max) {
      return (b - r) / d + 2;
    }
    return (r - g) / d + 4;
  }
  function rgb2hsl(v) {
    var range = 255;
    var r = v.r / range;
    var g = v.g / range;
    var b = v.b / range;
    var max = Math.max(r, g, b);
    var min = Math.min(r, g, b);
    var l = (max + min) / 2;
    var h, s, d;
    if (max !== min) {
      d = max - min;
      s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
      h = hueValue(r, g, b, d, max);
      h = h * 60 + 0.5;
    }
    return [h | 0, s || 0, l];
  }
  function calln(f, a, b, c) {
    return (Array.isArray(a) ? f(a[0], a[1], a[2]) : f(a, b, c)).map(n2b);
  }
  function hsl2rgb(h, s, l) {
    return calln(hsl2rgbn, h, s, l);
  }
  function hwb2rgb(h, w, b) {
    return calln(hwb2rgbn, h, w, b);
  }
  function hsv2rgb(h, s, v) {
    return calln(hsv2rgbn, h, s, v);
  }
  function hue(h) {
    return (h % 360 + 360) % 360;
  }
  function hueParse(str) {
    var m = HUE_RE.exec(str);
    var a = 255;
    var v;
    if (!m) {
      return;
    }
    if (m[5] !== v) {
      a = m[6] ? p2b(+m[5]) : n2b(+m[5]);
    }
    var h = hue(+m[2]);
    var p1 = +m[3] / 100;
    var p2 = +m[4] / 100;
    if (m[1] === 'hwb') {
      v = hwb2rgb(h, p1, p2);
    } else if (m[1] === 'hsv') {
      v = hsv2rgb(h, p1, p2);
    } else {
      v = hsl2rgb(h, p1, p2);
    }
    return {
      r: v[0],
      g: v[1],
      b: v[2],
      a: a
    };
  }
  function _rotate(v, deg) {
    var h = rgb2hsl(v);
    h[0] = hue(h[0] + deg);
    h = hsl2rgb(h);
    v.r = h[0];
    v.g = h[1];
    v.b = h[2];
  }
  function _hslString(v) {
    if (!v) {
      return;
    }
    var a = rgb2hsl(v);
    var h = a[0];
    var s = n2p(a[1]);
    var l = n2p(a[2]);
    return v.a < 255 ? "hsla(".concat(h, ", ").concat(s, "%, ").concat(l, "%, ").concat(b2n(v.a), ")") : "hsl(".concat(h, ", ").concat(s, "%, ").concat(l, "%)");
  }
  var map$2 = {
    x: 'dark',
    Z: 'light',
    Y: 're',
    X: 'blu',
    W: 'gr',
    V: 'medium',
    U: 'slate',
    A: 'ee',
    T: 'ol',
    S: 'or',
    B: 'ra',
    C: 'lateg',
    D: 'ights',
    R: 'in',
    Q: 'turquois',
    E: 'hi',
    P: 'ro',
    O: 'al',
    N: 'le',
    M: 'de',
    L: 'yello',
    F: 'en',
    K: 'ch',
    G: 'arks',
    H: 'ea',
    I: 'ightg',
    J: 'wh'
  };
  var names$1 = {
    OiceXe: 'f0f8ff',
    antiquewEte: 'faebd7',
    aqua: 'ffff',
    aquamarRe: '7fffd4',
    azuY: 'f0ffff',
    beige: 'f5f5dc',
    bisque: 'ffe4c4',
    black: '0',
    blanKedOmond: 'ffebcd',
    Xe: 'ff',
    XeviTet: '8a2be2',
    bPwn: 'a52a2a',
    burlywood: 'deb887',
    caMtXe: '5f9ea0',
    KartYuse: '7fff00',
    KocTate: 'd2691e',
    cSO: 'ff7f50',
    cSnflowerXe: '6495ed',
    cSnsilk: 'fff8dc',
    crimson: 'dc143c',
    cyan: 'ffff',
    xXe: '8b',
    xcyan: '8b8b',
    xgTMnPd: 'b8860b',
    xWay: 'a9a9a9',
    xgYF: '6400',
    xgYy: 'a9a9a9',
    xkhaki: 'bdb76b',
    xmagFta: '8b008b',
    xTivegYF: '556b2f',
    xSange: 'ff8c00',
    xScEd: '9932cc',
    xYd: '8b0000',
    xsOmon: 'e9967a',
    xsHgYF: '8fbc8f',
    xUXe: '483d8b',
    xUWay: '2f4f4f',
    xUgYy: '2f4f4f',
    xQe: 'ced1',
    xviTet: '9400d3',
    dAppRk: 'ff1493',
    dApskyXe: 'bfff',
    dimWay: '696969',
    dimgYy: '696969',
    dodgerXe: '1e90ff',
    fiYbrick: 'b22222',
    flSOwEte: 'fffaf0',
    foYstWAn: '228b22',
    fuKsia: 'ff00ff',
    gaRsbSo: 'dcdcdc',
    ghostwEte: 'f8f8ff',
    gTd: 'ffd700',
    gTMnPd: 'daa520',
    Way: '808080',
    gYF: '8000',
    gYFLw: 'adff2f',
    gYy: '808080',
    honeyMw: 'f0fff0',
    hotpRk: 'ff69b4',
    RdianYd: 'cd5c5c',
    Rdigo: '4b0082',
    ivSy: 'fffff0',
    khaki: 'f0e68c',
    lavFMr: 'e6e6fa',
    lavFMrXsh: 'fff0f5',
    lawngYF: '7cfc00',
    NmoncEffon: 'fffacd',
    ZXe: 'add8e6',
    ZcSO: 'f08080',
    Zcyan: 'e0ffff',
    ZgTMnPdLw: 'fafad2',
    ZWay: 'd3d3d3',
    ZgYF: '90ee90',
    ZgYy: 'd3d3d3',
    ZpRk: 'ffb6c1',
    ZsOmon: 'ffa07a',
    ZsHgYF: '20b2aa',
    ZskyXe: '87cefa',
    ZUWay: '778899',
    ZUgYy: '778899',
    ZstAlXe: 'b0c4de',
    ZLw: 'ffffe0',
    lime: 'ff00',
    limegYF: '32cd32',
    lRF: 'faf0e6',
    magFta: 'ff00ff',
    maPon: '800000',
    VaquamarRe: '66cdaa',
    VXe: 'cd',
    VScEd: 'ba55d3',
    VpurpN: '9370db',
    VsHgYF: '3cb371',
    VUXe: '7b68ee',
    VsprRggYF: 'fa9a',
    VQe: '48d1cc',
    VviTetYd: 'c71585',
    midnightXe: '191970',
    mRtcYam: 'f5fffa',
    mistyPse: 'ffe4e1',
    moccasR: 'ffe4b5',
    navajowEte: 'ffdead',
    navy: '80',
    Tdlace: 'fdf5e6',
    Tive: '808000',
    TivedBb: '6b8e23',
    Sange: 'ffa500',
    SangeYd: 'ff4500',
    ScEd: 'da70d6',
    pOegTMnPd: 'eee8aa',
    pOegYF: '98fb98',
    pOeQe: 'afeeee',
    pOeviTetYd: 'db7093',
    papayawEp: 'ffefd5',
    pHKpuff: 'ffdab9',
    peru: 'cd853f',
    pRk: 'ffc0cb',
    plum: 'dda0dd',
    powMrXe: 'b0e0e6',
    purpN: '800080',
    YbeccapurpN: '663399',
    Yd: 'ff0000',
    Psybrown: 'bc8f8f',
    PyOXe: '4169e1',
    saddNbPwn: '8b4513',
    sOmon: 'fa8072',
    sandybPwn: 'f4a460',
    sHgYF: '2e8b57',
    sHshell: 'fff5ee',
    siFna: 'a0522d',
    silver: 'c0c0c0',
    skyXe: '87ceeb',
    UXe: '6a5acd',
    UWay: '708090',
    UgYy: '708090',
    snow: 'fffafa',
    sprRggYF: 'ff7f',
    stAlXe: '4682b4',
    tan: 'd2b48c',
    teO: '8080',
    tEstN: 'd8bfd8',
    tomato: 'ff6347',
    Qe: '40e0d0',
    viTet: 'ee82ee',
    JHt: 'f5deb3',
    wEte: 'ffffff',
    wEtesmoke: 'f5f5f5',
    Lw: 'ffff00',
    LwgYF: '9acd32'
  };
  function unpack() {
    var unpacked = {};
    var keys = Object.keys(names$1);
    var tkeys = Object.keys(map$2);
    var i, j, k, ok, nk;
    for (i = 0; i < keys.length; i++) {
      ok = nk = keys[i];
      for (j = 0; j < tkeys.length; j++) {
        k = tkeys[j];
        nk = nk.replace(k, map$2[k]);
      }
      k = parseInt(names$1[ok], 16);
      unpacked[nk] = [k >> 16 & 0xFF, k >> 8 & 0xFF, k & 0xFF];
    }
    return unpacked;
  }
  var names;
  function nameParse(str) {
    if (!names) {
      names = unpack();
      names.transparent = [0, 0, 0, 0];
    }
    var a = names[str.toLowerCase()];
    return a && {
      r: a[0],
      g: a[1],
      b: a[2],
      a: a.length === 4 ? a[3] : 255
    };
  }
  var RGB_RE = /^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/;
  function rgbParse(str) {
    var m = RGB_RE.exec(str);
    var a = 255;
    var r, g, b;
    if (!m) {
      return;
    }
    if (m[7] !== r) {
      var v = +m[7];
      a = m[8] ? p2b(v) : lim(v * 255, 0, 255);
    }
    r = +m[1];
    g = +m[3];
    b = +m[5];
    r = 255 & (m[2] ? p2b(r) : lim(r, 0, 255));
    g = 255 & (m[4] ? p2b(g) : lim(g, 0, 255));
    b = 255 & (m[6] ? p2b(b) : lim(b, 0, 255));
    return {
      r: r,
      g: g,
      b: b,
      a: a
    };
  }
  function _rgbString(v) {
    return v && (v.a < 255 ? "rgba(".concat(v.r, ", ").concat(v.g, ", ").concat(v.b, ", ").concat(b2n(v.a), ")") : "rgb(".concat(v.r, ", ").concat(v.g, ", ").concat(v.b, ")"));
  }
  var to = function to(v) {
    return v <= 0.0031308 ? v * 12.92 : Math.pow(v, 1.0 / 2.4) * 1.055 - 0.055;
  };
  var from = function from(v) {
    return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
  };
  function _interpolate(rgb1, rgb2, t) {
    var r = from(b2n(rgb1.r));
    var g = from(b2n(rgb1.g));
    var b = from(b2n(rgb1.b));
    return {
      r: n2b(to(r + t * (from(b2n(rgb2.r)) - r))),
      g: n2b(to(g + t * (from(b2n(rgb2.g)) - g))),
      b: n2b(to(b + t * (from(b2n(rgb2.b)) - b))),
      a: rgb1.a + t * (rgb2.a - rgb1.a)
    };
  }
  function modHSL(v, i, ratio) {
    if (v) {
      var tmp = rgb2hsl(v);
      tmp[i] = Math.max(0, Math.min(tmp[i] + tmp[i] * ratio, i === 0 ? 360 : 1));
      tmp = hsl2rgb(tmp);
      v.r = tmp[0];
      v.g = tmp[1];
      v.b = tmp[2];
    }
  }
  function clone$1(v, proto) {
    return v ? Object.assign(proto || {}, v) : v;
  }
  function fromObject(input) {
    var v = {
      r: 0,
      g: 0,
      b: 0,
      a: 255
    };
    if (Array.isArray(input)) {
      if (input.length >= 3) {
        v = {
          r: input[0],
          g: input[1],
          b: input[2],
          a: 255
        };
        if (input.length > 3) {
          v.a = n2b(input[3]);
        }
      }
    } else {
      v = clone$1(input, {
        r: 0,
        g: 0,
        b: 0,
        a: 1
      });
      v.a = n2b(v.a);
    }
    return v;
  }
  function functionParse(str) {
    if (str.charAt(0) === 'r') {
      return rgbParse(str);
    }
    return hueParse(str);
  }
  var Color = /*#__PURE__*/function () {
    function Color(input) {
      _classCallCheck$1(this, Color);
      if (input instanceof Color) {
        return input;
      }
      var type = _typeof$1(input);
      var v;
      if (type === 'object') {
        v = fromObject(input);
      } else if (type === 'string') {
        v = hexParse(input) || nameParse(input) || functionParse(input);
      }
      this._rgb = v;
      this._valid = !!v;
    }
    return _createClass$1(Color, [{
      key: "valid",
      get: function get() {
        return this._valid;
      }
    }, {
      key: "rgb",
      get: function get() {
        var v = clone$1(this._rgb);
        if (v) {
          v.a = b2n(v.a);
        }
        return v;
      },
      set: function set(obj) {
        this._rgb = fromObject(obj);
      }
    }, {
      key: "rgbString",
      value: function rgbString() {
        return this._valid ? _rgbString(this._rgb) : undefined;
      }
    }, {
      key: "hexString",
      value: function hexString() {
        return this._valid ? _hexString(this._rgb) : undefined;
      }
    }, {
      key: "hslString",
      value: function hslString() {
        return this._valid ? _hslString(this._rgb) : undefined;
      }
    }, {
      key: "mix",
      value: function mix(color, weight) {
        if (color) {
          var c1 = this.rgb;
          var c2 = color.rgb;
          var w2;
          var p = weight === w2 ? 0.5 : weight;
          var w = 2 * p - 1;
          var a = c1.a - c2.a;
          var w1 = ((w * a === -1 ? w : (w + a) / (1 + w * a)) + 1) / 2.0;
          w2 = 1 - w1;
          c1.r = 0xFF & w1 * c1.r + w2 * c2.r + 0.5;
          c1.g = 0xFF & w1 * c1.g + w2 * c2.g + 0.5;
          c1.b = 0xFF & w1 * c1.b + w2 * c2.b + 0.5;
          c1.a = p * c1.a + (1 - p) * c2.a;
          this.rgb = c1;
        }
        return this;
      }
    }, {
      key: "interpolate",
      value: function interpolate(color, t) {
        if (color) {
          this._rgb = _interpolate(this._rgb, color._rgb, t);
        }
        return this;
      }
    }, {
      key: "clone",
      value: function clone() {
        return new Color(this.rgb);
      }
    }, {
      key: "alpha",
      value: function alpha(a) {
        this._rgb.a = n2b(a);
        return this;
      }
    }, {
      key: "clearer",
      value: function clearer(ratio) {
        var rgb = this._rgb;
        rgb.a *= 1 - ratio;
        return this;
      }
    }, {
      key: "greyscale",
      value: function greyscale() {
        var rgb = this._rgb;
        var val = round(rgb.r * 0.3 + rgb.g * 0.59 + rgb.b * 0.11);
        rgb.r = rgb.g = rgb.b = val;
        return this;
      }
    }, {
      key: "opaquer",
      value: function opaquer(ratio) {
        var rgb = this._rgb;
        rgb.a *= 1 + ratio;
        return this;
      }
    }, {
      key: "negate",
      value: function negate() {
        var v = this._rgb;
        v.r = 255 - v.r;
        v.g = 255 - v.g;
        v.b = 255 - v.b;
        return this;
      }
    }, {
      key: "lighten",
      value: function lighten(ratio) {
        modHSL(this._rgb, 2, ratio);
        return this;
      }
    }, {
      key: "darken",
      value: function darken(ratio) {
        modHSL(this._rgb, 2, -ratio);
        return this;
      }
    }, {
      key: "saturate",
      value: function saturate(ratio) {
        modHSL(this._rgb, 1, ratio);
        return this;
      }
    }, {
      key: "desaturate",
      value: function desaturate(ratio) {
        modHSL(this._rgb, 1, -ratio);
        return this;
      }
    }, {
      key: "rotate",
      value: function rotate(deg) {
        _rotate(this._rgb, deg);
        return this;
      }
    }]);
  }();

  /**
   * @namespace Chart.helpers
   */ /**
      * An empty function that can be used, for example, for optional callback.
      */
  function noop() {
    /* noop */}
  /**
   * Returns a unique id, sequentially generated from a global variable.
   */
  var uid = function () {
    var id = 0;
    return function () {
      return id++;
    };
  }();
  /**
   * Returns true if `value` is neither null nor undefined, else returns false.
   * @param value - The value to test.
   * @since 2.7.0
   */
  function isNullOrUndef(value) {
    return value === null || typeof value === 'undefined';
  }
  /**
   * Returns true if `value` is an array (including typed arrays), else returns false.
   * @param value - The value to test.
   * @function
   */
  function isArray(value) {
    if (Array.isArray && Array.isArray(value)) {
      return true;
    }
    var type = Object.prototype.toString.call(value);
    if (type.slice(0, 7) === '[object' && type.slice(-6) === 'Array]') {
      return true;
    }
    return false;
  }
  /**
   * Returns true if `value` is an object (excluding null), else returns false.
   * @param value - The value to test.
   * @since 2.7.0
   */
  function isObject(value) {
    return value !== null && Object.prototype.toString.call(value) === '[object Object]';
  }
  /**
   * Returns true if `value` is a finite number, else returns false
   * @param value  - The value to test.
   */
  function isNumberFinite(value) {
    return (typeof value === 'number' || value instanceof Number) && isFinite(+value);
  }
  /**
   * Returns `value` if finite, else returns `defaultValue`.
   * @param value - The value to return if defined.
   * @param defaultValue - The value to return if `value` is not finite.
   */
  function finiteOrDefault(value, defaultValue) {
    return isNumberFinite(value) ? value : defaultValue;
  }
  /**
   * Returns `value` if defined, else returns `defaultValue`.
   * @param value - The value to return if defined.
   * @param defaultValue - The value to return if `value` is undefined.
   */
  function valueOrDefault(value, defaultValue) {
    return typeof value === 'undefined' ? defaultValue : value;
  }
  var toPercentage = function toPercentage(value, dimension) {
    return typeof value === 'string' && value.endsWith('%') ? parseFloat(value) / 100 : +value / dimension;
  };
  var toDimension = function toDimension(value, dimension) {
    return typeof value === 'string' && value.endsWith('%') ? parseFloat(value) / 100 * dimension : +value;
  };
  /**
   * Calls `fn` with the given `args` in the scope defined by `thisArg` and returns the
   * value returned by `fn`. If `fn` is not a function, this method returns undefined.
   * @param fn - The function to call.
   * @param args - The arguments with which `fn` should be called.
   * @param [thisArg] - The value of `this` provided for the call to `fn`.
   */
  function callback(fn, args, thisArg) {
    if (fn && typeof fn.call === 'function') {
      return fn.apply(thisArg, args);
    }
  }
  function each(loopable, fn, thisArg, reverse) {
    var i, len, keys;
    if (isArray(loopable)) {
      len = loopable.length;
      if (reverse) {
        for (i = len - 1; i >= 0; i--) {
          fn.call(thisArg, loopable[i], i);
        }
      } else {
        for (i = 0; i < len; i++) {
          fn.call(thisArg, loopable[i], i);
        }
      }
    } else if (isObject(loopable)) {
      keys = Object.keys(loopable);
      len = keys.length;
      for (i = 0; i < len; i++) {
        fn.call(thisArg, loopable[keys[i]], keys[i]);
      }
    }
  }
  /**
   * Returns true if the `a0` and `a1` arrays have the same content, else returns false.
   * @param a0 - The array to compare
   * @param a1 - The array to compare
   * @private
   */
  function _elementsEqual(a0, a1) {
    var i, ilen, v0, v1;
    if (!a0 || !a1 || a0.length !== a1.length) {
      return false;
    }
    for (i = 0, ilen = a0.length; i < ilen; ++i) {
      v0 = a0[i];
      v1 = a1[i];
      if (v0.datasetIndex !== v1.datasetIndex || v0.index !== v1.index) {
        return false;
      }
    }
    return true;
  }
  /**
   * Returns a deep copy of `source` without keeping references on objects and arrays.
   * @param source - The value to clone.
   */
  function clone(source) {
    if (isArray(source)) {
      return source.map(clone);
    }
    if (isObject(source)) {
      var target = Object.create(null);
      var keys = Object.keys(source);
      var klen = keys.length;
      var k = 0;
      for (; k < klen; ++k) {
        target[keys[k]] = clone(source[keys[k]]);
      }
      return target;
    }
    return source;
  }
  function isValidKey(key) {
    return ['__proto__', 'prototype', 'constructor'].indexOf(key) === -1;
  }
  /**
   * The default merger when Chart.helpers.merge is called without merger option.
   * Note(SB): also used by mergeConfig and mergeScaleConfig as fallback.
   * @private
   */
  function _merger(key, target, source, options) {
    if (!isValidKey(key)) {
      return;
    }
    var tval = target[key];
    var sval = source[key];
    if (isObject(tval) && isObject(sval)) {
      // eslint-disable-next-line @typescript-eslint/no-use-before-define
      merge(tval, sval, options);
    } else {
      target[key] = clone(sval);
    }
  }
  function merge(target, source, options) {
    var sources = isArray(source) ? source : [source];
    var ilen = sources.length;
    if (!isObject(target)) {
      return target;
    }
    options = options || {};
    var merger = options.merger || _merger;
    var current;
    for (var i = 0; i < ilen; ++i) {
      current = sources[i];
      if (!isObject(current)) {
        continue;
      }
      var keys = Object.keys(current);
      for (var k = 0, klen = keys.length; k < klen; ++k) {
        merger(keys[k], target, current, options);
      }
    }
    return target;
  }
  function mergeIf(target, source) {
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    return merge(target, source, {
      merger: _mergerIf
    });
  }
  /**
   * Merges source[key] in target[key] only if target[key] is undefined.
   * @private
   */
  function _mergerIf(key, target, source) {
    if (!isValidKey(key)) {
      return;
    }
    var tval = target[key];
    var sval = source[key];
    if (isObject(tval) && isObject(sval)) {
      mergeIf(tval, sval);
    } else if (!Object.prototype.hasOwnProperty.call(target, key)) {
      target[key] = clone(sval);
    }
  }
  /**
   * @private
   */
  function _deprecated(scope, value, previous, current) {
    if (value !== undefined) {
      console.warn(scope + ': "' + previous + '" is deprecated. Please use "' + current + '" instead');
    }
  }
  // resolveObjectKey resolver cache
  var keyResolvers = {
    // Chart.helpers.core resolveObjectKey should resolve empty key to root object
    '': function _(v) {
      return v;
    },
    // default resolvers
    x: function x(o) {
      return o.x;
    },
    y: function y(o) {
      return o.y;
    }
  };
  /**
   * @private
   */
  function _splitKey(key) {
    var parts = key.split('.');
    var keys = [];
    var tmp = '';
    var _iterator = _createForOfIteratorHelper$1(parts),
      _step;
    try {
      for (_iterator.s(); !(_step = _iterator.n()).done;) {
        var part = _step.value;
        tmp += part;
        if (tmp.endsWith('\\')) {
          tmp = tmp.slice(0, -1) + '.';
        } else {
          keys.push(tmp);
          tmp = '';
        }
      }
    } catch (err) {
      _iterator.e(err);
    } finally {
      _iterator.f();
    }
    return keys;
  }
  function _getKeyResolver(key) {
    var keys = _splitKey(key);
    return function (obj) {
      var _iterator2 = _createForOfIteratorHelper$1(keys),
        _step2;
      try {
        for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
          var k = _step2.value;
          if (k === '') {
            break;
          }
          obj = obj && obj[k];
        }
      } catch (err) {
        _iterator2.e(err);
      } finally {
        _iterator2.f();
      }
      return obj;
    };
  }
  function resolveObjectKey(obj, key) {
    var resolver = keyResolvers[key] || (keyResolvers[key] = _getKeyResolver(key));
    return resolver(obj);
  }
  /**
   * @private
   */
  function _capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
  }
  var defined = function defined(value) {
    return typeof value !== 'undefined';
  };
  var isFunction = function isFunction(value) {
    return typeof value === 'function';
  };
  // Adapted from https://stackoverflow.com/questions/31128855/comparing-ecma6-sets-for-equality#31129384
  var setsEqual = function setsEqual(a, b) {
    if (a.size !== b.size) {
      return false;
    }
    var _iterator3 = _createForOfIteratorHelper$1(a),
      _step3;
    try {
      for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
        var item = _step3.value;
        if (!b.has(item)) {
          return false;
        }
      }
    } catch (err) {
      _iterator3.e(err);
    } finally {
      _iterator3.f();
    }
    return true;
  };
  /**
   * @param e - The event
   * @private
   */
  function _isClickEvent(e) {
    return e.type === 'mouseup' || e.type === 'click' || e.type === 'contextmenu';
  }

  /**
   * @alias Chart.helpers.math
   * @namespace
   */
  var PI = Math.PI;
  var TAU = 2 * PI;
  var PITAU = TAU + PI;
  var INFINITY = Number.POSITIVE_INFINITY;
  var RAD_PER_DEG = PI / 180;
  var HALF_PI = PI / 2;
  var QUARTER_PI = PI / 4;
  var TWO_THIRDS_PI = PI * 2 / 3;
  var log10 = Math.log10;
  var sign = Math.sign;
  function almostEquals(x, y, epsilon) {
    return Math.abs(x - y) < epsilon;
  }
  /**
   * Implementation of the nice number algorithm used in determining where axis labels will go
   */
  function niceNum(range) {
    var roundedRange = Math.round(range);
    range = almostEquals(range, roundedRange, range / 1000) ? roundedRange : range;
    var niceRange = Math.pow(10, Math.floor(log10(range)));
    var fraction = range / niceRange;
    var niceFraction = fraction <= 1 ? 1 : fraction <= 2 ? 2 : fraction <= 5 ? 5 : 10;
    return niceFraction * niceRange;
  }
  /**
   * Returns an array of factors sorted from 1 to sqrt(value)
   * @private
   */
  function _factorize(value) {
    var result = [];
    var sqrt = Math.sqrt(value);
    var i;
    for (i = 1; i < sqrt; i++) {
      if (value % i === 0) {
        result.push(i);
        result.push(value / i);
      }
    }
    if (sqrt === (sqrt | 0)) {
      result.push(sqrt);
    }
    result.sort(function (a, b) {
      return a - b;
    }).pop();
    return result;
  }
  function isNumber(n) {
    return !isNaN(parseFloat(n)) && isFinite(n);
  }
  function almostWhole(x, epsilon) {
    var rounded = Math.round(x);
    return rounded - epsilon <= x && rounded + epsilon >= x;
  }
  /**
   * @private
   */
  function _setMinAndMaxByKey(array, target, property) {
    var i, ilen, value;
    for (i = 0, ilen = array.length; i < ilen; i++) {
      value = array[i][property];
      if (!isNaN(value)) {
        target.min = Math.min(target.min, value);
        target.max = Math.max(target.max, value);
      }
    }
  }
  function toRadians(degrees) {
    return degrees * (PI / 180);
  }
  function toDegrees(radians) {
    return radians * (180 / PI);
  }
  /**
   * Returns the number of decimal places
   * i.e. the number of digits after the decimal point, of the value of this Number.
   * @param x - A number.
   * @returns The number of decimal places.
   * @private
   */
  function _decimalPlaces(x) {
    if (!isNumberFinite(x)) {
      return;
    }
    var e = 1;
    var p = 0;
    while (Math.round(x * e) / e !== x) {
      e *= 10;
      p++;
    }
    return p;
  }
  // Gets the angle from vertical upright to the point about a centre.
  function getAngleFromPoint(centrePoint, anglePoint) {
    var distanceFromXCenter = anglePoint.x - centrePoint.x;
    var distanceFromYCenter = anglePoint.y - centrePoint.y;
    var radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
    var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter);
    if (angle < -0.5 * PI) {
      angle += TAU; // make sure the returned angle is in the range of (-PI/2, 3PI/2]
    }
    return {
      angle: angle,
      distance: radialDistanceFromCenter
    };
  }
  function distanceBetweenPoints(pt1, pt2) {
    return Math.sqrt(Math.pow(pt2.x - pt1.x, 2) + Math.pow(pt2.y - pt1.y, 2));
  }
  /**
   * Shortest distance between angles, in either direction.
   * @private
   */
  function _angleDiff(a, b) {
    return (a - b + PITAU) % TAU - PI;
  }
  /**
   * Normalize angle to be between 0 and 2*PI
   * @private
   */
  function _normalizeAngle(a) {
    return (a % TAU + TAU) % TAU;
  }
  /**
   * @private
   */
  function _angleBetween(angle, start, end, sameAngleIsFullCircle) {
    var a = _normalizeAngle(angle);
    var s = _normalizeAngle(start);
    var e = _normalizeAngle(end);
    var angleToStart = _normalizeAngle(s - a);
    var angleToEnd = _normalizeAngle(e - a);
    var startToAngle = _normalizeAngle(a - s);
    var endToAngle = _normalizeAngle(a - e);
    return a === s || a === e || sameAngleIsFullCircle && s === e || angleToStart > angleToEnd && startToAngle < endToAngle;
  }
  /**
   * Limit `value` between `min` and `max`
   * @param value
   * @param min
   * @param max
   * @private
   */
  function _limitValue(value, min, max) {
    return Math.max(min, Math.min(max, value));
  }
  /**
   * @param {number} value
   * @private
   */
  function _int16Range(value) {
    return _limitValue(value, -32768, 32767);
  }
  /**
   * @param value
   * @param start
   * @param end
   * @param [epsilon]
   * @private
   */
  function _isBetween(value, start, end) {
    var epsilon = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 1e-6;
    return value >= Math.min(start, end) - epsilon && value <= Math.max(start, end) + epsilon;
  }
  function _lookup(table, value, cmp) {
    cmp = cmp || function (index) {
      return table[index] < value;
    };
    var hi = table.length - 1;
    var lo = 0;
    var mid;
    while (hi - lo > 1) {
      mid = lo + hi >> 1;
      if (cmp(mid)) {
        lo = mid;
      } else {
        hi = mid;
      }
    }
    return {
      lo: lo,
      hi: hi
    };
  }
  /**
   * Binary search
   * @param table - the table search. must be sorted!
   * @param key - property name for the value in each entry
   * @param value - value to find
   * @param last - lookup last index
   * @private
   */
  var _lookupByKey = function _lookupByKey(table, key, value, last) {
    return _lookup(table, value, last ? function (index) {
      var ti = table[index][key];
      return ti < value || ti === value && table[index + 1][key] === value;
    } : function (index) {
      return table[index][key] < value;
    });
  };
  /**
   * Reverse binary search
   * @param table - the table search. must be sorted!
   * @param key - property name for the value in each entry
   * @param value - value to find
   * @private
   */
  var _rlookupByKey = function _rlookupByKey(table, key, value) {
    return _lookup(table, value, function (index) {
      return table[index][key] >= value;
    });
  };
  /**
   * Return subset of `values` between `min` and `max` inclusive.
   * Values are assumed to be in sorted order.
   * @param values - sorted array of values
   * @param min - min value
   * @param max - max value
   */
  function _filterBetween(values, min, max) {
    var start = 0;
    var end = values.length;
    while (start < end && values[start] < min) {
      start++;
    }
    while (end > start && values[end - 1] > max) {
      end--;
    }
    return start > 0 || end < values.length ? values.slice(start, end) : values;
  }
  var arrayEvents = ['push', 'pop', 'shift', 'splice', 'unshift'];
  function listenArrayEvents(array, listener) {
    if (array._chartjs) {
      array._chartjs.listeners.push(listener);
      return;
    }
    Object.defineProperty(array, '_chartjs', {
      configurable: true,
      enumerable: false,
      value: {
        listeners: [listener]
      }
    });
    arrayEvents.forEach(function (key) {
      var method = '_onData' + _capitalize(key);
      var base = array[key];
      Object.defineProperty(array, key, {
        configurable: true,
        enumerable: false,
        value: function value() {
          for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
            args[_key] = arguments[_key];
          }
          var res = base.apply(this, args);
          array._chartjs.listeners.forEach(function (object) {
            if (typeof object[method] === 'function') {
              object[method].apply(object, args);
            }
          });
          return res;
        }
      });
    });
  }
  function unlistenArrayEvents(array, listener) {
    var stub = array._chartjs;
    if (!stub) {
      return;
    }
    var listeners = stub.listeners;
    var index = listeners.indexOf(listener);
    if (index !== -1) {
      listeners.splice(index, 1);
    }
    if (listeners.length > 0) {
      return;
    }
    arrayEvents.forEach(function (key) {
      delete array[key];
    });
    delete array._chartjs;
  }
  /**
   * @param items
   */
  function _arrayUnique(items) {
    var set = new Set(items);
    if (set.size === items.length) {
      return items;
    }
    return Array.from(set);
  }
  function fontString(pixelSize, fontStyle, fontFamily) {
    return fontStyle + ' ' + pixelSize + 'px ' + fontFamily;
  }
  /**
  * Request animation polyfill
  */
  var requestAnimFrame = function () {
    if (typeof window === 'undefined') {
      return function (callback) {
        return callback();
      };
    }
    return window.requestAnimationFrame;
  }();
  /**
   * Throttles calling `fn` once per animation frame
   * Latest arguments are used on the actual call
   */
  function throttled(fn, thisArg) {
    var argsToUse = [];
    var ticking = false;
    return function () {
      for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
        args[_key2] = arguments[_key2];
      }
      // Save the args for use later
      argsToUse = args;
      if (!ticking) {
        ticking = true;
        requestAnimFrame.call(window, function () {
          ticking = false;
          fn.apply(thisArg, argsToUse);
        });
      }
    };
  }
  /**
   * Debounces calling `fn` for `delay` ms
   */
  function debounce(fn, delay) {
    var timeout;
    return function () {
      for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
        args[_key3] = arguments[_key3];
      }
      if (delay) {
        clearTimeout(timeout);
        timeout = setTimeout(fn, delay, args);
      } else {
        fn.apply(this, args);
      }
      return delay;
    };
  }
  /**
   * Converts 'start' to 'left', 'end' to 'right' and others to 'center'
   * @private
   */
  var _toLeftRightCenter = function _toLeftRightCenter(align) {
    return align === 'start' ? 'left' : align === 'end' ? 'right' : 'center';
  };
  /**
   * Returns `start`, `end` or `(start + end) / 2` depending on `align`. Defaults to `center`
   * @private
   */
  var _alignStartEnd = function _alignStartEnd(align, start, end) {
    return align === 'start' ? start : align === 'end' ? end : (start + end) / 2;
  };
  /**
   * Returns `left`, `right` or `(left + right) / 2` depending on `align`. Defaults to `left`
   * @private
   */
  var _textX = function _textX(align, left, right, rtl) {
    var check = rtl ? 'left' : 'right';
    return align === check ? right : align === 'center' ? (left + right) / 2 : left;
  };
  /**
   * Return start and count of visible points.
   * @private
   */
  function _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled) {
    var pointCount = points.length;
    var start = 0;
    var count = pointCount;
    if (meta._sorted) {
      var iScale = meta.iScale,
        _parsed = meta._parsed;
      var axis = iScale.axis;
      var _iScale$getUserBounds = iScale.getUserBounds(),
        min = _iScale$getUserBounds.min,
        max = _iScale$getUserBounds.max,
        minDefined = _iScale$getUserBounds.minDefined,
        maxDefined = _iScale$getUserBounds.maxDefined;
      if (minDefined) {
        start = _limitValue(Math.min(
        // @ts-expect-error Need to type _parsed
        _lookupByKey(_parsed, axis, min).lo,
        // @ts-expect-error Need to fix types on _lookupByKey
        animationsDisabled ? pointCount : _lookupByKey(points, axis, iScale.getPixelForValue(min)).lo), 0, pointCount - 1);
      }
      if (maxDefined) {
        count = _limitValue(Math.max(
        // @ts-expect-error Need to type _parsed
        _lookupByKey(_parsed, iScale.axis, max, true).hi + 1,
        // @ts-expect-error Need to fix types on _lookupByKey
        animationsDisabled ? 0 : _lookupByKey(points, axis, iScale.getPixelForValue(max), true).hi + 1), start, pointCount) - start;
      } else {
        count = pointCount - start;
      }
    }
    return {
      start: start,
      count: count
    };
  }
  /**
   * Checks if the scale ranges have changed.
   * @param {object} meta - dataset meta.
   * @returns {boolean}
   * @private
   */
  function _scaleRangesChanged(meta) {
    var xScale = meta.xScale,
      yScale = meta.yScale,
      _scaleRanges = meta._scaleRanges;
    var newRanges = {
      xmin: xScale.min,
      xmax: xScale.max,
      ymin: yScale.min,
      ymax: yScale.max
    };
    if (!_scaleRanges) {
      meta._scaleRanges = newRanges;
      return true;
    }
    var changed = _scaleRanges.xmin !== xScale.min || _scaleRanges.xmax !== xScale.max || _scaleRanges.ymin !== yScale.min || _scaleRanges.ymax !== yScale.max;
    Object.assign(_scaleRanges, newRanges);
    return changed;
  }
  var atEdge = function atEdge(t) {
    return t === 0 || t === 1;
  };
  var elasticIn = function elasticIn(t, s, p) {
    return -(Math.pow(2, 10 * (t -= 1)) * Math.sin((t - s) * TAU / p));
  };
  var elasticOut = function elasticOut(t, s, p) {
    return Math.pow(2, -10 * t) * Math.sin((t - s) * TAU / p) + 1;
  };
  /**
   * Easing functions adapted from Robert Penner's easing equations.
   * @namespace Chart.helpers.easing.effects
   * @see http://www.robertpenner.com/easing/
   */
  var effects = {
    linear: function linear(t) {
      return t;
    },
    easeInQuad: function easeInQuad(t) {
      return t * t;
    },
    easeOutQuad: function easeOutQuad(t) {
      return -t * (t - 2);
    },
    easeInOutQuad: function easeInOutQuad(t) {
      return (t /= 0.5) < 1 ? 0.5 * t * t : -0.5 * (--t * (t - 2) - 1);
    },
    easeInCubic: function easeInCubic(t) {
      return t * t * t;
    },
    easeOutCubic: function easeOutCubic(t) {
      return (t -= 1) * t * t + 1;
    },
    easeInOutCubic: function easeInOutCubic(t) {
      return (t /= 0.5) < 1 ? 0.5 * t * t * t : 0.5 * ((t -= 2) * t * t + 2);
    },
    easeInQuart: function easeInQuart(t) {
      return t * t * t * t;
    },
    easeOutQuart: function easeOutQuart(t) {
      return -((t -= 1) * t * t * t - 1);
    },
    easeInOutQuart: function easeInOutQuart(t) {
      return (t /= 0.5) < 1 ? 0.5 * t * t * t * t : -0.5 * ((t -= 2) * t * t * t - 2);
    },
    easeInQuint: function easeInQuint(t) {
      return t * t * t * t * t;
    },
    easeOutQuint: function easeOutQuint(t) {
      return (t -= 1) * t * t * t * t + 1;
    },
    easeInOutQuint: function easeInOutQuint(t) {
      return (t /= 0.5) < 1 ? 0.5 * t * t * t * t * t : 0.5 * ((t -= 2) * t * t * t * t + 2);
    },
    easeInSine: function easeInSine(t) {
      return -Math.cos(t * HALF_PI) + 1;
    },
    easeOutSine: function easeOutSine(t) {
      return Math.sin(t * HALF_PI);
    },
    easeInOutSine: function easeInOutSine(t) {
      return -0.5 * (Math.cos(PI * t) - 1);
    },
    easeInExpo: function easeInExpo(t) {
      return t === 0 ? 0 : Math.pow(2, 10 * (t - 1));
    },
    easeOutExpo: function easeOutExpo(t) {
      return t === 1 ? 1 : -Math.pow(2, -10 * t) + 1;
    },
    easeInOutExpo: function easeInOutExpo(t) {
      return atEdge(t) ? t : t < 0.5 ? 0.5 * Math.pow(2, 10 * (t * 2 - 1)) : 0.5 * (-Math.pow(2, -10 * (t * 2 - 1)) + 2);
    },
    easeInCirc: function easeInCirc(t) {
      return t >= 1 ? t : -(Math.sqrt(1 - t * t) - 1);
    },
    easeOutCirc: function easeOutCirc(t) {
      return Math.sqrt(1 - (t -= 1) * t);
    },
    easeInOutCirc: function easeInOutCirc(t) {
      return (t /= 0.5) < 1 ? -0.5 * (Math.sqrt(1 - t * t) - 1) : 0.5 * (Math.sqrt(1 - (t -= 2) * t) + 1);
    },
    easeInElastic: function easeInElastic(t) {
      return atEdge(t) ? t : elasticIn(t, 0.075, 0.3);
    },
    easeOutElastic: function easeOutElastic(t) {
      return atEdge(t) ? t : elasticOut(t, 0.075, 0.3);
    },
    easeInOutElastic: function easeInOutElastic(t) {
      var s = 0.1125;
      var p = 0.45;
      return atEdge(t) ? t : t < 0.5 ? 0.5 * elasticIn(t * 2, s, p) : 0.5 + 0.5 * elasticOut(t * 2 - 1, s, p);
    },
    easeInBack: function easeInBack(t) {
      var s = 1.70158;
      return t * t * ((s + 1) * t - s);
    },
    easeOutBack: function easeOutBack(t) {
      var s = 1.70158;
      return (t -= 1) * t * ((s + 1) * t + s) + 1;
    },
    easeInOutBack: function easeInOutBack(t) {
      var s = 1.70158;
      if ((t /= 0.5) < 1) {
        return 0.5 * (t * t * (((s *= 1.525) + 1) * t - s));
      }
      return 0.5 * ((t -= 2) * t * (((s *= 1.525) + 1) * t + s) + 2);
    },
    easeInBounce: function easeInBounce(t) {
      return 1 - effects.easeOutBounce(1 - t);
    },
    easeOutBounce: function easeOutBounce(t) {
      var m = 7.5625;
      var d = 2.75;
      if (t < 1 / d) {
        return m * t * t;
      }
      if (t < 2 / d) {
        return m * (t -= 1.5 / d) * t + 0.75;
      }
      if (t < 2.5 / d) {
        return m * (t -= 2.25 / d) * t + 0.9375;
      }
      return m * (t -= 2.625 / d) * t + 0.984375;
    },
    easeInOutBounce: function easeInOutBounce(t) {
      return t < 0.5 ? effects.easeInBounce(t * 2) * 0.5 : effects.easeOutBounce(t * 2 - 1) * 0.5 + 0.5;
    }
  };
  function isPatternOrGradient(value) {
    if (value && _typeof$1(value) === 'object') {
      var type = value.toString();
      return type === '[object CanvasPattern]' || type === '[object CanvasGradient]';
    }
    return false;
  }
  function color(value) {
    return isPatternOrGradient(value) ? value : new Color(value);
  }
  function getHoverColor(value) {
    return isPatternOrGradient(value) ? value : new Color(value).saturate(0.5).darken(0.1).hexString();
  }
  var numbers = ['x', 'y', 'borderWidth', 'radius', 'tension'];
  var colors = ['color', 'borderColor', 'backgroundColor'];
  function applyAnimationsDefaults(defaults) {
    defaults.set('animation', {
      delay: undefined,
      duration: 1000,
      easing: 'easeOutQuart',
      fn: undefined,
      from: undefined,
      loop: undefined,
      to: undefined,
      type: undefined
    });
    defaults.describe('animation', {
      _fallback: false,
      _indexable: false,
      _scriptable: function _scriptable(name) {
        return name !== 'onProgress' && name !== 'onComplete' && name !== 'fn';
      }
    });
    defaults.set('animations', {
      colors: {
        type: 'color',
        properties: colors
      },
      numbers: {
        type: 'number',
        properties: numbers
      }
    });
    defaults.describe('animations', {
      _fallback: 'animation'
    });
    defaults.set('transitions', {
      active: {
        animation: {
          duration: 400
        }
      },
      resize: {
        animation: {
          duration: 0
        }
      },
      show: {
        animations: {
          colors: {
            from: 'transparent'
          },
          visible: {
            type: 'boolean',
            duration: 0
          }
        }
      },
      hide: {
        animations: {
          colors: {
            to: 'transparent'
          },
          visible: {
            type: 'boolean',
            easing: 'linear',
            fn: function fn(v) {
              return v | 0;
            }
          }
        }
      }
    });
  }
  function applyLayoutsDefaults(defaults) {
    defaults.set('layout', {
      autoPadding: true,
      padding: {
        top: 0,
        right: 0,
        bottom: 0,
        left: 0
      }
    });
  }
  var intlCache = new Map();
  function getNumberFormat(locale, options) {
    options = options || {};
    var cacheKey = locale + JSON.stringify(options);
    var formatter = intlCache.get(cacheKey);
    if (!formatter) {
      formatter = new Intl.NumberFormat(locale, options);
      intlCache.set(cacheKey, formatter);
    }
    return formatter;
  }
  function formatNumber(num, locale, options) {
    return getNumberFormat(locale, options).format(num);
  }
  var formatters$4 = {
    values: function values(value) {
      return isArray(value) ? value : '' + value;
    },
    numeric: function numeric(tickValue, index, ticks) {
      if (tickValue === 0) {
        return '0';
      }
      var locale = this.chart.options.locale;
      var notation;
      var delta = tickValue;
      if (ticks.length > 1) {
        var maxTick = Math.max(Math.abs(ticks[0].value), Math.abs(ticks[ticks.length - 1].value));
        if (maxTick < 1e-4 || maxTick > 1e+15) {
          notation = 'scientific';
        }
        delta = calculateDelta(tickValue, ticks);
      }
      var logDelta = log10(Math.abs(delta));
      var numDecimal = isNaN(logDelta) ? 1 : Math.max(Math.min(-1 * Math.floor(logDelta), 20), 0);
      var options = {
        notation: notation,
        minimumFractionDigits: numDecimal,
        maximumFractionDigits: numDecimal
      };
      Object.assign(options, this.options.ticks.format);
      return formatNumber(tickValue, locale, options);
    },
    logarithmic: function logarithmic(tickValue, index, ticks) {
      if (tickValue === 0) {
        return '0';
      }
      var remain = ticks[index].significand || tickValue / Math.pow(10, Math.floor(log10(tickValue)));
      if ([1, 2, 3, 5, 10, 15].includes(remain) || index > 0.8 * ticks.length) {
        return formatters$4.numeric.call(this, tickValue, index, ticks);
      }
      return '';
    }
  };
  function calculateDelta(tickValue, ticks) {
    var delta = ticks.length > 3 ? ticks[2].value - ticks[1].value : ticks[1].value - ticks[0].value;
    if (Math.abs(delta) >= 1 && tickValue !== Math.floor(tickValue)) {
      delta = tickValue - Math.floor(tickValue);
    }
    return delta;
  }
  var Ticks = {
    formatters: formatters$4
  };
  function applyScaleDefaults(defaults) {
    defaults.set('scale', {
      display: true,
      offset: false,
      reverse: false,
      beginAtZero: false,
      bounds: 'ticks',
      clip: true,
      grace: 0,
      grid: {
        display: true,
        lineWidth: 1,
        drawOnChartArea: true,
        drawTicks: true,
        tickLength: 8,
        tickWidth: function tickWidth(_ctx, options) {
          return options.lineWidth;
        },
        tickColor: function tickColor(_ctx, options) {
          return options.color;
        },
        offset: false
      },
      border: {
        display: true,
        dash: [],
        dashOffset: 0.0,
        width: 1
      },
      title: {
        display: false,
        text: '',
        padding: {
          top: 4,
          bottom: 4
        }
      },
      ticks: {
        minRotation: 0,
        maxRotation: 50,
        mirror: false,
        textStrokeWidth: 0,
        textStrokeColor: '',
        padding: 3,
        display: true,
        autoSkip: true,
        autoSkipPadding: 3,
        labelOffset: 0,
        callback: Ticks.formatters.values,
        minor: {},
        major: {},
        align: 'center',
        crossAlign: 'near',
        showLabelBackdrop: false,
        backdropColor: 'rgba(255, 255, 255, 0.75)',
        backdropPadding: 2
      }
    });
    defaults.route('scale.ticks', 'color', '', 'color');
    defaults.route('scale.grid', 'color', '', 'borderColor');
    defaults.route('scale.border', 'color', '', 'borderColor');
    defaults.route('scale.title', 'color', '', 'color');
    defaults.describe('scale', {
      _fallback: false,
      _scriptable: function _scriptable(name) {
        return !name.startsWith('before') && !name.startsWith('after') && name !== 'callback' && name !== 'parser';
      },
      _indexable: function _indexable(name) {
        return name !== 'borderDash' && name !== 'tickBorderDash' && name !== 'dash';
      }
    });
    defaults.describe('scales', {
      _fallback: 'scale'
    });
    defaults.describe('scale.ticks', {
      _scriptable: function _scriptable(name) {
        return name !== 'backdropPadding' && name !== 'callback';
      },
      _indexable: function _indexable(name) {
        return name !== 'backdropPadding';
      }
    });
  }
  var overrides = Object.create(null);
  var descriptors = Object.create(null);
  function getScope$1(node, key) {
    if (!key) {
      return node;
    }
    var keys = key.split('.');
    for (var i = 0, n = keys.length; i < n; ++i) {
      var k = keys[i];
      node = node[k] || (node[k] = Object.create(null));
    }
    return node;
  }
  function _set(root, scope, values) {
    if (typeof scope === 'string') {
      return merge(getScope$1(root, scope), values);
    }
    return merge(getScope$1(root, ''), scope);
  }
  var Defaults = /*#__PURE__*/function () {
    function Defaults(_descriptors, _appliers) {
      _classCallCheck$1(this, Defaults);
      this.animation = undefined;
      this.backgroundColor = 'rgba(0,0,0,0.1)';
      this.borderColor = 'rgba(0,0,0,0.1)';
      this.color = '#666';
      this.datasets = {};
      this.devicePixelRatio = function (context) {
        return context.chart.platform.getDevicePixelRatio();
      };
      this.elements = {};
      this.events = ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'];
      this.font = {
        family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
        size: 12,
        style: 'normal',
        lineHeight: 1.2,
        weight: null
      };
      this.hover = {};
      this.hoverBackgroundColor = function (ctx, options) {
        return getHoverColor(options.backgroundColor);
      };
      this.hoverBorderColor = function (ctx, options) {
        return getHoverColor(options.borderColor);
      };
      this.hoverColor = function (ctx, options) {
        return getHoverColor(options.color);
      };
      this.indexAxis = 'x';
      this.interaction = {
        mode: 'nearest',
        intersect: true,
        includeInvisible: false
      };
      this.maintainAspectRatio = true;
      this.onHover = null;
      this.onClick = null;
      this.parsing = true;
      this.plugins = {};
      this.responsive = true;
      this.scale = undefined;
      this.scales = {};
      this.showLine = true;
      this.drawActiveElementsOnTop = true;
      this.describe(_descriptors);
      this.apply(_appliers);
    }
    return _createClass$1(Defaults, [{
      key: "set",
      value: function set(scope, values) {
        return _set(this, scope, values);
      }
    }, {
      key: "get",
      value: function get(scope) {
        return getScope$1(this, scope);
      }
    }, {
      key: "describe",
      value: function describe(scope, values) {
        return _set(descriptors, scope, values);
      }
    }, {
      key: "override",
      value: function override(scope, values) {
        return _set(overrides, scope, values);
      }
    }, {
      key: "route",
      value: function route(scope, name, targetScope, targetName) {
        var scopeObject = getScope$1(this, scope);
        var targetScopeObject = getScope$1(this, targetScope);
        var privateName = '_' + name;
        Object.defineProperties(scopeObject, _defineProperty$1(_defineProperty$1({}, privateName, {
          value: scopeObject[name],
          writable: true
        }), name, {
          enumerable: true,
          get: function get() {
            var local = this[privateName];
            var target = targetScopeObject[targetName];
            if (isObject(local)) {
              return Object.assign({}, target, local);
            }
            return valueOrDefault(local, target);
          },
          set: function set(value) {
            this[privateName] = value;
          }
        }));
      }
    }, {
      key: "apply",
      value: function apply(appliers) {
        var _this = this;
        appliers.forEach(function (apply) {
          return apply(_this);
        });
      }
    }]);
  }();
  var defaults = /* #__PURE__ */new Defaults({
    _scriptable: function _scriptable(name) {
      return !name.startsWith('on');
    },
    _indexable: function _indexable(name) {
      return name !== 'events';
    },
    hover: {
      _fallback: 'interaction'
    },
    interaction: {
      _scriptable: false,
      _indexable: false
    }
  }, [applyAnimationsDefaults, applyLayoutsDefaults, applyScaleDefaults]);

  /**
   * Converts the given font object into a CSS font string.
   * @param font - A font object.
   * @return The CSS font string. See https://developer.mozilla.org/en-US/docs/Web/CSS/font
   * @private
   */
  function toFontString(font) {
    if (!font || isNullOrUndef(font.size) || isNullOrUndef(font.family)) {
      return null;
    }
    return (font.style ? font.style + ' ' : '') + (font.weight ? font.weight + ' ' : '') + font.size + 'px ' + font.family;
  }
  /**
   * @private
   */
  function _measureText(ctx, data, gc, longest, string) {
    var textWidth = data[string];
    if (!textWidth) {
      textWidth = data[string] = ctx.measureText(string).width;
      gc.push(string);
    }
    if (textWidth > longest) {
      longest = textWidth;
    }
    return longest;
  }
  /**
   * @private
   */ // eslint-disable-next-line complexity
  function _longestText(ctx, font, arrayOfThings, cache) {
    cache = cache || {};
    var data = cache.data = cache.data || {};
    var gc = cache.garbageCollect = cache.garbageCollect || [];
    if (cache.font !== font) {
      data = cache.data = {};
      gc = cache.garbageCollect = [];
      cache.font = font;
    }
    ctx.save();
    ctx.font = font;
    var longest = 0;
    var ilen = arrayOfThings.length;
    var i, j, jlen, thing, nestedThing;
    for (i = 0; i < ilen; i++) {
      thing = arrayOfThings[i];
      // Undefined strings and arrays should not be measured
      if (thing !== undefined && thing !== null && !isArray(thing)) {
        longest = _measureText(ctx, data, gc, longest, thing);
      } else if (isArray(thing)) {
        // if it is an array lets measure each element
        // to do maybe simplify this function a bit so we can do this more recursively?
        for (j = 0, jlen = thing.length; j < jlen; j++) {
          nestedThing = thing[j];
          // Undefined strings and arrays should not be measured
          if (nestedThing !== undefined && nestedThing !== null && !isArray(nestedThing)) {
            longest = _measureText(ctx, data, gc, longest, nestedThing);
          }
        }
      }
    }
    ctx.restore();
    var gcLen = gc.length / 2;
    if (gcLen > arrayOfThings.length) {
      for (i = 0; i < gcLen; i++) {
        delete data[gc[i]];
      }
      gc.splice(0, gcLen);
    }
    return longest;
  }
  /**
   * Returns the aligned pixel value to avoid anti-aliasing blur
   * @param chart - The chart instance.
   * @param pixel - A pixel value.
   * @param width - The width of the element.
   * @returns The aligned pixel value.
   * @private
   */
  function _alignPixel(chart, pixel, width) {
    var devicePixelRatio = chart.currentDevicePixelRatio;
    var halfWidth = width !== 0 ? Math.max(width / 2, 0.5) : 0;
    return Math.round((pixel - halfWidth) * devicePixelRatio) / devicePixelRatio + halfWidth;
  }
  /**
   * Clears the entire canvas.
   */
  function clearCanvas(canvas, ctx) {
    if (!ctx && !canvas) {
      return;
    }
    ctx = ctx || canvas.getContext('2d');
    ctx.save();
    // canvas.width and canvas.height do not consider the canvas transform,
    // while clearRect does
    ctx.resetTransform();
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    ctx.restore();
  }
  function drawPoint(ctx, options, x, y) {
    // eslint-disable-next-line @typescript-eslint/no-use-before-define
    drawPointLegend(ctx, options, x, y, null);
  }
  // eslint-disable-next-line complexity
  function drawPointLegend(ctx, options, x, y, w) {
    var type, xOffset, yOffset, size, cornerRadius, width, xOffsetW, yOffsetW;
    var style = options.pointStyle;
    var rotation = options.rotation;
    var radius = options.radius;
    var rad = (rotation || 0) * RAD_PER_DEG;
    if (style && _typeof$1(style) === 'object') {
      type = style.toString();
      if (type === '[object HTMLImageElement]' || type === '[object HTMLCanvasElement]') {
        ctx.save();
        ctx.translate(x, y);
        ctx.rotate(rad);
        ctx.drawImage(style, -style.width / 2, -style.height / 2, style.width, style.height);
        ctx.restore();
        return;
      }
    }
    if (isNaN(radius) || radius <= 0) {
      return;
    }
    ctx.beginPath();
    switch (style) {
      // Default includes circle
      default:
        if (w) {
          ctx.ellipse(x, y, w / 2, radius, 0, 0, TAU);
        } else {
          ctx.arc(x, y, radius, 0, TAU);
        }
        ctx.closePath();
        break;
      case 'triangle':
        width = w ? w / 2 : radius;
        ctx.moveTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
        rad += TWO_THIRDS_PI;
        ctx.lineTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
        rad += TWO_THIRDS_PI;
        ctx.lineTo(x + Math.sin(rad) * width, y - Math.cos(rad) * radius);
        ctx.closePath();
        break;
      case 'rectRounded':
        // NOTE: the rounded rect implementation changed to use `arc` instead of
        // `quadraticCurveTo` since it generates better results when rect is
        // almost a circle. 0.516 (instead of 0.5) produces results with visually
        // closer proportion to the previous impl and it is inscribed in the
        // circle with `radius`. For more details, see the following PRs:
        // https://github.com/chartjs/Chart.js/issues/5597
        // https://github.com/chartjs/Chart.js/issues/5858
        cornerRadius = radius * 0.516;
        size = radius - cornerRadius;
        xOffset = Math.cos(rad + QUARTER_PI) * size;
        xOffsetW = Math.cos(rad + QUARTER_PI) * (w ? w / 2 - cornerRadius : size);
        yOffset = Math.sin(rad + QUARTER_PI) * size;
        yOffsetW = Math.sin(rad + QUARTER_PI) * (w ? w / 2 - cornerRadius : size);
        ctx.arc(x - xOffsetW, y - yOffset, cornerRadius, rad - PI, rad - HALF_PI);
        ctx.arc(x + yOffsetW, y - xOffset, cornerRadius, rad - HALF_PI, rad);
        ctx.arc(x + xOffsetW, y + yOffset, cornerRadius, rad, rad + HALF_PI);
        ctx.arc(x - yOffsetW, y + xOffset, cornerRadius, rad + HALF_PI, rad + PI);
        ctx.closePath();
        break;
      case 'rect':
        if (!rotation) {
          size = Math.SQRT1_2 * radius;
          width = w ? w / 2 : size;
          ctx.rect(x - width, y - size, 2 * width, 2 * size);
          break;
        }
        rad += QUARTER_PI;
      /* falls through */
      case 'rectRot':
        xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
        xOffset = Math.cos(rad) * radius;
        yOffset = Math.sin(rad) * radius;
        yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
        ctx.moveTo(x - xOffsetW, y - yOffset);
        ctx.lineTo(x + yOffsetW, y - xOffset);
        ctx.lineTo(x + xOffsetW, y + yOffset);
        ctx.lineTo(x - yOffsetW, y + xOffset);
        ctx.closePath();
        break;
      case 'crossRot':
        rad += QUARTER_PI;
      /* falls through */
      case 'cross':
        xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
        xOffset = Math.cos(rad) * radius;
        yOffset = Math.sin(rad) * radius;
        yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
        ctx.moveTo(x - xOffsetW, y - yOffset);
        ctx.lineTo(x + xOffsetW, y + yOffset);
        ctx.moveTo(x + yOffsetW, y - xOffset);
        ctx.lineTo(x - yOffsetW, y + xOffset);
        break;
      case 'star':
        xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
        xOffset = Math.cos(rad) * radius;
        yOffset = Math.sin(rad) * radius;
        yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
        ctx.moveTo(x - xOffsetW, y - yOffset);
        ctx.lineTo(x + xOffsetW, y + yOffset);
        ctx.moveTo(x + yOffsetW, y - xOffset);
        ctx.lineTo(x - yOffsetW, y + xOffset);
        rad += QUARTER_PI;
        xOffsetW = Math.cos(rad) * (w ? w / 2 : radius);
        xOffset = Math.cos(rad) * radius;
        yOffset = Math.sin(rad) * radius;
        yOffsetW = Math.sin(rad) * (w ? w / 2 : radius);
        ctx.moveTo(x - xOffsetW, y - yOffset);
        ctx.lineTo(x + xOffsetW, y + yOffset);
        ctx.moveTo(x + yOffsetW, y - xOffset);
        ctx.lineTo(x - yOffsetW, y + xOffset);
        break;
      case 'line':
        xOffset = w ? w / 2 : Math.cos(rad) * radius;
        yOffset = Math.sin(rad) * radius;
        ctx.moveTo(x - xOffset, y - yOffset);
        ctx.lineTo(x + xOffset, y + yOffset);
        break;
      case 'dash':
        ctx.moveTo(x, y);
        ctx.lineTo(x + Math.cos(rad) * (w ? w / 2 : radius), y + Math.sin(rad) * radius);
        break;
      case false:
        ctx.closePath();
        break;
    }
    ctx.fill();
    if (options.borderWidth > 0) {
      ctx.stroke();
    }
  }
  /**
   * Returns true if the point is inside the rectangle
   * @param point - The point to test
   * @param area - The rectangle
   * @param margin - allowed margin
   * @private
   */
  function _isPointInArea(point, area, margin) {
    margin = margin || 0.5; // margin - default is to match rounded decimals
    return !area || point && point.x > area.left - margin && point.x < area.right + margin && point.y > area.top - margin && point.y < area.bottom + margin;
  }
  function clipArea(ctx, area) {
    ctx.save();
    ctx.beginPath();
    ctx.rect(area.left, area.top, area.right - area.left, area.bottom - area.top);
    ctx.clip();
  }
  function unclipArea(ctx) {
    ctx.restore();
  }
  /**
   * @private
   */
  function _steppedLineTo(ctx, previous, target, flip, mode) {
    if (!previous) {
      return ctx.lineTo(target.x, target.y);
    }
    if (mode === 'middle') {
      var midpoint = (previous.x + target.x) / 2.0;
      ctx.lineTo(midpoint, previous.y);
      ctx.lineTo(midpoint, target.y);
    } else if (mode === 'after' !== !!flip) {
      ctx.lineTo(previous.x, target.y);
    } else {
      ctx.lineTo(target.x, previous.y);
    }
    ctx.lineTo(target.x, target.y);
  }
  /**
   * @private
   */
  function _bezierCurveTo(ctx, previous, target, flip) {
    if (!previous) {
      return ctx.lineTo(target.x, target.y);
    }
    ctx.bezierCurveTo(flip ? previous.cp1x : previous.cp2x, flip ? previous.cp1y : previous.cp2y, flip ? target.cp2x : target.cp1x, flip ? target.cp2y : target.cp1y, target.x, target.y);
  }
  function setRenderOpts(ctx, opts) {
    if (opts.translation) {
      ctx.translate(opts.translation[0], opts.translation[1]);
    }
    if (!isNullOrUndef(opts.rotation)) {
      ctx.rotate(opts.rotation);
    }
    if (opts.color) {
      ctx.fillStyle = opts.color;
    }
    if (opts.textAlign) {
      ctx.textAlign = opts.textAlign;
    }
    if (opts.textBaseline) {
      ctx.textBaseline = opts.textBaseline;
    }
  }
  function decorateText(ctx, x, y, line, opts) {
    if (opts.strikethrough || opts.underline) {
      /**
      * Now that IE11 support has been dropped, we can use more
      * of the TextMetrics object. The actual bounding boxes
      * are unflagged in Chrome, Firefox, Edge, and Safari so they
      * can be safely used.
      * See https://developer.mozilla.org/en-US/docs/Web/API/TextMetrics#Browser_compatibility
      */
      var metrics = ctx.measureText(line);
      var left = x - metrics.actualBoundingBoxLeft;
      var right = x + metrics.actualBoundingBoxRight;
      var top = y - metrics.actualBoundingBoxAscent;
      var bottom = y + metrics.actualBoundingBoxDescent;
      var yDecoration = opts.strikethrough ? (top + bottom) / 2 : bottom;
      ctx.strokeStyle = ctx.fillStyle;
      ctx.beginPath();
      ctx.lineWidth = opts.decorationWidth || 2;
      ctx.moveTo(left, yDecoration);
      ctx.lineTo(right, yDecoration);
      ctx.stroke();
    }
  }
  function drawBackdrop(ctx, opts) {
    var oldColor = ctx.fillStyle;
    ctx.fillStyle = opts.color;
    ctx.fillRect(opts.left, opts.top, opts.width, opts.height);
    ctx.fillStyle = oldColor;
  }
  /**
   * Render text onto the canvas
   */
  function renderText(ctx, text, x, y, font) {
    var opts = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : {};
    var lines = isArray(text) ? text : [text];
    var stroke = opts.strokeWidth > 0 && opts.strokeColor !== '';
    var i, line;
    ctx.save();
    ctx.font = font.string;
    setRenderOpts(ctx, opts);
    for (i = 0; i < lines.length; ++i) {
      line = lines[i];
      if (opts.backdrop) {
        drawBackdrop(ctx, opts.backdrop);
      }
      if (stroke) {
        if (opts.strokeColor) {
          ctx.strokeStyle = opts.strokeColor;
        }
        if (!isNullOrUndef(opts.strokeWidth)) {
          ctx.lineWidth = opts.strokeWidth;
        }
        ctx.strokeText(line, x, y, opts.maxWidth);
      }
      ctx.fillText(line, x, y, opts.maxWidth);
      decorateText(ctx, x, y, line, opts);
      y += Number(font.lineHeight);
    }
    ctx.restore();
  }
  /**
   * Add a path of a rectangle with rounded corners to the current sub-path
   * @param ctx - Context
   * @param rect - Bounding rect
   */
  function addRoundedRectPath(ctx, rect) {
    var x = rect.x,
      y = rect.y,
      w = rect.w,
      h = rect.h,
      radius = rect.radius;
    // top left arc
    ctx.arc(x + radius.topLeft, y + radius.topLeft, radius.topLeft, 1.5 * PI, PI, true);
    // line from top left to bottom left
    ctx.lineTo(x, y + h - radius.bottomLeft);
    // bottom left arc
    ctx.arc(x + radius.bottomLeft, y + h - radius.bottomLeft, radius.bottomLeft, PI, HALF_PI, true);
    // line from bottom left to bottom right
    ctx.lineTo(x + w - radius.bottomRight, y + h);
    // bottom right arc
    ctx.arc(x + w - radius.bottomRight, y + h - radius.bottomRight, radius.bottomRight, HALF_PI, 0, true);
    // line from bottom right to top right
    ctx.lineTo(x + w, y + radius.topRight);
    // top right arc
    ctx.arc(x + w - radius.topRight, y + radius.topRight, radius.topRight, 0, -HALF_PI, true);
    // line from top right to top left
    ctx.lineTo(x + radius.topLeft, y);
  }
  var LINE_HEIGHT = /^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/;
  var FONT_STYLE = /^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/;
  /**
   * @alias Chart.helpers.options
   * @namespace
   */ /**
      * Converts the given line height `value` in pixels for a specific font `size`.
      * @param value - The lineHeight to parse (eg. 1.6, '14px', '75%', '1.6em').
      * @param size - The font size (in pixels) used to resolve relative `value`.
      * @returns The effective line height in pixels (size * 1.2 if value is invalid).
      * @see https://developer.mozilla.org/en-US/docs/Web/CSS/line-height
      * @since 2.7.0
      */
  function toLineHeight(value, size) {
    var matches = ('' + value).match(LINE_HEIGHT);
    if (!matches || matches[1] === 'normal') {
      return size * 1.2;
    }
    value = +matches[2];
    switch (matches[3]) {
      case 'px':
        return value;
      case '%':
        value /= 100;
        break;
    }
    return size * value;
  }
  var numberOrZero = function numberOrZero(v) {
    return +v || 0;
  };
  function _readValueToProps(value, props) {
    var ret = {};
    var objProps = isObject(props);
    var keys = objProps ? Object.keys(props) : props;
    var read = isObject(value) ? objProps ? function (prop) {
      return valueOrDefault(value[prop], value[props[prop]]);
    } : function (prop) {
      return value[prop];
    } : function () {
      return value;
    };
    var _iterator4 = _createForOfIteratorHelper$1(keys),
      _step4;
    try {
      for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
        var prop = _step4.value;
        ret[prop] = numberOrZero(read(prop));
      }
    } catch (err) {
      _iterator4.e(err);
    } finally {
      _iterator4.f();
    }
    return ret;
  }
  /**
   * Converts the given value into a TRBL object.
   * @param value - If a number, set the value to all TRBL component,
   *  else, if an object, use defined properties and sets undefined ones to 0.
   *  x / y are shorthands for same value for left/right and top/bottom.
   * @returns The padding values (top, right, bottom, left)
   * @since 3.0.0
   */
  function toTRBL(value) {
    return _readValueToProps(value, {
      top: 'y',
      right: 'x',
      bottom: 'y',
      left: 'x'
    });
  }
  /**
   * Converts the given value into a TRBL corners object (similar with css border-radius).
   * @param value - If a number, set the value to all TRBL corner components,
   *  else, if an object, use defined properties and sets undefined ones to 0.
   * @returns The TRBL corner values (topLeft, topRight, bottomLeft, bottomRight)
   * @since 3.0.0
   */
  function toTRBLCorners(value) {
    return _readValueToProps(value, ['topLeft', 'topRight', 'bottomLeft', 'bottomRight']);
  }
  /**
   * Converts the given value into a padding object with pre-computed width/height.
   * @param value - If a number, set the value to all TRBL component,
   *  else, if an object, use defined properties and sets undefined ones to 0.
   *  x / y are shorthands for same value for left/right and top/bottom.
   * @returns The padding values (top, right, bottom, left, width, height)
   * @since 2.7.0
   */
  function toPadding(value) {
    var obj = toTRBL(value);
    obj.width = obj.left + obj.right;
    obj.height = obj.top + obj.bottom;
    return obj;
  }
  /**
   * Parses font options and returns the font object.
   * @param options - A object that contains font options to be parsed.
   * @param fallback - A object that contains fallback font options.
   * @return The font object.
   * @private
   */
  function toFont(options, fallback) {
    options = options || {};
    fallback = fallback || defaults.font;
    var size = valueOrDefault(options.size, fallback.size);
    if (typeof size === 'string') {
      size = parseInt(size, 10);
    }
    var style = valueOrDefault(options.style, fallback.style);
    if (style && !('' + style).match(FONT_STYLE)) {
      console.warn('Invalid font style specified: "' + style + '"');
      style = undefined;
    }
    var font = {
      family: valueOrDefault(options.family, fallback.family),
      lineHeight: toLineHeight(valueOrDefault(options.lineHeight, fallback.lineHeight), size),
      size: size,
      style: style,
      weight: valueOrDefault(options.weight, fallback.weight),
      string: ''
    };
    font.string = toFontString(font);
    return font;
  }
  /**
   * Evaluates the given `inputs` sequentially and returns the first defined value.
   * @param inputs - An array of values, falling back to the last value.
   * @param context - If defined and the current value is a function, the value
   * is called with `context` as first argument and the result becomes the new input.
   * @param index - If defined and the current value is an array, the value
   * at `index` become the new input.
   * @param info - object to return information about resolution in
   * @param info.cacheable - Will be set to `false` if option is not cacheable.
   * @since 2.7.0
   */
  function resolve(inputs, context, index, info) {
    var cacheable = true;
    var i, ilen, value;
    for (i = 0, ilen = inputs.length; i < ilen; ++i) {
      value = inputs[i];
      if (value === undefined) {
        continue;
      }
      if (context !== undefined && typeof value === 'function') {
        value = value(context);
        cacheable = false;
      }
      if (index !== undefined && isArray(value)) {
        value = value[index % value.length];
        cacheable = false;
      }
      if (value !== undefined) {
        if (info && !cacheable) {
          info.cacheable = false;
        }
        return value;
      }
    }
  }
  /**
   * @param minmax
   * @param grace
   * @param beginAtZero
   * @private
   */
  function _addGrace(minmax, grace, beginAtZero) {
    var min = minmax.min,
      max = minmax.max;
    var change = toDimension(grace, (max - min) / 2);
    var keepZero = function keepZero(value, add) {
      return beginAtZero && value === 0 ? 0 : value + add;
    };
    return {
      min: keepZero(min, -Math.abs(change)),
      max: keepZero(max, change)
    };
  }
  function createContext(parentContext, context) {
    return Object.assign(Object.create(parentContext), context);
  }

  /**
   * Creates a Proxy for resolving raw values for options.
   * @param scopes - The option scopes to look for values, in resolution order
   * @param prefixes - The prefixes for values, in resolution order.
   * @param rootScopes - The root option scopes
   * @param fallback - Parent scopes fallback
   * @param getTarget - callback for getting the target for changed values
   * @returns Proxy
   * @private
   */
  function _createResolver(scopes) {
    var prefixes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [''];
    var rootScopes = arguments.length > 2 ? arguments[2] : undefined;
    var fallback = arguments.length > 3 ? arguments[3] : undefined;
    var getTarget = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : function () {
      return scopes[0];
    };
    var finalRootScopes = rootScopes || scopes;
    if (typeof fallback === 'undefined') {
      fallback = _resolve('_fallback', scopes);
    }
    var cache = _defineProperty$1(_defineProperty$1(_defineProperty$1(_defineProperty$1(_defineProperty$1(_defineProperty$1(_defineProperty$1({}, Symbol.toStringTag, 'Object'), "_cacheable", true), "_scopes", scopes), "_rootScopes", finalRootScopes), "_fallback", fallback), "_getTarget", getTarget), "override", function override(scope) {
      return _createResolver([scope].concat(_toConsumableArray(scopes)), prefixes, finalRootScopes, fallback);
    });
    return new Proxy(cache, {
      /**
      * A trap for the delete operator.
      */
      deleteProperty: function deleteProperty(target, prop) {
        delete target[prop]; // remove from cache
        delete target._keys; // remove cached keys
        delete scopes[0][prop]; // remove from top level scope
        return true;
      },
      /**
      * A trap for getting property values.
      */
      get: function get(target, prop) {
        return _cached(target, prop, function () {
          return _resolveWithPrefixes(prop, prefixes, scopes, target);
        });
      },
      /**
      * A trap for Object.getOwnPropertyDescriptor.
      * Also used by Object.hasOwnProperty.
      */
      getOwnPropertyDescriptor: function getOwnPropertyDescriptor(target, prop) {
        return Reflect.getOwnPropertyDescriptor(target._scopes[0], prop);
      },
      /**
      * A trap for Object.getPrototypeOf.
      */
      getPrototypeOf: function getPrototypeOf() {
        return Reflect.getPrototypeOf(scopes[0]);
      },
      /**
      * A trap for the in operator.
      */
      has: function has(target, prop) {
        return getKeysFromAllScopes(target).includes(prop);
      },
      /**
      * A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
      */
      ownKeys: function ownKeys(target) {
        return getKeysFromAllScopes(target);
      },
      /**
      * A trap for setting property values.
      */
      set: function set(target, prop, value) {
        var storage = target._storage || (target._storage = getTarget());
        target[prop] = storage[prop] = value; // set to top level scope + cache
        delete target._keys; // remove cached keys
        return true;
      }
    });
  }
  /**
   * Returns an Proxy for resolving option values with context.
   * @param proxy - The Proxy returned by `_createResolver`
   * @param context - Context object for scriptable/indexable options
   * @param subProxy - The proxy provided for scriptable options
   * @param descriptorDefaults - Defaults for descriptors
   * @private
   */
  function _attachContext(proxy, context, subProxy, descriptorDefaults) {
    var cache = {
      _cacheable: false,
      _proxy: proxy,
      _context: context,
      _subProxy: subProxy,
      _stack: new Set(),
      _descriptors: _descriptors(proxy, descriptorDefaults),
      setContext: function setContext(ctx) {
        return _attachContext(proxy, ctx, subProxy, descriptorDefaults);
      },
      override: function override(scope) {
        return _attachContext(proxy.override(scope), context, subProxy, descriptorDefaults);
      }
    };
    return new Proxy(cache, {
      /**
      * A trap for the delete operator.
      */
      deleteProperty: function deleteProperty(target, prop) {
        delete target[prop]; // remove from cache
        delete proxy[prop]; // remove from proxy
        return true;
      },
      /**
      * A trap for getting property values.
      */
      get: function get(target, prop, receiver) {
        return _cached(target, prop, function () {
          return _resolveWithContext(target, prop, receiver);
        });
      },
      /**
      * A trap for Object.getOwnPropertyDescriptor.
      * Also used by Object.hasOwnProperty.
      */
      getOwnPropertyDescriptor: function getOwnPropertyDescriptor(target, prop) {
        return target._descriptors.allKeys ? Reflect.has(proxy, prop) ? {
          enumerable: true,
          configurable: true
        } : undefined : Reflect.getOwnPropertyDescriptor(proxy, prop);
      },
      /**
      * A trap for Object.getPrototypeOf.
      */
      getPrototypeOf: function getPrototypeOf() {
        return Reflect.getPrototypeOf(proxy);
      },
      /**
      * A trap for the in operator.
      */
      has: function has(target, prop) {
        return Reflect.has(proxy, prop);
      },
      /**
      * A trap for Object.getOwnPropertyNames and Object.getOwnPropertySymbols.
      */
      ownKeys: function ownKeys() {
        return Reflect.ownKeys(proxy);
      },
      /**
      * A trap for setting property values.
      */
      set: function set(target, prop, value) {
        proxy[prop] = value; // set to proxy
        delete target[prop]; // remove from cache
        return true;
      }
    });
  }
  /**
   * @private
   */
  function _descriptors(proxy) {
    var defaults = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {
      scriptable: true,
      indexable: true
    };
    var _proxy$_scriptable = proxy._scriptable,
      _scriptable = _proxy$_scriptable === void 0 ? defaults.scriptable : _proxy$_scriptable,
      _proxy$_indexable = proxy._indexable,
      _indexable = _proxy$_indexable === void 0 ? defaults.indexable : _proxy$_indexable,
      _proxy$_allKeys = proxy._allKeys,
      _allKeys = _proxy$_allKeys === void 0 ? defaults.allKeys : _proxy$_allKeys;
    return {
      allKeys: _allKeys,
      scriptable: _scriptable,
      indexable: _indexable,
      isScriptable: isFunction(_scriptable) ? _scriptable : function () {
        return _scriptable;
      },
      isIndexable: isFunction(_indexable) ? _indexable : function () {
        return _indexable;
      }
    };
  }
  var readKey = function readKey(prefix, name) {
    return prefix ? prefix + _capitalize(name) : name;
  };
  var needsSubResolver = function needsSubResolver(prop, value) {
    return isObject(value) && prop !== 'adapters' && (Object.getPrototypeOf(value) === null || value.constructor === Object);
  };
  function _cached(target, prop, resolve) {
    if (Object.prototype.hasOwnProperty.call(target, prop) || prop === 'constructor') {
      return target[prop];
    }
    var value = resolve();
    // cache the resolved value
    target[prop] = value;
    return value;
  }
  function _resolveWithContext(target, prop, receiver) {
    var _proxy = target._proxy,
      _context = target._context,
      _subProxy = target._subProxy,
      descriptors = target._descriptors;
    var value = _proxy[prop]; // resolve from proxy
    // resolve with context
    if (isFunction(value) && descriptors.isScriptable(prop)) {
      value = _resolveScriptable(prop, value, target, receiver);
    }
    if (isArray(value) && value.length) {
      value = _resolveArray(prop, value, target, descriptors.isIndexable);
    }
    if (needsSubResolver(prop, value)) {
      // if the resolved value is an object, create a sub resolver for it
      value = _attachContext(value, _context, _subProxy && _subProxy[prop], descriptors);
    }
    return value;
  }
  function _resolveScriptable(prop, getValue, target, receiver) {
    var _proxy = target._proxy,
      _context = target._context,
      _subProxy = target._subProxy,
      _stack = target._stack;
    if (_stack.has(prop)) {
      throw new Error('Recursion detected: ' + Array.from(_stack).join('->') + '->' + prop);
    }
    _stack.add(prop);
    var value = getValue(_context, _subProxy || receiver);
    _stack["delete"](prop);
    if (needsSubResolver(prop, value)) {
      // When scriptable option returns an object, create a resolver on that.
      value = createSubResolver(_proxy._scopes, _proxy, prop, value);
    }
    return value;
  }
  function _resolveArray(prop, value, target, isIndexable) {
    var _proxy = target._proxy,
      _context = target._context,
      _subProxy = target._subProxy,
      descriptors = target._descriptors;
    if (typeof _context.index !== 'undefined' && isIndexable(prop)) {
      return value[_context.index % value.length];
    } else if (isObject(value[0])) {
      // Array of objects, return array or resolvers
      var arr = value;
      var scopes = _proxy._scopes.filter(function (s) {
        return s !== arr;
      });
      value = [];
      var _iterator5 = _createForOfIteratorHelper$1(arr),
        _step5;
      try {
        for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
          var item = _step5.value;
          var resolver = createSubResolver(scopes, _proxy, prop, item);
          value.push(_attachContext(resolver, _context, _subProxy && _subProxy[prop], descriptors));
        }
      } catch (err) {
        _iterator5.e(err);
      } finally {
        _iterator5.f();
      }
    }
    return value;
  }
  function resolveFallback(fallback, prop, value) {
    return isFunction(fallback) ? fallback(prop, value) : fallback;
  }
  var getScope = function getScope(key, parent) {
    return key === true ? parent : typeof key === 'string' ? resolveObjectKey(parent, key) : undefined;
  };
  function addScopes(set, parentScopes, key, parentFallback, value) {
    var _iterator6 = _createForOfIteratorHelper$1(parentScopes),
      _step6;
    try {
      for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
        var parent = _step6.value;
        var scope = getScope(key, parent);
        if (scope) {
          set.add(scope);
          var fallback = resolveFallback(scope._fallback, key, value);
          if (typeof fallback !== 'undefined' && fallback !== key && fallback !== parentFallback) {
            // When we reach the descriptor that defines a new _fallback, return that.
            // The fallback will resume to that new scope.
            return fallback;
          }
        } else if (scope === false && typeof parentFallback !== 'undefined' && key !== parentFallback) {
          // Fallback to `false` results to `false`, when falling back to different key.
          // For example `interaction` from `hover` or `plugins.tooltip` and `animation` from `animations`
          return null;
        }
      }
    } catch (err) {
      _iterator6.e(err);
    } finally {
      _iterator6.f();
    }
    return false;
  }
  function createSubResolver(parentScopes, resolver, prop, value) {
    var rootScopes = resolver._rootScopes;
    var fallback = resolveFallback(resolver._fallback, prop, value);
    var allScopes = [].concat(_toConsumableArray(parentScopes), _toConsumableArray(rootScopes));
    var set = new Set();
    set.add(value);
    var key = addScopesFromKey(set, allScopes, prop, fallback || prop, value);
    if (key === null) {
      return false;
    }
    if (typeof fallback !== 'undefined' && fallback !== prop) {
      key = addScopesFromKey(set, allScopes, fallback, key, value);
      if (key === null) {
        return false;
      }
    }
    return _createResolver(Array.from(set), [''], rootScopes, fallback, function () {
      return subGetTarget(resolver, prop, value);
    });
  }
  function addScopesFromKey(set, allScopes, key, fallback, item) {
    while (key) {
      key = addScopes(set, allScopes, key, fallback, item);
    }
    return key;
  }
  function subGetTarget(resolver, prop, value) {
    var parent = resolver._getTarget();
    if (!(prop in parent)) {
      parent[prop] = {};
    }
    var target = parent[prop];
    if (isArray(target) && isObject(value)) {
      // For array of objects, the object is used to store updated values
      return value;
    }
    return target || {};
  }
  function _resolveWithPrefixes(prop, prefixes, scopes, proxy) {
    var value;
    var _iterator7 = _createForOfIteratorHelper$1(prefixes),
      _step7;
    try {
      for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
        var prefix = _step7.value;
        value = _resolve(readKey(prefix, prop), scopes);
        if (typeof value !== 'undefined') {
          return needsSubResolver(prop, value) ? createSubResolver(scopes, proxy, prop, value) : value;
        }
      }
    } catch (err) {
      _iterator7.e(err);
    } finally {
      _iterator7.f();
    }
  }
  function _resolve(key, scopes) {
    var _iterator8 = _createForOfIteratorHelper$1(scopes),
      _step8;
    try {
      for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
        var scope = _step8.value;
        if (!scope) {
          continue;
        }
        var value = scope[key];
        if (typeof value !== 'undefined') {
          return value;
        }
      }
    } catch (err) {
      _iterator8.e(err);
    } finally {
      _iterator8.f();
    }
  }
  function getKeysFromAllScopes(target) {
    var keys = target._keys;
    if (!keys) {
      keys = target._keys = resolveKeysFromAllScopes(target._scopes);
    }
    return keys;
  }
  function resolveKeysFromAllScopes(scopes) {
    var set = new Set();
    var _iterator9 = _createForOfIteratorHelper$1(scopes),
      _step9;
    try {
      for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
        var scope = _step9.value;
        var _iterator10 = _createForOfIteratorHelper$1(Object.keys(scope).filter(function (k) {
            return !k.startsWith('_');
          })),
          _step10;
        try {
          for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
            var key = _step10.value;
            set.add(key);
          }
        } catch (err) {
          _iterator10.e(err);
        } finally {
          _iterator10.f();
        }
      }
    } catch (err) {
      _iterator9.e(err);
    } finally {
      _iterator9.f();
    }
    return Array.from(set);
  }
  function _parseObjectDataRadialScale(meta, data, start, count) {
    var iScale = meta.iScale;
    var _this$_parsing$key = this._parsing.key,
      key = _this$_parsing$key === void 0 ? 'r' : _this$_parsing$key;
    var parsed = new Array(count);
    var i, ilen, index, item;
    for (i = 0, ilen = count; i < ilen; ++i) {
      index = i + start;
      item = data[index];
      parsed[i] = {
        r: iScale.parse(resolveObjectKey(item, key), index)
      };
    }
    return parsed;
  }
  var EPSILON = Number.EPSILON || 1e-14;
  var getPoint = function getPoint(points, i) {
    return i < points.length && !points[i].skip && points[i];
  };
  var getValueAxis = function getValueAxis(indexAxis) {
    return indexAxis === 'x' ? 'y' : 'x';
  };
  function splineCurve(firstPoint, middlePoint, afterPoint, t) {
    // Props to Rob Spencer at scaled innovation for his post on splining between points
    // http://scaledinnovation.com/analytics/splines/aboutSplines.html
    // This function must also respect "skipped" points
    var previous = firstPoint.skip ? middlePoint : firstPoint;
    var current = middlePoint;
    var next = afterPoint.skip ? middlePoint : afterPoint;
    var d01 = distanceBetweenPoints(current, previous);
    var d12 = distanceBetweenPoints(next, current);
    var s01 = d01 / (d01 + d12);
    var s12 = d12 / (d01 + d12);
    // If all points are the same, s01 & s02 will be inf
    s01 = isNaN(s01) ? 0 : s01;
    s12 = isNaN(s12) ? 0 : s12;
    var fa = t * s01; // scaling factor for triangle Ta
    var fb = t * s12;
    return {
      previous: {
        x: current.x - fa * (next.x - previous.x),
        y: current.y - fa * (next.y - previous.y)
      },
      next: {
        x: current.x + fb * (next.x - previous.x),
        y: current.y + fb * (next.y - previous.y)
      }
    };
  }
  /**
   * Adjust tangents to ensure monotonic properties
   */
  function monotoneAdjust(points, deltaK, mK) {
    var pointsLen = points.length;
    var alphaK, betaK, tauK, squaredMagnitude, pointCurrent;
    var pointAfter = getPoint(points, 0);
    for (var i = 0; i < pointsLen - 1; ++i) {
      pointCurrent = pointAfter;
      pointAfter = getPoint(points, i + 1);
      if (!pointCurrent || !pointAfter) {
        continue;
      }
      if (almostEquals(deltaK[i], 0, EPSILON)) {
        mK[i] = mK[i + 1] = 0;
        continue;
      }
      alphaK = mK[i] / deltaK[i];
      betaK = mK[i + 1] / deltaK[i];
      squaredMagnitude = Math.pow(alphaK, 2) + Math.pow(betaK, 2);
      if (squaredMagnitude <= 9) {
        continue;
      }
      tauK = 3 / Math.sqrt(squaredMagnitude);
      mK[i] = alphaK * tauK * deltaK[i];
      mK[i + 1] = betaK * tauK * deltaK[i];
    }
  }
  function monotoneCompute(points, mK) {
    var indexAxis = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'x';
    var valueAxis = getValueAxis(indexAxis);
    var pointsLen = points.length;
    var delta, pointBefore, pointCurrent;
    var pointAfter = getPoint(points, 0);
    for (var i = 0; i < pointsLen; ++i) {
      pointBefore = pointCurrent;
      pointCurrent = pointAfter;
      pointAfter = getPoint(points, i + 1);
      if (!pointCurrent) {
        continue;
      }
      var iPixel = pointCurrent[indexAxis];
      var vPixel = pointCurrent[valueAxis];
      if (pointBefore) {
        delta = (iPixel - pointBefore[indexAxis]) / 3;
        pointCurrent["cp1".concat(indexAxis)] = iPixel - delta;
        pointCurrent["cp1".concat(valueAxis)] = vPixel - delta * mK[i];
      }
      if (pointAfter) {
        delta = (pointAfter[indexAxis] - iPixel) / 3;
        pointCurrent["cp2".concat(indexAxis)] = iPixel + delta;
        pointCurrent["cp2".concat(valueAxis)] = vPixel + delta * mK[i];
      }
    }
  }
  /**
   * This function calculates Bézier control points in a similar way than |splineCurve|,
   * but preserves monotonicity of the provided data and ensures no local extremums are added
   * between the dataset discrete points due to the interpolation.
   * See : https://en.wikipedia.org/wiki/Monotone_cubic_interpolation
   */
  function splineCurveMonotone(points) {
    var indexAxis = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'x';
    var valueAxis = getValueAxis(indexAxis);
    var pointsLen = points.length;
    var deltaK = Array(pointsLen).fill(0);
    var mK = Array(pointsLen);
    // Calculate slopes (deltaK) and initialize tangents (mK)
    var i, pointBefore, pointCurrent;
    var pointAfter = getPoint(points, 0);
    for (i = 0; i < pointsLen; ++i) {
      pointBefore = pointCurrent;
      pointCurrent = pointAfter;
      pointAfter = getPoint(points, i + 1);
      if (!pointCurrent) {
        continue;
      }
      if (pointAfter) {
        var slopeDelta = pointAfter[indexAxis] - pointCurrent[indexAxis];
        // In the case of two points that appear at the same x pixel, slopeDeltaX is 0
        deltaK[i] = slopeDelta !== 0 ? (pointAfter[valueAxis] - pointCurrent[valueAxis]) / slopeDelta : 0;
      }
      mK[i] = !pointBefore ? deltaK[i] : !pointAfter ? deltaK[i - 1] : sign(deltaK[i - 1]) !== sign(deltaK[i]) ? 0 : (deltaK[i - 1] + deltaK[i]) / 2;
    }
    monotoneAdjust(points, deltaK, mK);
    monotoneCompute(points, mK, indexAxis);
  }
  function capControlPoint(pt, min, max) {
    return Math.max(Math.min(pt, max), min);
  }
  function capBezierPoints(points, area) {
    var i, ilen, point, inArea, inAreaPrev;
    var inAreaNext = _isPointInArea(points[0], area);
    for (i = 0, ilen = points.length; i < ilen; ++i) {
      inAreaPrev = inArea;
      inArea = inAreaNext;
      inAreaNext = i < ilen - 1 && _isPointInArea(points[i + 1], area);
      if (!inArea) {
        continue;
      }
      point = points[i];
      if (inAreaPrev) {
        point.cp1x = capControlPoint(point.cp1x, area.left, area.right);
        point.cp1y = capControlPoint(point.cp1y, area.top, area.bottom);
      }
      if (inAreaNext) {
        point.cp2x = capControlPoint(point.cp2x, area.left, area.right);
        point.cp2y = capControlPoint(point.cp2y, area.top, area.bottom);
      }
    }
  }
  /**
   * @private
   */
  function _updateBezierControlPoints(points, options, area, loop, indexAxis) {
    var i, ilen, point, controlPoints;
    // Only consider points that are drawn in case the spanGaps option is used
    if (options.spanGaps) {
      points = points.filter(function (pt) {
        return !pt.skip;
      });
    }
    if (options.cubicInterpolationMode === 'monotone') {
      splineCurveMonotone(points, indexAxis);
    } else {
      var prev = loop ? points[points.length - 1] : points[0];
      for (i = 0, ilen = points.length; i < ilen; ++i) {
        point = points[i];
        controlPoints = splineCurve(prev, point, points[Math.min(i + 1, ilen - (loop ? 0 : 1)) % ilen], options.tension);
        point.cp1x = controlPoints.previous.x;
        point.cp1y = controlPoints.previous.y;
        point.cp2x = controlPoints.next.x;
        point.cp2y = controlPoints.next.y;
        prev = point;
      }
    }
    if (options.capBezierPoints) {
      capBezierPoints(points, area);
    }
  }

  /**
   * Note: typedefs are auto-exported, so use a made-up `dom` namespace where
   * necessary to avoid duplicates with `export * from './helpers`; see
   * https://github.com/microsoft/TypeScript/issues/46011
   * @typedef { import('../core/core.controller.js').default } dom.Chart
   * @typedef { import('../../types').ChartEvent } ChartEvent
   */ /**
      * @private
      */
  function _isDomSupported() {
    return typeof window !== 'undefined' && typeof document !== 'undefined';
  }
  /**
   * @private
   */
  function _getParentNode(domNode) {
    var parent = domNode.parentNode;
    if (parent && parent.toString() === '[object ShadowRoot]') {
      parent = parent.host;
    }
    return parent;
  }
  /**
   * convert max-width/max-height values that may be percentages into a number
   * @private
   */
  function parseMaxStyle(styleValue, node, parentProperty) {
    var valueInPixels;
    if (typeof styleValue === 'string') {
      valueInPixels = parseInt(styleValue, 10);
      if (styleValue.indexOf('%') !== -1) {
        // percentage * size in dimension
        valueInPixels = valueInPixels / 100 * node.parentNode[parentProperty];
      }
    } else {
      valueInPixels = styleValue;
    }
    return valueInPixels;
  }
  var getComputedStyle = function getComputedStyle(element) {
    return element.ownerDocument.defaultView.getComputedStyle(element, null);
  };
  function getStyle(el, property) {
    return getComputedStyle(el).getPropertyValue(property);
  }
  var positions = ['top', 'right', 'bottom', 'left'];
  function getPositionedStyle(styles, style, suffix) {
    var result = {};
    suffix = suffix ? '-' + suffix : '';
    for (var i = 0; i < 4; i++) {
      var pos = positions[i];
      result[pos] = parseFloat(styles[style + '-' + pos + suffix]) || 0;
    }
    result.width = result.left + result.right;
    result.height = result.top + result.bottom;
    return result;
  }
  var useOffsetPos = function useOffsetPos(x, y, target) {
    return (x > 0 || y > 0) && (!target || !target.shadowRoot);
  };
  /**
   * @param e
   * @param canvas
   * @returns Canvas position
   */
  function getCanvasPosition(e, canvas) {
    var touches = e.touches;
    var source = touches && touches.length ? touches[0] : e;
    var offsetX = source.offsetX,
      offsetY = source.offsetY;
    var box = false;
    var x, y;
    if (useOffsetPos(offsetX, offsetY, e.target)) {
      x = offsetX;
      y = offsetY;
    } else {
      var rect = canvas.getBoundingClientRect();
      x = source.clientX - rect.left;
      y = source.clientY - rect.top;
      box = true;
    }
    return {
      x: x,
      y: y,
      box: box
    };
  }
  /**
   * Gets an event's x, y coordinates, relative to the chart area
   * @param event
   * @param chart
   * @returns x and y coordinates of the event
   */
  function getRelativePosition(event, chart) {
    if ('native' in event) {
      return event;
    }
    var canvas = chart.canvas,
      currentDevicePixelRatio = chart.currentDevicePixelRatio;
    var style = getComputedStyle(canvas);
    var borderBox = style.boxSizing === 'border-box';
    var paddings = getPositionedStyle(style, 'padding');
    var borders = getPositionedStyle(style, 'border', 'width');
    var _getCanvasPosition = getCanvasPosition(event, canvas),
      x = _getCanvasPosition.x,
      y = _getCanvasPosition.y,
      box = _getCanvasPosition.box;
    var xOffset = paddings.left + (box && borders.left);
    var yOffset = paddings.top + (box && borders.top);
    var width = chart.width,
      height = chart.height;
    if (borderBox) {
      width -= paddings.width + borders.width;
      height -= paddings.height + borders.height;
    }
    return {
      x: Math.round((x - xOffset) / width * canvas.width / currentDevicePixelRatio),
      y: Math.round((y - yOffset) / height * canvas.height / currentDevicePixelRatio)
    };
  }
  function getContainerSize(canvas, width, height) {
    var maxWidth, maxHeight;
    if (width === undefined || height === undefined) {
      var container = canvas && _getParentNode(canvas);
      if (!container) {
        width = canvas.clientWidth;
        height = canvas.clientHeight;
      } else {
        var rect = container.getBoundingClientRect(); // this is the border box of the container
        var containerStyle = getComputedStyle(container);
        var containerBorder = getPositionedStyle(containerStyle, 'border', 'width');
        var containerPadding = getPositionedStyle(containerStyle, 'padding');
        width = rect.width - containerPadding.width - containerBorder.width;
        height = rect.height - containerPadding.height - containerBorder.height;
        maxWidth = parseMaxStyle(containerStyle.maxWidth, container, 'clientWidth');
        maxHeight = parseMaxStyle(containerStyle.maxHeight, container, 'clientHeight');
      }
    }
    return {
      width: width,
      height: height,
      maxWidth: maxWidth || INFINITY,
      maxHeight: maxHeight || INFINITY
    };
  }
  var round1 = function round1(v) {
    return Math.round(v * 10) / 10;
  };
  // eslint-disable-next-line complexity
  function getMaximumSize(canvas, bbWidth, bbHeight, aspectRatio) {
    var style = getComputedStyle(canvas);
    var margins = getPositionedStyle(style, 'margin');
    var maxWidth = parseMaxStyle(style.maxWidth, canvas, 'clientWidth') || INFINITY;
    var maxHeight = parseMaxStyle(style.maxHeight, canvas, 'clientHeight') || INFINITY;
    var containerSize = getContainerSize(canvas, bbWidth, bbHeight);
    var width = containerSize.width,
      height = containerSize.height;
    if (style.boxSizing === 'content-box') {
      var borders = getPositionedStyle(style, 'border', 'width');
      var paddings = getPositionedStyle(style, 'padding');
      width -= paddings.width + borders.width;
      height -= paddings.height + borders.height;
    }
    width = Math.max(0, width - margins.width);
    height = Math.max(0, aspectRatio ? width / aspectRatio : height - margins.height);
    width = round1(Math.min(width, maxWidth, containerSize.maxWidth));
    height = round1(Math.min(height, maxHeight, containerSize.maxHeight));
    if (width && !height) {
      // https://github.com/chartjs/Chart.js/issues/4659
      // If the canvas has width, but no height, default to aspectRatio of 2 (canvas default)
      height = round1(width / 2);
    }
    var maintainHeight = bbWidth !== undefined || bbHeight !== undefined;
    if (maintainHeight && aspectRatio && containerSize.height && height > containerSize.height) {
      height = containerSize.height;
      width = round1(Math.floor(height * aspectRatio));
    }
    return {
      width: width,
      height: height
    };
  }
  /**
   * @param chart
   * @param forceRatio
   * @param forceStyle
   * @returns True if the canvas context size or transformation has changed.
   */
  function retinaScale(chart, forceRatio, forceStyle) {
    var pixelRatio = forceRatio || 1;
    var deviceHeight = Math.floor(chart.height * pixelRatio);
    var deviceWidth = Math.floor(chart.width * pixelRatio);
    chart.height = Math.floor(chart.height);
    chart.width = Math.floor(chart.width);
    var canvas = chart.canvas;
    // If no style has been set on the canvas, the render size is used as display size,
    // making the chart visually bigger, so let's enforce it to the "correct" values.
    // See https://github.com/chartjs/Chart.js/issues/3575
    if (canvas.style && (forceStyle || !canvas.style.height && !canvas.style.width)) {
      canvas.style.height = "".concat(chart.height, "px");
      canvas.style.width = "".concat(chart.width, "px");
    }
    if (chart.currentDevicePixelRatio !== pixelRatio || canvas.height !== deviceHeight || canvas.width !== deviceWidth) {
      chart.currentDevicePixelRatio = pixelRatio;
      canvas.height = deviceHeight;
      canvas.width = deviceWidth;
      chart.ctx.setTransform(pixelRatio, 0, 0, pixelRatio, 0, 0);
      return true;
    }
    return false;
  }
  /**
   * Detects support for options object argument in addEventListener.
   * https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener#Safely_detecting_option_support
   * @private
   */
  var supportsEventListenerOptions = function () {
    var passiveSupported = false;
    try {
      var options = {
        get passive() {
          passiveSupported = true;
          return false;
        }
      };
      if (_isDomSupported()) {
        window.addEventListener('test', null, options);
        window.removeEventListener('test', null, options);
      }
    } catch (e) {
      // continue regardless of error
    }
    return passiveSupported;
  }();
  /**
   * The "used" size is the final value of a dimension property after all calculations have
   * been performed. This method uses the computed style of `element` but returns undefined
   * if the computed style is not expressed in pixels. That can happen in some cases where
   * `element` has a size relative to its parent and this last one is not yet displayed,
   * for example because of `display: none` on a parent node.
   * @see https://developer.mozilla.org/en-US/docs/Web/CSS/used_value
   * @returns Size in pixels or undefined if unknown.
   */
  function readUsedSize(element, property) {
    var value = getStyle(element, property);
    var matches = value && value.match(/^(\d+)(\.\d+)?px$/);
    return matches ? +matches[1] : undefined;
  }

  /**
   * @private
   */
  function _pointInLine(p1, p2, t, mode) {
    return {
      x: p1.x + t * (p2.x - p1.x),
      y: p1.y + t * (p2.y - p1.y)
    };
  }
  /**
   * @private
   */
  function _steppedInterpolation(p1, p2, t, mode) {
    return {
      x: p1.x + t * (p2.x - p1.x),
      y: mode === 'middle' ? t < 0.5 ? p1.y : p2.y : mode === 'after' ? t < 1 ? p1.y : p2.y : t > 0 ? p2.y : p1.y
    };
  }
  /**
   * @private
   */
  function _bezierInterpolation(p1, p2, t, mode) {
    var cp1 = {
      x: p1.cp2x,
      y: p1.cp2y
    };
    var cp2 = {
      x: p2.cp1x,
      y: p2.cp1y
    };
    var a = _pointInLine(p1, cp1, t);
    var b = _pointInLine(cp1, cp2, t);
    var c = _pointInLine(cp2, p2, t);
    var d = _pointInLine(a, b, t);
    var e = _pointInLine(b, c, t);
    return _pointInLine(d, e, t);
  }
  var getRightToLeftAdapter = function getRightToLeftAdapter(rectX, width) {
    return {
      x: function x(_x) {
        return rectX + rectX + width - _x;
      },
      setWidth: function setWidth(w) {
        width = w;
      },
      textAlign: function textAlign(align) {
        if (align === 'center') {
          return align;
        }
        return align === 'right' ? 'left' : 'right';
      },
      xPlus: function xPlus(x, value) {
        return x - value;
      },
      leftForLtr: function leftForLtr(x, itemWidth) {
        return x - itemWidth;
      }
    };
  };
  var getLeftToRightAdapter = function getLeftToRightAdapter() {
    return {
      x: function x(_x2) {
        return _x2;
      },
      setWidth: function setWidth(w) {},
      textAlign: function textAlign(align) {
        return align;
      },
      xPlus: function xPlus(x, value) {
        return x + value;
      },
      leftForLtr: function leftForLtr(x, _itemWidth) {
        return x;
      }
    };
  };
  function getRtlAdapter(rtl, rectX, width) {
    return rtl ? getRightToLeftAdapter(rectX, width) : getLeftToRightAdapter();
  }
  function overrideTextDirection(ctx, direction) {
    var style, original;
    if (direction === 'ltr' || direction === 'rtl') {
      style = ctx.canvas.style;
      original = [style.getPropertyValue('direction'), style.getPropertyPriority('direction')];
      style.setProperty('direction', direction, 'important');
      ctx.prevTextDirection = original;
    }
  }
  function restoreTextDirection(ctx, original) {
    if (original !== undefined) {
      delete ctx.prevTextDirection;
      ctx.canvas.style.setProperty('direction', original[0], original[1]);
    }
  }
  function propertyFn(property) {
    if (property === 'angle') {
      return {
        between: _angleBetween,
        compare: _angleDiff,
        normalize: _normalizeAngle
      };
    }
    return {
      between: _isBetween,
      compare: function compare(a, b) {
        return a - b;
      },
      normalize: function normalize(x) {
        return x;
      }
    };
  }
  function normalizeSegment(_ref) {
    var start = _ref.start,
      end = _ref.end,
      count = _ref.count,
      loop = _ref.loop,
      style = _ref.style;
    return {
      start: start % count,
      end: end % count,
      loop: loop && (end - start + 1) % count === 0,
      style: style
    };
  }
  function getSegment(segment, points, bounds) {
    var property = bounds.property,
      startBound = bounds.start,
      endBound = bounds.end;
    var _propertyFn = propertyFn(property),
      between = _propertyFn.between,
      normalize = _propertyFn.normalize;
    var count = points.length;
    var start = segment.start,
      end = segment.end,
      loop = segment.loop;
    var i, ilen;
    if (loop) {
      start += count;
      end += count;
      for (i = 0, ilen = count; i < ilen; ++i) {
        if (!between(normalize(points[start % count][property]), startBound, endBound)) {
          break;
        }
        start--;
        end--;
      }
      start %= count;
      end %= count;
    }
    if (end < start) {
      end += count;
    }
    return {
      start: start,
      end: end,
      loop: loop,
      style: segment.style
    };
  }
  function _boundSegment(segment, points, bounds) {
    if (!bounds) {
      return [segment];
    }
    var property = bounds.property,
      startBound = bounds.start,
      endBound = bounds.end;
    var count = points.length;
    var _propertyFn2 = propertyFn(property),
      compare = _propertyFn2.compare,
      between = _propertyFn2.between,
      normalize = _propertyFn2.normalize;
    var _getSegment = getSegment(segment, points, bounds),
      start = _getSegment.start,
      end = _getSegment.end,
      loop = _getSegment.loop,
      style = _getSegment.style;
    var result = [];
    var inside = false;
    var subStart = null;
    var value, point, prevValue;
    var startIsBefore = function startIsBefore() {
      return between(startBound, prevValue, value) && compare(startBound, prevValue) !== 0;
    };
    var endIsBefore = function endIsBefore() {
      return compare(endBound, value) === 0 || between(endBound, prevValue, value);
    };
    var shouldStart = function shouldStart() {
      return inside || startIsBefore();
    };
    var shouldStop = function shouldStop() {
      return !inside || endIsBefore();
    };
    for (var i = start, prev = start; i <= end; ++i) {
      point = points[i % count];
      if (point.skip) {
        continue;
      }
      value = normalize(point[property]);
      if (value === prevValue) {
        continue;
      }
      inside = between(value, startBound, endBound);
      if (subStart === null && shouldStart()) {
        subStart = compare(value, startBound) === 0 ? i : prev;
      }
      if (subStart !== null && shouldStop()) {
        result.push(normalizeSegment({
          start: subStart,
          end: i,
          loop: loop,
          count: count,
          style: style
        }));
        subStart = null;
      }
      prev = i;
      prevValue = value;
    }
    if (subStart !== null) {
      result.push(normalizeSegment({
        start: subStart,
        end: end,
        loop: loop,
        count: count,
        style: style
      }));
    }
    return result;
  }
  function _boundSegments(line, bounds) {
    var result = [];
    var segments = line.segments;
    for (var i = 0; i < segments.length; i++) {
      var sub = _boundSegment(segments[i], line.points, bounds);
      if (sub.length) {
        result.push.apply(result, _toConsumableArray(sub));
      }
    }
    return result;
  }
  function findStartAndEnd(points, count, loop, spanGaps) {
    var start = 0;
    var end = count - 1;
    if (loop && !spanGaps) {
      while (start < count && !points[start].skip) {
        start++;
      }
    }
    while (start < count && points[start].skip) {
      start++;
    }
    start %= count;
    if (loop) {
      end += start;
    }
    while (end > start && points[end % count].skip) {
      end--;
    }
    end %= count;
    return {
      start: start,
      end: end
    };
  }
  function solidSegments(points, start, max, loop) {
    var count = points.length;
    var result = [];
    var last = start;
    var prev = points[start];
    var end;
    for (end = start + 1; end <= max; ++end) {
      var cur = points[end % count];
      if (cur.skip || cur.stop) {
        if (!prev.skip) {
          loop = false;
          result.push({
            start: start % count,
            end: (end - 1) % count,
            loop: loop
          });
          start = last = cur.stop ? end : null;
        }
      } else {
        last = end;
        if (prev.skip) {
          start = end;
        }
      }
      prev = cur;
    }
    if (last !== null) {
      result.push({
        start: start % count,
        end: last % count,
        loop: loop
      });
    }
    return result;
  }
  function _computeSegments(line, segmentOptions) {
    var points = line.points;
    var spanGaps = line.options.spanGaps;
    var count = points.length;
    if (!count) {
      return [];
    }
    var loop = !!line._loop;
    var _findStartAndEnd = findStartAndEnd(points, count, loop, spanGaps),
      start = _findStartAndEnd.start,
      end = _findStartAndEnd.end;
    if (spanGaps === true) {
      return splitByStyles(line, [{
        start: start,
        end: end,
        loop: loop
      }], points, segmentOptions);
    }
    var max = end < start ? end + count : end;
    var completeLoop = !!line._fullLoop && start === 0 && end === count - 1;
    return splitByStyles(line, solidSegments(points, start, max, completeLoop), points, segmentOptions);
  }
  function splitByStyles(line, segments, points, segmentOptions) {
    if (!segmentOptions || !segmentOptions.setContext || !points) {
      return segments;
    }
    return doSplitByStyles(line, segments, points, segmentOptions);
  }
  function doSplitByStyles(line, segments, points, segmentOptions) {
    var chartContext = line._chart.getContext();
    var baseStyle = readStyle(line.options);
    var datasetIndex = line._datasetIndex,
      spanGaps = line.options.spanGaps;
    var count = points.length;
    var result = [];
    var prevStyle = baseStyle;
    var start = segments[0].start;
    var i = start;
    function addStyle(s, e, l, st) {
      var dir = spanGaps ? -1 : 1;
      if (s === e) {
        return;
      }
      s += count;
      while (points[s % count].skip) {
        s -= dir;
      }
      while (points[e % count].skip) {
        e += dir;
      }
      if (s % count !== e % count) {
        result.push({
          start: s % count,
          end: e % count,
          loop: l,
          style: st
        });
        prevStyle = st;
        start = e % count;
      }
    }
    var _iterator11 = _createForOfIteratorHelper$1(segments),
      _step11;
    try {
      for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
        var segment = _step11.value;
        start = spanGaps ? start : segment.start;
        var prev = points[start % count];
        var style = void 0;
        for (i = start + 1; i <= segment.end; i++) {
          var pt = points[i % count];
          style = readStyle(segmentOptions.setContext(createContext(chartContext, {
            type: 'segment',
            p0: prev,
            p1: pt,
            p0DataIndex: (i - 1) % count,
            p1DataIndex: i % count,
            datasetIndex: datasetIndex
          })));
          if (styleChanged(style, prevStyle)) {
            addStyle(start, i - 1, segment.loop, prevStyle);
          }
          prev = pt;
          prevStyle = style;
        }
        if (start < i - 1) {
          addStyle(start, i - 1, segment.loop, prevStyle);
        }
      }
    } catch (err) {
      _iterator11.e(err);
    } finally {
      _iterator11.f();
    }
    return result;
  }
  function readStyle(options) {
    return {
      backgroundColor: options.backgroundColor,
      borderCapStyle: options.borderCapStyle,
      borderDash: options.borderDash,
      borderDashOffset: options.borderDashOffset,
      borderJoinStyle: options.borderJoinStyle,
      borderWidth: options.borderWidth,
      borderColor: options.borderColor
    };
  }
  function styleChanged(style, prevStyle) {
    if (!prevStyle) {
      return false;
    }
    var cache = [];
    var replacer = function replacer(key, value) {
      if (!isPatternOrGradient(value)) {
        return value;
      }
      if (!cache.includes(value)) {
        cache.push(value);
      }
      return cache.indexOf(value);
    };
    return JSON.stringify(style, replacer) !== JSON.stringify(prevStyle, replacer);
  }

  var Animator = /*#__PURE__*/function () {
    function Animator() {
      _classCallCheck$1(this, Animator);
      this._request = null;
      this._charts = new Map();
      this._running = false;
      this._lastDate = undefined;
    }
    return _createClass$1(Animator, [{
      key: "_notify",
      value: function _notify(chart, anims, date, type) {
        var callbacks = anims.listeners[type];
        var numSteps = anims.duration;
        callbacks.forEach(function (fn) {
          return fn({
            chart: chart,
            initial: anims.initial,
            numSteps: numSteps,
            currentStep: Math.min(date - anims.start, numSteps)
          });
        });
      }
    }, {
      key: "_refresh",
      value: function _refresh() {
        var _this = this;
        if (this._request) {
          return;
        }
        this._running = true;
        this._request = requestAnimFrame.call(window, function () {
          _this._update();
          _this._request = null;
          if (_this._running) {
            _this._refresh();
          }
        });
      }
    }, {
      key: "_update",
      value: function _update() {
        var _this2 = this;
        var date = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : Date.now();
        var remaining = 0;
        this._charts.forEach(function (anims, chart) {
          if (!anims.running || !anims.items.length) {
            return;
          }
          var items = anims.items;
          var i = items.length - 1;
          var draw = false;
          var item;
          for (; i >= 0; --i) {
            item = items[i];
            if (item._active) {
              if (item._total > anims.duration) {
                anims.duration = item._total;
              }
              item.tick(date);
              draw = true;
            } else {
              items[i] = items[items.length - 1];
              items.pop();
            }
          }
          if (draw) {
            chart.draw();
            _this2._notify(chart, anims, date, 'progress');
          }
          if (!items.length) {
            anims.running = false;
            _this2._notify(chart, anims, date, 'complete');
            anims.initial = false;
          }
          remaining += items.length;
        });
        this._lastDate = date;
        if (remaining === 0) {
          this._running = false;
        }
      }
    }, {
      key: "_getAnims",
      value: function _getAnims(chart) {
        var charts = this._charts;
        var anims = charts.get(chart);
        if (!anims) {
          anims = {
            running: false,
            initial: true,
            items: [],
            listeners: {
              complete: [],
              progress: []
            }
          };
          charts.set(chart, anims);
        }
        return anims;
      }
    }, {
      key: "listen",
      value: function listen(chart, event, cb) {
        this._getAnims(chart).listeners[event].push(cb);
      }
    }, {
      key: "add",
      value: function add(chart, items) {
        var _this$_getAnims$items;
        if (!items || !items.length) {
          return;
        }
        (_this$_getAnims$items = this._getAnims(chart).items).push.apply(_this$_getAnims$items, _toConsumableArray(items));
      }
    }, {
      key: "has",
      value: function has(chart) {
        return this._getAnims(chart).items.length > 0;
      }
    }, {
      key: "start",
      value: function start(chart) {
        var anims = this._charts.get(chart);
        if (!anims) {
          return;
        }
        anims.running = true;
        anims.start = Date.now();
        anims.duration = anims.items.reduce(function (acc, cur) {
          return Math.max(acc, cur._duration);
        }, 0);
        this._refresh();
      }
    }, {
      key: "running",
      value: function running(chart) {
        if (!this._running) {
          return false;
        }
        var anims = this._charts.get(chart);
        if (!anims || !anims.running || !anims.items.length) {
          return false;
        }
        return true;
      }
    }, {
      key: "stop",
      value: function stop(chart) {
        var anims = this._charts.get(chart);
        if (!anims || !anims.items.length) {
          return;
        }
        var items = anims.items;
        var i = items.length - 1;
        for (; i >= 0; --i) {
          items[i].cancel();
        }
        anims.items = [];
        this._notify(chart, anims, Date.now(), 'complete');
      }
    }, {
      key: "remove",
      value: function remove(chart) {
        return this._charts["delete"](chart);
      }
    }]);
  }();
  var animator = /* #__PURE__ */new Animator();
  var transparent = 'transparent';
  var interpolators = {
    "boolean": function boolean(from, to, factor) {
      return factor > 0.5 ? to : from;
    },
    color: function color$1(from, to, factor) {
      var c0 = color(from || transparent);
      var c1 = c0.valid && color(to || transparent);
      return c1 && c1.valid ? c1.mix(c0, factor).hexString() : to;
    },
    number: function number(from, to, factor) {
      return from + (to - from) * factor;
    }
  };
  var Animation = /*#__PURE__*/function () {
    function Animation(cfg, target, prop, to) {
      _classCallCheck$1(this, Animation);
      var currentValue = target[prop];
      to = resolve([cfg.to, to, currentValue, cfg.from]);
      var from = resolve([cfg.from, currentValue, to]);
      this._active = true;
      this._fn = cfg.fn || interpolators[cfg.type || _typeof$1(from)];
      this._easing = effects[cfg.easing] || effects.linear;
      this._start = Math.floor(Date.now() + (cfg.delay || 0));
      this._duration = this._total = Math.floor(cfg.duration);
      this._loop = !!cfg.loop;
      this._target = target;
      this._prop = prop;
      this._from = from;
      this._to = to;
      this._promises = undefined;
    }
    return _createClass$1(Animation, [{
      key: "active",
      value: function active() {
        return this._active;
      }
    }, {
      key: "update",
      value: function update(cfg, to, date) {
        if (this._active) {
          this._notify(false);
          var currentValue = this._target[this._prop];
          var elapsed = date - this._start;
          var remain = this._duration - elapsed;
          this._start = date;
          this._duration = Math.floor(Math.max(remain, cfg.duration));
          this._total += elapsed;
          this._loop = !!cfg.loop;
          this._to = resolve([cfg.to, to, currentValue, cfg.from]);
          this._from = resolve([cfg.from, currentValue, to]);
        }
      }
    }, {
      key: "cancel",
      value: function cancel() {
        if (this._active) {
          this.tick(Date.now());
          this._active = false;
          this._notify(false);
        }
      }
    }, {
      key: "tick",
      value: function tick(date) {
        var elapsed = date - this._start;
        var duration = this._duration;
        var prop = this._prop;
        var from = this._from;
        var loop = this._loop;
        var to = this._to;
        var factor;
        this._active = from !== to && (loop || elapsed < duration);
        if (!this._active) {
          this._target[prop] = to;
          this._notify(true);
          return;
        }
        if (elapsed < 0) {
          this._target[prop] = from;
          return;
        }
        factor = elapsed / duration % 2;
        factor = loop && factor > 1 ? 2 - factor : factor;
        factor = this._easing(Math.min(1, Math.max(0, factor)));
        this._target[prop] = this._fn(from, to, factor);
      }
    }, {
      key: "wait",
      value: function wait() {
        var promises = this._promises || (this._promises = []);
        return new Promise(function (res, rej) {
          promises.push({
            res: res,
            rej: rej
          });
        });
      }
    }, {
      key: "_notify",
      value: function _notify(resolved) {
        var method = resolved ? 'res' : 'rej';
        var promises = this._promises || [];
        for (var i = 0; i < promises.length; i++) {
          promises[i][method]();
        }
      }
    }]);
  }();
  var Animations = /*#__PURE__*/function () {
    function Animations(chart, config) {
      _classCallCheck$1(this, Animations);
      this._chart = chart;
      this._properties = new Map();
      this.configure(config);
    }
    return _createClass$1(Animations, [{
      key: "configure",
      value: function configure(config) {
        if (!isObject(config)) {
          return;
        }
        var animationOptions = Object.keys(defaults.animation);
        var animatedProps = this._properties;
        Object.getOwnPropertyNames(config).forEach(function (key) {
          var cfg = config[key];
          if (!isObject(cfg)) {
            return;
          }
          var resolved = {};
          for (var _i = 0, _animationOptions = animationOptions; _i < _animationOptions.length; _i++) {
            var option = _animationOptions[_i];
            resolved[option] = cfg[option];
          }
          (isArray(cfg.properties) && cfg.properties || [key]).forEach(function (prop) {
            if (prop === key || !animatedProps.has(prop)) {
              animatedProps.set(prop, resolved);
            }
          });
        });
      }
    }, {
      key: "_animateOptions",
      value: function _animateOptions(target, values) {
        var newOptions = values.options;
        var options = resolveTargetOptions(target, newOptions);
        if (!options) {
          return [];
        }
        var animations = this._createAnimations(options, newOptions);
        if (newOptions.$shared) {
          awaitAll(target.options.$animations, newOptions).then(function () {
            target.options = newOptions;
          }, function () {});
        }
        return animations;
      }
    }, {
      key: "_createAnimations",
      value: function _createAnimations(target, values) {
        var animatedProps = this._properties;
        var animations = [];
        var running = target.$animations || (target.$animations = {});
        var props = Object.keys(values);
        var date = Date.now();
        var i;
        for (i = props.length - 1; i >= 0; --i) {
          var prop = props[i];
          if (prop.charAt(0) === '$') {
            continue;
          }
          if (prop === 'options') {
            animations.push.apply(animations, _toConsumableArray(this._animateOptions(target, values)));
            continue;
          }
          var value = values[prop];
          var animation = running[prop];
          var cfg = animatedProps.get(prop);
          if (animation) {
            if (cfg && animation.active()) {
              animation.update(cfg, value, date);
              continue;
            } else {
              animation.cancel();
            }
          }
          if (!cfg || !cfg.duration) {
            target[prop] = value;
            continue;
          }
          running[prop] = animation = new Animation(cfg, target, prop, value);
          animations.push(animation);
        }
        return animations;
      }
    }, {
      key: "update",
      value: function update(target, values) {
        if (this._properties.size === 0) {
          Object.assign(target, values);
          return;
        }
        var animations = this._createAnimations(target, values);
        if (animations.length) {
          animator.add(this._chart, animations);
          return true;
        }
      }
    }]);
  }();
  function awaitAll(animations, properties) {
    var running = [];
    var keys = Object.keys(properties);
    for (var i = 0; i < keys.length; i++) {
      var anim = animations[keys[i]];
      if (anim && anim.active()) {
        running.push(anim.wait());
      }
    }
    return Promise.all(running);
  }
  function resolveTargetOptions(target, newOptions) {
    if (!newOptions) {
      return;
    }
    var options = target.options;
    if (!options) {
      target.options = newOptions;
      return;
    }
    if (options.$shared) {
      target.options = options = Object.assign({}, options, {
        $shared: false,
        $animations: {}
      });
    }
    return options;
  }
  function scaleClip(scale, allowedOverflow) {
    var opts = scale && scale.options || {};
    var reverse = opts.reverse;
    var min = opts.min === undefined ? allowedOverflow : 0;
    var max = opts.max === undefined ? allowedOverflow : 0;
    return {
      start: reverse ? max : min,
      end: reverse ? min : max
    };
  }
  function defaultClip(xScale, yScale, allowedOverflow) {
    if (allowedOverflow === false) {
      return false;
    }
    var x = scaleClip(xScale, allowedOverflow);
    var y = scaleClip(yScale, allowedOverflow);
    return {
      top: y.end,
      right: x.end,
      bottom: y.start,
      left: x.start
    };
  }
  function toClip(value) {
    var t, r, b, l;
    if (isObject(value)) {
      t = value.top;
      r = value.right;
      b = value.bottom;
      l = value.left;
    } else {
      t = r = b = l = value;
    }
    return {
      top: t,
      right: r,
      bottom: b,
      left: l,
      disabled: value === false
    };
  }
  function getSortedDatasetIndices(chart, filterVisible) {
    var keys = [];
    var metasets = chart._getSortedDatasetMetas(filterVisible);
    var i, ilen;
    for (i = 0, ilen = metasets.length; i < ilen; ++i) {
      keys.push(metasets[i].index);
    }
    return keys;
  }
  function _applyStack(stack, value, dsIndex) {
    var options = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};
    var keys = stack.keys;
    var singleMode = options.mode === 'single';
    var i, ilen, datasetIndex, otherValue;
    if (value === null) {
      return;
    }
    for (i = 0, ilen = keys.length; i < ilen; ++i) {
      datasetIndex = +keys[i];
      if (datasetIndex === dsIndex) {
        if (options.all) {
          continue;
        }
        break;
      }
      otherValue = stack.values[datasetIndex];
      if (isNumberFinite(otherValue) && (singleMode || value === 0 || sign(value) === sign(otherValue))) {
        value += otherValue;
      }
    }
    return value;
  }
  function convertObjectDataToArray(data, meta) {
    var iScale = meta.iScale,
      vScale = meta.vScale;
    var iAxisKey = iScale.axis === 'x' ? 'x' : 'y';
    var vAxisKey = vScale.axis === 'x' ? 'x' : 'y';
    var keys = Object.keys(data);
    var adata = new Array(keys.length);
    var i, ilen, key;
    for (i = 0, ilen = keys.length; i < ilen; ++i) {
      key = keys[i];
      adata[i] = _defineProperty$1(_defineProperty$1({}, iAxisKey, key), vAxisKey, data[key]);
    }
    return adata;
  }
  function isStacked(scale, meta) {
    var stacked = scale && scale.options.stacked;
    return stacked || stacked === undefined && meta.stack !== undefined;
  }
  function getStackKey(indexScale, valueScale, meta) {
    return "".concat(indexScale.id, ".").concat(valueScale.id, ".").concat(meta.stack || meta.type);
  }
  function getUserBounds(scale) {
    var _scale$getUserBounds = scale.getUserBounds(),
      min = _scale$getUserBounds.min,
      max = _scale$getUserBounds.max,
      minDefined = _scale$getUserBounds.minDefined,
      maxDefined = _scale$getUserBounds.maxDefined;
    return {
      min: minDefined ? min : Number.NEGATIVE_INFINITY,
      max: maxDefined ? max : Number.POSITIVE_INFINITY
    };
  }
  function getOrCreateStack(stacks, stackKey, indexValue) {
    var subStack = stacks[stackKey] || (stacks[stackKey] = {});
    return subStack[indexValue] || (subStack[indexValue] = {});
  }
  function getLastIndexInStack(stack, vScale, positive, type) {
    var _iterator = _createForOfIteratorHelper$1(vScale.getMatchingVisibleMetas(type).reverse()),
      _step;
    try {
      for (_iterator.s(); !(_step = _iterator.n()).done;) {
        var meta = _step.value;
        var value = stack[meta.index];
        if (positive && value > 0 || !positive && value < 0) {
          return meta.index;
        }
      }
    } catch (err) {
      _iterator.e(err);
    } finally {
      _iterator.f();
    }
    return null;
  }
  function updateStacks(controller, parsed) {
    var chart = controller.chart,
      meta = controller._cachedMeta;
    var stacks = chart._stacks || (chart._stacks = {});
    var iScale = meta.iScale,
      vScale = meta.vScale,
      datasetIndex = meta.index;
    var iAxis = iScale.axis;
    var vAxis = vScale.axis;
    var key = getStackKey(iScale, vScale, meta);
    var ilen = parsed.length;
    var stack;
    for (var i = 0; i < ilen; ++i) {
      var item = parsed[i];
      var _index = item[iAxis],
        value = item[vAxis];
      var itemStacks = item._stacks || (item._stacks = {});
      stack = itemStacks[vAxis] = getOrCreateStack(stacks, key, _index);
      stack[datasetIndex] = value;
      stack._top = getLastIndexInStack(stack, vScale, true, meta.type);
      stack._bottom = getLastIndexInStack(stack, vScale, false, meta.type);
      var visualValues = stack._visualValues || (stack._visualValues = {});
      visualValues[datasetIndex] = value;
    }
  }
  function getFirstScaleId(chart, axis) {
    var scales = chart.scales;
    return Object.keys(scales).filter(function (key) {
      return scales[key].axis === axis;
    }).shift();
  }
  function createDatasetContext(parent, index) {
    return createContext(parent, {
      active: false,
      dataset: undefined,
      datasetIndex: index,
      index: index,
      mode: 'default',
      type: 'dataset'
    });
  }
  function createDataContext(parent, index, element) {
    return createContext(parent, {
      active: false,
      dataIndex: index,
      parsed: undefined,
      raw: undefined,
      element: element,
      index: index,
      mode: 'default',
      type: 'data'
    });
  }
  function clearStacks(meta, items) {
    var datasetIndex = meta.controller.index;
    var axis = meta.vScale && meta.vScale.axis;
    if (!axis) {
      return;
    }
    items = items || meta._parsed;
    var _iterator2 = _createForOfIteratorHelper$1(items),
      _step2;
    try {
      for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
        var parsed = _step2.value;
        var stacks = parsed._stacks;
        if (!stacks || stacks[axis] === undefined || stacks[axis][datasetIndex] === undefined) {
          return;
        }
        delete stacks[axis][datasetIndex];
        if (stacks[axis]._visualValues !== undefined && stacks[axis]._visualValues[datasetIndex] !== undefined) {
          delete stacks[axis]._visualValues[datasetIndex];
        }
      }
    } catch (err) {
      _iterator2.e(err);
    } finally {
      _iterator2.f();
    }
  }
  var isDirectUpdateMode = function isDirectUpdateMode(mode) {
    return mode === 'reset' || mode === 'none';
  };
  var cloneIfNotShared = function cloneIfNotShared(cached, shared) {
    return shared ? cached : Object.assign({}, cached);
  };
  var createStack = function createStack(canStack, meta, chart) {
    return canStack && !meta.hidden && meta._stacked && {
      keys: getSortedDatasetIndices(chart, true),
      values: null
    };
  };
  var DatasetController = /*#__PURE__*/function () {
    function DatasetController(chart, datasetIndex) {
      _classCallCheck$1(this, DatasetController);
      this.chart = chart;
      this._ctx = chart.ctx;
      this.index = datasetIndex;
      this._cachedDataOpts = {};
      this._cachedMeta = this.getMeta();
      this._type = this._cachedMeta.type;
      this.options = undefined;
      this._parsing = false;
      this._data = undefined;
      this._objectData = undefined;
      this._sharedOptions = undefined;
      this._drawStart = undefined;
      this._drawCount = undefined;
      this.enableOptionSharing = false;
      this.supportsDecimation = false;
      this.$context = undefined;
      this._syncList = [];
      this.datasetElementType = (this instanceof DatasetController ? this.constructor : void 0).datasetElementType;
      this.dataElementType = (this instanceof DatasetController ? this.constructor : void 0).dataElementType;
      this.initialize();
    }
    return _createClass$1(DatasetController, [{
      key: "initialize",
      value: function initialize() {
        var meta = this._cachedMeta;
        this.configure();
        this.linkScales();
        meta._stacked = isStacked(meta.vScale, meta);
        this.addElements();
        if (this.options.fill && !this.chart.isPluginEnabled('filler')) {
          console.warn("Tried to use the 'fill' option without the 'Filler' plugin enabled. Please import and register the 'Filler' plugin and make sure it is not disabled in the options");
        }
      }
    }, {
      key: "updateIndex",
      value: function updateIndex(datasetIndex) {
        if (this.index !== datasetIndex) {
          clearStacks(this._cachedMeta);
        }
        this.index = datasetIndex;
      }
    }, {
      key: "linkScales",
      value: function linkScales() {
        var chart = this.chart;
        var meta = this._cachedMeta;
        var dataset = this.getDataset();
        var chooseId = function chooseId(axis, x, y, r) {
          return axis === 'x' ? x : axis === 'r' ? r : y;
        };
        var xid = meta.xAxisID = valueOrDefault(dataset.xAxisID, getFirstScaleId(chart, 'x'));
        var yid = meta.yAxisID = valueOrDefault(dataset.yAxisID, getFirstScaleId(chart, 'y'));
        var rid = meta.rAxisID = valueOrDefault(dataset.rAxisID, getFirstScaleId(chart, 'r'));
        var indexAxis = meta.indexAxis;
        var iid = meta.iAxisID = chooseId(indexAxis, xid, yid, rid);
        var vid = meta.vAxisID = chooseId(indexAxis, yid, xid, rid);
        meta.xScale = this.getScaleForId(xid);
        meta.yScale = this.getScaleForId(yid);
        meta.rScale = this.getScaleForId(rid);
        meta.iScale = this.getScaleForId(iid);
        meta.vScale = this.getScaleForId(vid);
      }
    }, {
      key: "getDataset",
      value: function getDataset() {
        return this.chart.data.datasets[this.index];
      }
    }, {
      key: "getMeta",
      value: function getMeta() {
        return this.chart.getDatasetMeta(this.index);
      }
    }, {
      key: "getScaleForId",
      value: function getScaleForId(scaleID) {
        return this.chart.scales[scaleID];
      }
    }, {
      key: "_getOtherScale",
      value: function _getOtherScale(scale) {
        var meta = this._cachedMeta;
        return scale === meta.iScale ? meta.vScale : meta.iScale;
      }
    }, {
      key: "reset",
      value: function reset() {
        this._update('reset');
      }
    }, {
      key: "_destroy",
      value: function _destroy() {
        var meta = this._cachedMeta;
        if (this._data) {
          unlistenArrayEvents(this._data, this);
        }
        if (meta._stacked) {
          clearStacks(meta);
        }
      }
    }, {
      key: "_dataCheck",
      value: function _dataCheck() {
        var dataset = this.getDataset();
        var data = dataset.data || (dataset.data = []);
        var _data = this._data;
        if (isObject(data)) {
          var meta = this._cachedMeta;
          this._data = convertObjectDataToArray(data, meta);
        } else if (_data !== data) {
          if (_data) {
            unlistenArrayEvents(_data, this);
            var _meta = this._cachedMeta;
            clearStacks(_meta);
            _meta._parsed = [];
          }
          if (data && Object.isExtensible(data)) {
            listenArrayEvents(data, this);
          }
          this._syncList = [];
          this._data = data;
        }
      }
    }, {
      key: "addElements",
      value: function addElements() {
        var meta = this._cachedMeta;
        this._dataCheck();
        if (this.datasetElementType) {
          meta.dataset = new this.datasetElementType();
        }
      }
    }, {
      key: "buildOrUpdateElements",
      value: function buildOrUpdateElements(resetNewElements) {
        var meta = this._cachedMeta;
        var dataset = this.getDataset();
        var stackChanged = false;
        this._dataCheck();
        var oldStacked = meta._stacked;
        meta._stacked = isStacked(meta.vScale, meta);
        if (meta.stack !== dataset.stack) {
          stackChanged = true;
          clearStacks(meta);
          meta.stack = dataset.stack;
        }
        this._resyncElements(resetNewElements);
        if (stackChanged || oldStacked !== meta._stacked) {
          updateStacks(this, meta._parsed);
        }
      }
    }, {
      key: "configure",
      value: function configure() {
        var config = this.chart.config;
        var scopeKeys = config.datasetScopeKeys(this._type);
        var scopes = config.getOptionScopes(this.getDataset(), scopeKeys, true);
        this.options = config.createResolver(scopes, this.getContext());
        this._parsing = this.options.parsing;
        this._cachedDataOpts = {};
      }
    }, {
      key: "parse",
      value: function parse(start, count) {
        var meta = this._cachedMeta,
          data = this._data;
        var iScale = meta.iScale,
          _stacked = meta._stacked;
        var iAxis = iScale.axis;
        var sorted = start === 0 && count === data.length ? true : meta._sorted;
        var prev = start > 0 && meta._parsed[start - 1];
        var i, cur, parsed;
        if (this._parsing === false) {
          meta._parsed = data;
          meta._sorted = true;
          parsed = data;
        } else {
          if (isArray(data[start])) {
            parsed = this.parseArrayData(meta, data, start, count);
          } else if (isObject(data[start])) {
            parsed = this.parseObjectData(meta, data, start, count);
          } else {
            parsed = this.parsePrimitiveData(meta, data, start, count);
          }
          var isNotInOrderComparedToPrev = function isNotInOrderComparedToPrev() {
            return cur[iAxis] === null || prev && cur[iAxis] < prev[iAxis];
          };
          for (i = 0; i < count; ++i) {
            meta._parsed[i + start] = cur = parsed[i];
            if (sorted) {
              if (isNotInOrderComparedToPrev()) {
                sorted = false;
              }
              prev = cur;
            }
          }
          meta._sorted = sorted;
        }
        if (_stacked) {
          updateStacks(this, parsed);
        }
      }
    }, {
      key: "parsePrimitiveData",
      value: function parsePrimitiveData(meta, data, start, count) {
        var iScale = meta.iScale,
          vScale = meta.vScale;
        var iAxis = iScale.axis;
        var vAxis = vScale.axis;
        var labels = iScale.getLabels();
        var singleScale = iScale === vScale;
        var parsed = new Array(count);
        var i, ilen, index;
        for (i = 0, ilen = count; i < ilen; ++i) {
          index = i + start;
          parsed[i] = _defineProperty$1(_defineProperty$1({}, iAxis, singleScale || iScale.parse(labels[index], index)), vAxis, vScale.parse(data[index], index));
        }
        return parsed;
      }
    }, {
      key: "parseArrayData",
      value: function parseArrayData(meta, data, start, count) {
        var xScale = meta.xScale,
          yScale = meta.yScale;
        var parsed = new Array(count);
        var i, ilen, index, item;
        for (i = 0, ilen = count; i < ilen; ++i) {
          index = i + start;
          item = data[index];
          parsed[i] = {
            x: xScale.parse(item[0], index),
            y: yScale.parse(item[1], index)
          };
        }
        return parsed;
      }
    }, {
      key: "parseObjectData",
      value: function parseObjectData(meta, data, start, count) {
        var xScale = meta.xScale,
          yScale = meta.yScale;
        var _this$_parsing = this._parsing,
          _this$_parsing$xAxisK = _this$_parsing.xAxisKey,
          xAxisKey = _this$_parsing$xAxisK === void 0 ? 'x' : _this$_parsing$xAxisK,
          _this$_parsing$yAxisK = _this$_parsing.yAxisKey,
          yAxisKey = _this$_parsing$yAxisK === void 0 ? 'y' : _this$_parsing$yAxisK;
        var parsed = new Array(count);
        var i, ilen, index, item;
        for (i = 0, ilen = count; i < ilen; ++i) {
          index = i + start;
          item = data[index];
          parsed[i] = {
            x: xScale.parse(resolveObjectKey(item, xAxisKey), index),
            y: yScale.parse(resolveObjectKey(item, yAxisKey), index)
          };
        }
        return parsed;
      }
    }, {
      key: "getParsed",
      value: function getParsed(index) {
        return this._cachedMeta._parsed[index];
      }
    }, {
      key: "getDataElement",
      value: function getDataElement(index) {
        return this._cachedMeta.data[index];
      }
    }, {
      key: "applyStack",
      value: function applyStack(scale, parsed, mode) {
        var chart = this.chart;
        var meta = this._cachedMeta;
        var value = parsed[scale.axis];
        var stack = {
          keys: getSortedDatasetIndices(chart, true),
          values: parsed._stacks[scale.axis]._visualValues
        };
        return _applyStack(stack, value, meta.index, {
          mode: mode
        });
      }
    }, {
      key: "updateRangeFromParsed",
      value: function updateRangeFromParsed(range, scale, parsed, stack) {
        var parsedValue = parsed[scale.axis];
        var value = parsedValue === null ? NaN : parsedValue;
        var values = stack && parsed._stacks[scale.axis];
        if (stack && values) {
          stack.values = values;
          value = _applyStack(stack, parsedValue, this._cachedMeta.index);
        }
        range.min = Math.min(range.min, value);
        range.max = Math.max(range.max, value);
      }
    }, {
      key: "getMinMax",
      value: function getMinMax(scale, canStack) {
        var meta = this._cachedMeta;
        var _parsed = meta._parsed;
        var sorted = meta._sorted && scale === meta.iScale;
        var ilen = _parsed.length;
        var otherScale = this._getOtherScale(scale);
        var stack = createStack(canStack, meta, this.chart);
        var range = {
          min: Number.POSITIVE_INFINITY,
          max: Number.NEGATIVE_INFINITY
        };
        var _getUserBounds = getUserBounds(otherScale),
          otherMin = _getUserBounds.min,
          otherMax = _getUserBounds.max;
        var i, parsed;
        function _skip() {
          parsed = _parsed[i];
          var otherValue = parsed[otherScale.axis];
          return !isNumberFinite(parsed[scale.axis]) || otherMin > otherValue || otherMax < otherValue;
        }
        for (i = 0; i < ilen; ++i) {
          if (_skip()) {
            continue;
          }
          this.updateRangeFromParsed(range, scale, parsed, stack);
          if (sorted) {
            break;
          }
        }
        if (sorted) {
          for (i = ilen - 1; i >= 0; --i) {
            if (_skip()) {
              continue;
            }
            this.updateRangeFromParsed(range, scale, parsed, stack);
            break;
          }
        }
        return range;
      }
    }, {
      key: "getAllParsedValues",
      value: function getAllParsedValues(scale) {
        var parsed = this._cachedMeta._parsed;
        var values = [];
        var i, ilen, value;
        for (i = 0, ilen = parsed.length; i < ilen; ++i) {
          value = parsed[i][scale.axis];
          if (isNumberFinite(value)) {
            values.push(value);
          }
        }
        return values;
      }
    }, {
      key: "getMaxOverflow",
      value: function getMaxOverflow() {
        return false;
      }
    }, {
      key: "getLabelAndValue",
      value: function getLabelAndValue(index) {
        var meta = this._cachedMeta;
        var iScale = meta.iScale;
        var vScale = meta.vScale;
        var parsed = this.getParsed(index);
        return {
          label: iScale ? '' + iScale.getLabelForValue(parsed[iScale.axis]) : '',
          value: vScale ? '' + vScale.getLabelForValue(parsed[vScale.axis]) : ''
        };
      }
    }, {
      key: "_update",
      value: function _update(mode) {
        var meta = this._cachedMeta;
        this.update(mode || 'default');
        meta._clip = toClip(valueOrDefault(this.options.clip, defaultClip(meta.xScale, meta.yScale, this.getMaxOverflow())));
      }
    }, {
      key: "update",
      value: function update(mode) {}
    }, {
      key: "draw",
      value: function draw() {
        var ctx = this._ctx;
        var chart = this.chart;
        var meta = this._cachedMeta;
        var elements = meta.data || [];
        var area = chart.chartArea;
        var active = [];
        var start = this._drawStart || 0;
        var count = this._drawCount || elements.length - start;
        var drawActiveElementsOnTop = this.options.drawActiveElementsOnTop;
        var i;
        if (meta.dataset) {
          meta.dataset.draw(ctx, area, start, count);
        }
        for (i = start; i < start + count; ++i) {
          var element = elements[i];
          if (element.hidden) {
            continue;
          }
          if (element.active && drawActiveElementsOnTop) {
            active.push(element);
          } else {
            element.draw(ctx, area);
          }
        }
        for (i = 0; i < active.length; ++i) {
          active[i].draw(ctx, area);
        }
      }
    }, {
      key: "getStyle",
      value: function getStyle(index, active) {
        var mode = active ? 'active' : 'default';
        return index === undefined && this._cachedMeta.dataset ? this.resolveDatasetElementOptions(mode) : this.resolveDataElementOptions(index || 0, mode);
      }
    }, {
      key: "getContext",
      value: function getContext(index, active, mode) {
        var dataset = this.getDataset();
        var context;
        if (index >= 0 && index < this._cachedMeta.data.length) {
          var element = this._cachedMeta.data[index];
          context = element.$context || (element.$context = createDataContext(this.getContext(), index, element));
          context.parsed = this.getParsed(index);
          context.raw = dataset.data[index];
          context.index = context.dataIndex = index;
        } else {
          context = this.$context || (this.$context = createDatasetContext(this.chart.getContext(), this.index));
          context.dataset = dataset;
          context.index = context.datasetIndex = this.index;
        }
        context.active = !!active;
        context.mode = mode;
        return context;
      }
    }, {
      key: "resolveDatasetElementOptions",
      value: function resolveDatasetElementOptions(mode) {
        return this._resolveElementOptions(this.datasetElementType.id, mode);
      }
    }, {
      key: "resolveDataElementOptions",
      value: function resolveDataElementOptions(index, mode) {
        return this._resolveElementOptions(this.dataElementType.id, mode, index);
      }
    }, {
      key: "_resolveElementOptions",
      value: function _resolveElementOptions(elementType) {
        var _this3 = this;
        var mode = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'default';
        var index = arguments.length > 2 ? arguments[2] : undefined;
        var active = mode === 'active';
        var cache = this._cachedDataOpts;
        var cacheKey = elementType + '-' + mode;
        var cached = cache[cacheKey];
        var sharing = this.enableOptionSharing && defined(index);
        if (cached) {
          return cloneIfNotShared(cached, sharing);
        }
        var config = this.chart.config;
        var scopeKeys = config.datasetElementScopeKeys(this._type, elementType);
        var prefixes = active ? ["".concat(elementType, "Hover"), 'hover', elementType, ''] : [elementType, ''];
        var scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
        var names = Object.keys(defaults.elements[elementType]);
        var context = function context() {
          return _this3.getContext(index, active, mode);
        };
        var values = config.resolveNamedOptions(scopes, names, context, prefixes);
        if (values.$shared) {
          values.$shared = sharing;
          cache[cacheKey] = Object.freeze(cloneIfNotShared(values, sharing));
        }
        return values;
      }
    }, {
      key: "_resolveAnimations",
      value: function _resolveAnimations(index, transition, active) {
        var chart = this.chart;
        var cache = this._cachedDataOpts;
        var cacheKey = "animation-".concat(transition);
        var cached = cache[cacheKey];
        if (cached) {
          return cached;
        }
        var options;
        if (chart.options.animation !== false) {
          var config = this.chart.config;
          var scopeKeys = config.datasetAnimationScopeKeys(this._type, transition);
          var scopes = config.getOptionScopes(this.getDataset(), scopeKeys);
          options = config.createResolver(scopes, this.getContext(index, active, transition));
        }
        var animations = new Animations(chart, options && options.animations);
        if (options && options._cacheable) {
          cache[cacheKey] = Object.freeze(animations);
        }
        return animations;
      }
    }, {
      key: "getSharedOptions",
      value: function getSharedOptions(options) {
        if (!options.$shared) {
          return;
        }
        return this._sharedOptions || (this._sharedOptions = Object.assign({}, options));
      }
    }, {
      key: "includeOptions",
      value: function includeOptions(mode, sharedOptions) {
        return !sharedOptions || isDirectUpdateMode(mode) || this.chart._animationsDisabled;
      }
    }, {
      key: "_getSharedOptions",
      value: function _getSharedOptions(start, mode) {
        var firstOpts = this.resolveDataElementOptions(start, mode);
        var previouslySharedOptions = this._sharedOptions;
        var sharedOptions = this.getSharedOptions(firstOpts);
        var includeOptions = this.includeOptions(mode, sharedOptions) || sharedOptions !== previouslySharedOptions;
        this.updateSharedOptions(sharedOptions, mode, firstOpts);
        return {
          sharedOptions: sharedOptions,
          includeOptions: includeOptions
        };
      }
    }, {
      key: "updateElement",
      value: function updateElement(element, index, properties, mode) {
        if (isDirectUpdateMode(mode)) {
          Object.assign(element, properties);
        } else {
          this._resolveAnimations(index, mode).update(element, properties);
        }
      }
    }, {
      key: "updateSharedOptions",
      value: function updateSharedOptions(sharedOptions, mode, newOptions) {
        if (sharedOptions && !isDirectUpdateMode(mode)) {
          this._resolveAnimations(undefined, mode).update(sharedOptions, newOptions);
        }
      }
    }, {
      key: "_setStyle",
      value: function _setStyle(element, index, mode, active) {
        element.active = active;
        var options = this.getStyle(index, active);
        this._resolveAnimations(index, mode, active).update(element, {
          options: !active && this.getSharedOptions(options) || options
        });
      }
    }, {
      key: "removeHoverStyle",
      value: function removeHoverStyle(element, datasetIndex, index) {
        this._setStyle(element, index, 'active', false);
      }
    }, {
      key: "setHoverStyle",
      value: function setHoverStyle(element, datasetIndex, index) {
        this._setStyle(element, index, 'active', true);
      }
    }, {
      key: "_removeDatasetHoverStyle",
      value: function _removeDatasetHoverStyle() {
        var element = this._cachedMeta.dataset;
        if (element) {
          this._setStyle(element, undefined, 'active', false);
        }
      }
    }, {
      key: "_setDatasetHoverStyle",
      value: function _setDatasetHoverStyle() {
        var element = this._cachedMeta.dataset;
        if (element) {
          this._setStyle(element, undefined, 'active', true);
        }
      }
    }, {
      key: "_resyncElements",
      value: function _resyncElements(resetNewElements) {
        var data = this._data;
        var elements = this._cachedMeta.data;
        var _iterator3 = _createForOfIteratorHelper$1(this._syncList),
          _step3;
        try {
          for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
            var _step3$value = _slicedToArray(_step3.value, 3),
              method = _step3$value[0],
              arg1 = _step3$value[1],
              arg2 = _step3$value[2];
            this[method](arg1, arg2);
          }
        } catch (err) {
          _iterator3.e(err);
        } finally {
          _iterator3.f();
        }
        this._syncList = [];
        var numMeta = elements.length;
        var numData = data.length;
        var count = Math.min(numData, numMeta);
        if (count) {
          this.parse(0, count);
        }
        if (numData > numMeta) {
          this._insertElements(numMeta, numData - numMeta, resetNewElements);
        } else if (numData < numMeta) {
          this._removeElements(numData, numMeta - numData);
        }
      }
    }, {
      key: "_insertElements",
      value: function _insertElements(start, count) {
        var resetNewElements = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
        var meta = this._cachedMeta;
        var data = meta.data;
        var end = start + count;
        var i;
        var move = function move(arr) {
          arr.length += count;
          for (i = arr.length - 1; i >= end; i--) {
            arr[i] = arr[i - count];
          }
        };
        move(data);
        for (i = start; i < end; ++i) {
          data[i] = new this.dataElementType();
        }
        if (this._parsing) {
          move(meta._parsed);
        }
        this.parse(start, count);
        if (resetNewElements) {
          this.updateElements(data, start, count, 'reset');
        }
      }
    }, {
      key: "updateElements",
      value: function updateElements(element, start, count, mode) {}
    }, {
      key: "_removeElements",
      value: function _removeElements(start, count) {
        var meta = this._cachedMeta;
        if (this._parsing) {
          var removed = meta._parsed.splice(start, count);
          if (meta._stacked) {
            clearStacks(meta, removed);
          }
        }
        meta.data.splice(start, count);
      }
    }, {
      key: "_sync",
      value: function _sync(args) {
        if (this._parsing) {
          this._syncList.push(args);
        } else {
          var _args2 = _slicedToArray(args, 3),
            method = _args2[0],
            arg1 = _args2[1],
            arg2 = _args2[2];
          this[method](arg1, arg2);
        }
        this.chart._dataChanges.push([this.index].concat(_toConsumableArray(args)));
      }
    }, {
      key: "_onDataPush",
      value: function _onDataPush() {
        var count = arguments.length;
        this._sync(['_insertElements', this.getDataset().data.length - count, count]);
      }
    }, {
      key: "_onDataPop",
      value: function _onDataPop() {
        this._sync(['_removeElements', this._cachedMeta.data.length - 1, 1]);
      }
    }, {
      key: "_onDataShift",
      value: function _onDataShift() {
        this._sync(['_removeElements', 0, 1]);
      }
    }, {
      key: "_onDataSplice",
      value: function _onDataSplice(start, count) {
        if (count) {
          this._sync(['_removeElements', start, count]);
        }
        var newCount = arguments.length - 2;
        if (newCount) {
          this._sync(['_insertElements', start, newCount]);
        }
      }
    }, {
      key: "_onDataUnshift",
      value: function _onDataUnshift() {
        this._sync(['_insertElements', 0, arguments.length]);
      }
    }]);
  }();
  _defineProperty$1(DatasetController, "defaults", {});
  _defineProperty$1(DatasetController, "datasetElementType", null);
  _defineProperty$1(DatasetController, "dataElementType", null);
  function getAllScaleValues(scale, type) {
    if (!scale._cache.$bar) {
      var visibleMetas = scale.getMatchingVisibleMetas(type);
      var values = [];
      for (var i = 0, ilen = visibleMetas.length; i < ilen; i++) {
        values = values.concat(visibleMetas[i].controller.getAllParsedValues(scale));
      }
      scale._cache.$bar = _arrayUnique(values.sort(function (a, b) {
        return a - b;
      }));
    }
    return scale._cache.$bar;
  }
  function computeMinSampleSize(meta) {
    var scale = meta.iScale;
    var values = getAllScaleValues(scale, meta.type);
    var min = scale._length;
    var i, ilen, curr, prev;
    var updateMinAndPrev = function updateMinAndPrev() {
      if (curr === 32767 || curr === -32768) {
        return;
      }
      if (defined(prev)) {
        min = Math.min(min, Math.abs(curr - prev) || min);
      }
      prev = curr;
    };
    for (i = 0, ilen = values.length; i < ilen; ++i) {
      curr = scale.getPixelForValue(values[i]);
      updateMinAndPrev();
    }
    prev = undefined;
    for (i = 0, ilen = scale.ticks.length; i < ilen; ++i) {
      curr = scale.getPixelForTick(i);
      updateMinAndPrev();
    }
    return min;
  }
  function computeFitCategoryTraits(index, ruler, options, stackCount) {
    var thickness = options.barThickness;
    var size, ratio;
    if (isNullOrUndef(thickness)) {
      size = ruler.min * options.categoryPercentage;
      ratio = options.barPercentage;
    } else {
      size = thickness * stackCount;
      ratio = 1;
    }
    return {
      chunk: size / stackCount,
      ratio: ratio,
      start: ruler.pixels[index] - size / 2
    };
  }
  function computeFlexCategoryTraits(index, ruler, options, stackCount) {
    var pixels = ruler.pixels;
    var curr = pixels[index];
    var prev = index > 0 ? pixels[index - 1] : null;
    var next = index < pixels.length - 1 ? pixels[index + 1] : null;
    var percent = options.categoryPercentage;
    if (prev === null) {
      prev = curr - (next === null ? ruler.end - ruler.start : next - curr);
    }
    if (next === null) {
      next = curr + curr - prev;
    }
    var start = curr - (curr - Math.min(prev, next)) / 2 * percent;
    var size = Math.abs(next - prev) / 2 * percent;
    return {
      chunk: size / stackCount,
      ratio: options.barPercentage,
      start: start
    };
  }
  function parseFloatBar(entry, item, vScale, i) {
    var startValue = vScale.parse(entry[0], i);
    var endValue = vScale.parse(entry[1], i);
    var min = Math.min(startValue, endValue);
    var max = Math.max(startValue, endValue);
    var barStart = min;
    var barEnd = max;
    if (Math.abs(min) > Math.abs(max)) {
      barStart = max;
      barEnd = min;
    }
    item[vScale.axis] = barEnd;
    item._custom = {
      barStart: barStart,
      barEnd: barEnd,
      start: startValue,
      end: endValue,
      min: min,
      max: max
    };
  }
  function parseValue(entry, item, vScale, i) {
    if (isArray(entry)) {
      parseFloatBar(entry, item, vScale, i);
    } else {
      item[vScale.axis] = vScale.parse(entry, i);
    }
    return item;
  }
  function parseArrayOrPrimitive(meta, data, start, count) {
    var iScale = meta.iScale;
    var vScale = meta.vScale;
    var labels = iScale.getLabels();
    var singleScale = iScale === vScale;
    var parsed = [];
    var i, ilen, item, entry;
    for (i = start, ilen = start + count; i < ilen; ++i) {
      entry = data[i];
      item = {};
      item[iScale.axis] = singleScale || iScale.parse(labels[i], i);
      parsed.push(parseValue(entry, item, vScale, i));
    }
    return parsed;
  }
  function isFloatBar(custom) {
    return custom && custom.barStart !== undefined && custom.barEnd !== undefined;
  }
  function barSign(size, vScale, actualBase) {
    if (size !== 0) {
      return sign(size);
    }
    return (vScale.isHorizontal() ? 1 : -1) * (vScale.min >= actualBase ? 1 : -1);
  }
  function borderProps(properties) {
    var reverse, start, end, top, bottom;
    if (properties.horizontal) {
      reverse = properties.base > properties.x;
      start = 'left';
      end = 'right';
    } else {
      reverse = properties.base < properties.y;
      start = 'bottom';
      end = 'top';
    }
    if (reverse) {
      top = 'end';
      bottom = 'start';
    } else {
      top = 'start';
      bottom = 'end';
    }
    return {
      start: start,
      end: end,
      reverse: reverse,
      top: top,
      bottom: bottom
    };
  }
  function setBorderSkipped(properties, options, stack, index) {
    var edge = options.borderSkipped;
    var res = {};
    if (!edge) {
      properties.borderSkipped = res;
      return;
    }
    if (edge === true) {
      properties.borderSkipped = {
        top: true,
        right: true,
        bottom: true,
        left: true
      };
      return;
    }
    var _borderProps = borderProps(properties),
      start = _borderProps.start,
      end = _borderProps.end,
      reverse = _borderProps.reverse,
      top = _borderProps.top,
      bottom = _borderProps.bottom;
    if (edge === 'middle' && stack) {
      properties.enableBorderRadius = true;
      if ((stack._top || 0) === index) {
        edge = top;
      } else if ((stack._bottom || 0) === index) {
        edge = bottom;
      } else {
        res[parseEdge(bottom, start, end, reverse)] = true;
        edge = top;
      }
    }
    res[parseEdge(edge, start, end, reverse)] = true;
    properties.borderSkipped = res;
  }
  function parseEdge(edge, a, b, reverse) {
    if (reverse) {
      edge = swap(edge, a, b);
      edge = startEnd(edge, b, a);
    } else {
      edge = startEnd(edge, a, b);
    }
    return edge;
  }
  function swap(orig, v1, v2) {
    return orig === v1 ? v2 : orig === v2 ? v1 : orig;
  }
  function startEnd(v, start, end) {
    return v === 'start' ? start : v === 'end' ? end : v;
  }
  function setInflateAmount(properties, _ref, ratio) {
    var inflateAmount = _ref.inflateAmount;
    properties.inflateAmount = inflateAmount === 'auto' ? ratio === 1 ? 0.33 : 0 : inflateAmount;
  }
  var BarController = /*#__PURE__*/function (_DatasetController2) {
    function BarController() {
      _classCallCheck$1(this, BarController);
      return _callSuper(this, BarController, arguments);
    }
    _inherits$1(BarController, _DatasetController2);
    return _createClass$1(BarController, [{
      key: "parsePrimitiveData",
      value: function parsePrimitiveData(meta, data, start, count) {
        return parseArrayOrPrimitive(meta, data, start, count);
      }
    }, {
      key: "parseArrayData",
      value: function parseArrayData(meta, data, start, count) {
        return parseArrayOrPrimitive(meta, data, start, count);
      }
    }, {
      key: "parseObjectData",
      value: function parseObjectData(meta, data, start, count) {
        var iScale = meta.iScale,
          vScale = meta.vScale;
        var _this$_parsing2 = this._parsing,
          _this$_parsing2$xAxis = _this$_parsing2.xAxisKey,
          xAxisKey = _this$_parsing2$xAxis === void 0 ? 'x' : _this$_parsing2$xAxis,
          _this$_parsing2$yAxis = _this$_parsing2.yAxisKey,
          yAxisKey = _this$_parsing2$yAxis === void 0 ? 'y' : _this$_parsing2$yAxis;
        var iAxisKey = iScale.axis === 'x' ? xAxisKey : yAxisKey;
        var vAxisKey = vScale.axis === 'x' ? xAxisKey : yAxisKey;
        var parsed = [];
        var i, ilen, item, obj;
        for (i = start, ilen = start + count; i < ilen; ++i) {
          obj = data[i];
          item = {};
          item[iScale.axis] = iScale.parse(resolveObjectKey(obj, iAxisKey), i);
          parsed.push(parseValue(resolveObjectKey(obj, vAxisKey), item, vScale, i));
        }
        return parsed;
      }
    }, {
      key: "updateRangeFromParsed",
      value: function updateRangeFromParsed(range, scale, parsed, stack) {
        _get(_getPrototypeOf$1(BarController.prototype), "updateRangeFromParsed", this).call(this, range, scale, parsed, stack);
        var custom = parsed._custom;
        if (custom && scale === this._cachedMeta.vScale) {
          range.min = Math.min(range.min, custom.min);
          range.max = Math.max(range.max, custom.max);
        }
      }
    }, {
      key: "getMaxOverflow",
      value: function getMaxOverflow() {
        return 0;
      }
    }, {
      key: "getLabelAndValue",
      value: function getLabelAndValue(index) {
        var meta = this._cachedMeta;
        var iScale = meta.iScale,
          vScale = meta.vScale;
        var parsed = this.getParsed(index);
        var custom = parsed._custom;
        var value = isFloatBar(custom) ? '[' + custom.start + ', ' + custom.end + ']' : '' + vScale.getLabelForValue(parsed[vScale.axis]);
        return {
          label: '' + iScale.getLabelForValue(parsed[iScale.axis]),
          value: value
        };
      }
    }, {
      key: "initialize",
      value: function initialize() {
        this.enableOptionSharing = true;
        _get(_getPrototypeOf$1(BarController.prototype), "initialize", this).call(this);
        var meta = this._cachedMeta;
        meta.stack = this.getDataset().stack;
      }
    }, {
      key: "update",
      value: function update(mode) {
        var meta = this._cachedMeta;
        this.updateElements(meta.data, 0, meta.data.length, mode);
      }
    }, {
      key: "updateElements",
      value: function updateElements(bars, start, count, mode) {
        var reset = mode === 'reset';
        var index = this.index,
          vScale = this._cachedMeta.vScale;
        var base = vScale.getBasePixel();
        var horizontal = vScale.isHorizontal();
        var ruler = this._getRuler();
        var _this$_getSharedOptio = this._getSharedOptions(start, mode),
          sharedOptions = _this$_getSharedOptio.sharedOptions,
          includeOptions = _this$_getSharedOptio.includeOptions;
        for (var i = start; i < start + count; i++) {
          var parsed = this.getParsed(i);
          var vpixels = reset || isNullOrUndef(parsed[vScale.axis]) ? {
            base: base,
            head: base
          } : this._calculateBarValuePixels(i);
          var ipixels = this._calculateBarIndexPixels(i, ruler);
          var stack = (parsed._stacks || {})[vScale.axis];
          var properties = {
            horizontal: horizontal,
            base: vpixels.base,
            enableBorderRadius: !stack || isFloatBar(parsed._custom) || index === stack._top || index === stack._bottom,
            x: horizontal ? vpixels.head : ipixels.center,
            y: horizontal ? ipixels.center : vpixels.head,
            height: horizontal ? ipixels.size : Math.abs(vpixels.size),
            width: horizontal ? Math.abs(vpixels.size) : ipixels.size
          };
          if (includeOptions) {
            properties.options = sharedOptions || this.resolveDataElementOptions(i, bars[i].active ? 'active' : mode);
          }
          var options = properties.options || bars[i].options;
          setBorderSkipped(properties, options, stack, index);
          setInflateAmount(properties, options, ruler.ratio);
          this.updateElement(bars[i], i, properties, mode);
        }
      }
    }, {
      key: "_getStacks",
      value: function _getStacks(last, dataIndex) {
        var iScale = this._cachedMeta.iScale;
        var metasets = iScale.getMatchingVisibleMetas(this._type).filter(function (meta) {
          return meta.controller.options.grouped;
        });
        var stacked = iScale.options.stacked;
        var stacks = [];
        var skipNull = function skipNull(meta) {
          var parsed = meta.controller.getParsed(dataIndex);
          var val = parsed && parsed[meta.vScale.axis];
          if (isNullOrUndef(val) || isNaN(val)) {
            return true;
          }
        };
        var _iterator4 = _createForOfIteratorHelper$1(metasets),
          _step4;
        try {
          for (_iterator4.s(); !(_step4 = _iterator4.n()).done;) {
            var meta = _step4.value;
            if (dataIndex !== undefined && skipNull(meta)) {
              continue;
            }
            if (stacked === false || stacks.indexOf(meta.stack) === -1 || stacked === undefined && meta.stack === undefined) {
              stacks.push(meta.stack);
            }
            if (meta.index === last) {
              break;
            }
          }
        } catch (err) {
          _iterator4.e(err);
        } finally {
          _iterator4.f();
        }
        if (!stacks.length) {
          stacks.push(undefined);
        }
        return stacks;
      }
    }, {
      key: "_getStackCount",
      value: function _getStackCount(index) {
        return this._getStacks(undefined, index).length;
      }
    }, {
      key: "_getStackIndex",
      value: function _getStackIndex(datasetIndex, name, dataIndex) {
        var stacks = this._getStacks(datasetIndex, dataIndex);
        var index = name !== undefined ? stacks.indexOf(name) : -1;
        return index === -1 ? stacks.length - 1 : index;
      }
    }, {
      key: "_getRuler",
      value: function _getRuler() {
        var opts = this.options;
        var meta = this._cachedMeta;
        var iScale = meta.iScale;
        var pixels = [];
        var i, ilen;
        for (i = 0, ilen = meta.data.length; i < ilen; ++i) {
          pixels.push(iScale.getPixelForValue(this.getParsed(i)[iScale.axis], i));
        }
        var barThickness = opts.barThickness;
        var min = barThickness || computeMinSampleSize(meta);
        return {
          min: min,
          pixels: pixels,
          start: iScale._startPixel,
          end: iScale._endPixel,
          stackCount: this._getStackCount(),
          scale: iScale,
          grouped: opts.grouped,
          ratio: barThickness ? 1 : opts.categoryPercentage * opts.barPercentage
        };
      }
    }, {
      key: "_calculateBarValuePixels",
      value: function _calculateBarValuePixels(index) {
        var _this$_cachedMeta = this._cachedMeta,
          vScale = _this$_cachedMeta.vScale,
          _stacked = _this$_cachedMeta._stacked,
          datasetIndex = _this$_cachedMeta.index,
          _this$options = this.options,
          baseValue = _this$options.base,
          minBarLength = _this$options.minBarLength;
        var actualBase = baseValue || 0;
        var parsed = this.getParsed(index);
        var custom = parsed._custom;
        var floating = isFloatBar(custom);
        var value = parsed[vScale.axis];
        var start = 0;
        var length = _stacked ? this.applyStack(vScale, parsed, _stacked) : value;
        var head, size;
        if (length !== value) {
          start = length - value;
          length = value;
        }
        if (floating) {
          value = custom.barStart;
          length = custom.barEnd - custom.barStart;
          if (value !== 0 && sign(value) !== sign(custom.barEnd)) {
            start = 0;
          }
          start += value;
        }
        var startValue = !isNullOrUndef(baseValue) && !floating ? baseValue : start;
        var base = vScale.getPixelForValue(startValue);
        if (this.chart.getDataVisibility(index)) {
          head = vScale.getPixelForValue(start + length);
        } else {
          head = base;
        }
        size = head - base;
        if (Math.abs(size) < minBarLength) {
          size = barSign(size, vScale, actualBase) * minBarLength;
          if (value === actualBase) {
            base -= size / 2;
          }
          var startPixel = vScale.getPixelForDecimal(0);
          var endPixel = vScale.getPixelForDecimal(1);
          var min = Math.min(startPixel, endPixel);
          var max = Math.max(startPixel, endPixel);
          base = Math.max(Math.min(base, max), min);
          head = base + size;
          if (_stacked && !floating) {
            parsed._stacks[vScale.axis]._visualValues[datasetIndex] = vScale.getValueForPixel(head) - vScale.getValueForPixel(base);
          }
        }
        if (base === vScale.getPixelForValue(actualBase)) {
          var halfGrid = sign(size) * vScale.getLineWidthForValue(actualBase) / 2;
          base += halfGrid;
          size -= halfGrid;
        }
        return {
          size: size,
          base: base,
          head: head,
          center: head + size / 2
        };
      }
    }, {
      key: "_calculateBarIndexPixels",
      value: function _calculateBarIndexPixels(index, ruler) {
        var scale = ruler.scale;
        var options = this.options;
        var skipNull = options.skipNull;
        var maxBarThickness = valueOrDefault(options.maxBarThickness, Infinity);
        var center, size;
        if (ruler.grouped) {
          var stackCount = skipNull ? this._getStackCount(index) : ruler.stackCount;
          var range = options.barThickness === 'flex' ? computeFlexCategoryTraits(index, ruler, options, stackCount) : computeFitCategoryTraits(index, ruler, options, stackCount);
          var stackIndex = this._getStackIndex(this.index, this._cachedMeta.stack, skipNull ? index : undefined);
          center = range.start + range.chunk * stackIndex + range.chunk / 2;
          size = Math.min(maxBarThickness, range.chunk * range.ratio);
        } else {
          center = scale.getPixelForValue(this.getParsed(index)[scale.axis], index);
          size = Math.min(maxBarThickness, ruler.min * ruler.ratio);
        }
        return {
          base: center - size / 2,
          head: center + size / 2,
          center: center,
          size: size
        };
      }
    }, {
      key: "draw",
      value: function draw() {
        var meta = this._cachedMeta;
        var vScale = meta.vScale;
        var rects = meta.data;
        var ilen = rects.length;
        var i = 0;
        for (; i < ilen; ++i) {
          if (this.getParsed(i)[vScale.axis] !== null && !rects[i].hidden) {
            rects[i].draw(this._ctx);
          }
        }
      }
    }]);
  }(DatasetController);
  _defineProperty$1(BarController, "id", 'bar');
  _defineProperty$1(BarController, "defaults", {
    datasetElementType: false,
    dataElementType: 'bar',
    categoryPercentage: 0.8,
    barPercentage: 0.9,
    grouped: true,
    animations: {
      numbers: {
        type: 'number',
        properties: ['x', 'y', 'base', 'width', 'height']
      }
    }
  });
  _defineProperty$1(BarController, "overrides", {
    scales: {
      _index_: {
        type: 'category',
        offset: true,
        grid: {
          offset: true
        }
      },
      _value_: {
        type: 'linear',
        beginAtZero: true
      }
    }
  });
  var BubbleController = /*#__PURE__*/function (_DatasetController3) {
    function BubbleController() {
      _classCallCheck$1(this, BubbleController);
      return _callSuper(this, BubbleController, arguments);
    }
    _inherits$1(BubbleController, _DatasetController3);
    return _createClass$1(BubbleController, [{
      key: "initialize",
      value: function initialize() {
        this.enableOptionSharing = true;
        _get(_getPrototypeOf$1(BubbleController.prototype), "initialize", this).call(this);
      }
    }, {
      key: "parsePrimitiveData",
      value: function parsePrimitiveData(meta, data, start, count) {
        var parsed = _get(_getPrototypeOf$1(BubbleController.prototype), "parsePrimitiveData", this).call(this, meta, data, start, count);
        for (var i = 0; i < parsed.length; i++) {
          parsed[i]._custom = this.resolveDataElementOptions(i + start).radius;
        }
        return parsed;
      }
    }, {
      key: "parseArrayData",
      value: function parseArrayData(meta, data, start, count) {
        var parsed = _get(_getPrototypeOf$1(BubbleController.prototype), "parseArrayData", this).call(this, meta, data, start, count);
        for (var i = 0; i < parsed.length; i++) {
          var item = data[start + i];
          parsed[i]._custom = valueOrDefault(item[2], this.resolveDataElementOptions(i + start).radius);
        }
        return parsed;
      }
    }, {
      key: "parseObjectData",
      value: function parseObjectData(meta, data, start, count) {
        var parsed = _get(_getPrototypeOf$1(BubbleController.prototype), "parseObjectData", this).call(this, meta, data, start, count);
        for (var i = 0; i < parsed.length; i++) {
          var item = data[start + i];
          parsed[i]._custom = valueOrDefault(item && item.r && +item.r, this.resolveDataElementOptions(i + start).radius);
        }
        return parsed;
      }
    }, {
      key: "getMaxOverflow",
      value: function getMaxOverflow() {
        var data = this._cachedMeta.data;
        var max = 0;
        for (var i = data.length - 1; i >= 0; --i) {
          max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
        }
        return max > 0 && max;
      }
    }, {
      key: "getLabelAndValue",
      value: function getLabelAndValue(index) {
        var meta = this._cachedMeta;
        var labels = this.chart.data.labels || [];
        var xScale = meta.xScale,
          yScale = meta.yScale;
        var parsed = this.getParsed(index);
        var x = xScale.getLabelForValue(parsed.x);
        var y = yScale.getLabelForValue(parsed.y);
        var r = parsed._custom;
        return {
          label: labels[index] || '',
          value: '(' + x + ', ' + y + (r ? ', ' + r : '') + ')'
        };
      }
    }, {
      key: "update",
      value: function update(mode) {
        var points = this._cachedMeta.data;
        this.updateElements(points, 0, points.length, mode);
      }
    }, {
      key: "updateElements",
      value: function updateElements(points, start, count, mode) {
        var reset = mode === 'reset';
        var _this$_cachedMeta2 = this._cachedMeta,
          iScale = _this$_cachedMeta2.iScale,
          vScale = _this$_cachedMeta2.vScale;
        var _this$_getSharedOptio2 = this._getSharedOptions(start, mode),
          sharedOptions = _this$_getSharedOptio2.sharedOptions,
          includeOptions = _this$_getSharedOptio2.includeOptions;
        var iAxis = iScale.axis;
        var vAxis = vScale.axis;
        for (var i = start; i < start + count; i++) {
          var point = points[i];
          var parsed = !reset && this.getParsed(i);
          var properties = {};
          var iPixel = properties[iAxis] = reset ? iScale.getPixelForDecimal(0.5) : iScale.getPixelForValue(parsed[iAxis]);
          var vPixel = properties[vAxis] = reset ? vScale.getBasePixel() : vScale.getPixelForValue(parsed[vAxis]);
          properties.skip = isNaN(iPixel) || isNaN(vPixel);
          if (includeOptions) {
            properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
            if (reset) {
              properties.options.radius = 0;
            }
          }
          this.updateElement(point, i, properties, mode);
        }
      }
    }, {
      key: "resolveDataElementOptions",
      value: function resolveDataElementOptions(index, mode) {
        var parsed = this.getParsed(index);
        var values = _get(_getPrototypeOf$1(BubbleController.prototype), "resolveDataElementOptions", this).call(this, index, mode);
        if (values.$shared) {
          values = Object.assign({}, values, {
            $shared: false
          });
        }
        var radius = values.radius;
        if (mode !== 'active') {
          values.radius = 0;
        }
        values.radius += valueOrDefault(parsed && parsed._custom, radius);
        return values;
      }
    }]);
  }(DatasetController);
  _defineProperty$1(BubbleController, "id", 'bubble');
  _defineProperty$1(BubbleController, "defaults", {
    datasetElementType: false,
    dataElementType: 'point',
    animations: {
      numbers: {
        type: 'number',
        properties: ['x', 'y', 'borderWidth', 'radius']
      }
    }
  });
  _defineProperty$1(BubbleController, "overrides", {
    scales: {
      x: {
        type: 'linear'
      },
      y: {
        type: 'linear'
      }
    }
  });
  function getRatioAndOffset(rotation, circumference, cutout) {
    var ratioX = 1;
    var ratioY = 1;
    var offsetX = 0;
    var offsetY = 0;
    if (circumference < TAU) {
      var startAngle = rotation;
      var endAngle = startAngle + circumference;
      var startX = Math.cos(startAngle);
      var startY = Math.sin(startAngle);
      var endX = Math.cos(endAngle);
      var endY = Math.sin(endAngle);
      var calcMax = function calcMax(angle, a, b) {
        return _angleBetween(angle, startAngle, endAngle, true) ? 1 : Math.max(a, a * cutout, b, b * cutout);
      };
      var calcMin = function calcMin(angle, a, b) {
        return _angleBetween(angle, startAngle, endAngle, true) ? -1 : Math.min(a, a * cutout, b, b * cutout);
      };
      var maxX = calcMax(0, startX, endX);
      var maxY = calcMax(HALF_PI, startY, endY);
      var minX = calcMin(PI, startX, endX);
      var minY = calcMin(PI + HALF_PI, startY, endY);
      ratioX = (maxX - minX) / 2;
      ratioY = (maxY - minY) / 2;
      offsetX = -(maxX + minX) / 2;
      offsetY = -(maxY + minY) / 2;
    }
    return {
      ratioX: ratioX,
      ratioY: ratioY,
      offsetX: offsetX,
      offsetY: offsetY
    };
  }
  var DoughnutController = /*#__PURE__*/function (_DatasetController4) {
    function DoughnutController(chart, datasetIndex) {
      var _this4;
      _classCallCheck$1(this, DoughnutController);
      _this4 = _callSuper(this, DoughnutController, [chart, datasetIndex]);
      _this4.enableOptionSharing = true;
      _this4.innerRadius = undefined;
      _this4.outerRadius = undefined;
      _this4.offsetX = undefined;
      _this4.offsetY = undefined;
      return _this4;
    }
    _inherits$1(DoughnutController, _DatasetController4);
    return _createClass$1(DoughnutController, [{
      key: "linkScales",
      value: function linkScales() {}
    }, {
      key: "parse",
      value: function parse(start, count) {
        var data = this.getDataset().data;
        var meta = this._cachedMeta;
        if (this._parsing === false) {
          meta._parsed = data;
        } else {
          var getter = function getter(i) {
            return +data[i];
          };
          if (isObject(data[start])) {
            var _this$_parsing$key = this._parsing.key,
              key = _this$_parsing$key === void 0 ? 'value' : _this$_parsing$key;
            getter = function getter(i) {
              return +resolveObjectKey(data[i], key);
            };
          }
          var i, ilen;
          for (i = start, ilen = start + count; i < ilen; ++i) {
            meta._parsed[i] = getter(i);
          }
        }
      }
    }, {
      key: "_getRotation",
      value: function _getRotation() {
        return toRadians(this.options.rotation - 90);
      }
    }, {
      key: "_getCircumference",
      value: function _getCircumference() {
        return toRadians(this.options.circumference);
      }
    }, {
      key: "_getRotationExtents",
      value: function _getRotationExtents() {
        var min = TAU;
        var max = -TAU;
        for (var i = 0; i < this.chart.data.datasets.length; ++i) {
          if (this.chart.isDatasetVisible(i) && this.chart.getDatasetMeta(i).type === this._type) {
            var controller = this.chart.getDatasetMeta(i).controller;
            var rotation = controller._getRotation();
            var circumference = controller._getCircumference();
            min = Math.min(min, rotation);
            max = Math.max(max, rotation + circumference);
          }
        }
        return {
          rotation: min,
          circumference: max - min
        };
      }
    }, {
      key: "update",
      value: function update(mode) {
        var chart = this.chart;
        var chartArea = chart.chartArea;
        var meta = this._cachedMeta;
        var arcs = meta.data;
        var spacing = this.getMaxBorderWidth() + this.getMaxOffset(arcs) + this.options.spacing;
        var maxSize = Math.max((Math.min(chartArea.width, chartArea.height) - spacing) / 2, 0);
        var cutout = Math.min(toPercentage(this.options.cutout, maxSize), 1);
        var chartWeight = this._getRingWeight(this.index);
        var _this$_getRotationExt = this._getRotationExtents(),
          circumference = _this$_getRotationExt.circumference,
          rotation = _this$_getRotationExt.rotation;
        var _getRatioAndOffset = getRatioAndOffset(rotation, circumference, cutout),
          ratioX = _getRatioAndOffset.ratioX,
          ratioY = _getRatioAndOffset.ratioY,
          offsetX = _getRatioAndOffset.offsetX,
          offsetY = _getRatioAndOffset.offsetY;
        var maxWidth = (chartArea.width - spacing) / ratioX;
        var maxHeight = (chartArea.height - spacing) / ratioY;
        var maxRadius = Math.max(Math.min(maxWidth, maxHeight) / 2, 0);
        var outerRadius = toDimension(this.options.radius, maxRadius);
        var innerRadius = Math.max(outerRadius * cutout, 0);
        var radiusLength = (outerRadius - innerRadius) / this._getVisibleDatasetWeightTotal();
        this.offsetX = offsetX * outerRadius;
        this.offsetY = offsetY * outerRadius;
        meta.total = this.calculateTotal();
        this.outerRadius = outerRadius - radiusLength * this._getRingWeightOffset(this.index);
        this.innerRadius = Math.max(this.outerRadius - radiusLength * chartWeight, 0);
        this.updateElements(arcs, 0, arcs.length, mode);
      }
    }, {
      key: "_circumference",
      value: function _circumference(i, reset) {
        var opts = this.options;
        var meta = this._cachedMeta;
        var circumference = this._getCircumference();
        if (reset && opts.animation.animateRotate || !this.chart.getDataVisibility(i) || meta._parsed[i] === null || meta.data[i].hidden) {
          return 0;
        }
        return this.calculateCircumference(meta._parsed[i] * circumference / TAU);
      }
    }, {
      key: "updateElements",
      value: function updateElements(arcs, start, count, mode) {
        var reset = mode === 'reset';
        var chart = this.chart;
        var chartArea = chart.chartArea;
        var opts = chart.options;
        var animationOpts = opts.animation;
        var centerX = (chartArea.left + chartArea.right) / 2;
        var centerY = (chartArea.top + chartArea.bottom) / 2;
        var animateScale = reset && animationOpts.animateScale;
        var innerRadius = animateScale ? 0 : this.innerRadius;
        var outerRadius = animateScale ? 0 : this.outerRadius;
        var _this$_getSharedOptio3 = this._getSharedOptions(start, mode),
          sharedOptions = _this$_getSharedOptio3.sharedOptions,
          includeOptions = _this$_getSharedOptio3.includeOptions;
        var startAngle = this._getRotation();
        var i;
        for (i = 0; i < start; ++i) {
          startAngle += this._circumference(i, reset);
        }
        for (i = start; i < start + count; ++i) {
          var circumference = this._circumference(i, reset);
          var arc = arcs[i];
          var properties = {
            x: centerX + this.offsetX,
            y: centerY + this.offsetY,
            startAngle: startAngle,
            endAngle: startAngle + circumference,
            circumference: circumference,
            outerRadius: outerRadius,
            innerRadius: innerRadius
          };
          if (includeOptions) {
            properties.options = sharedOptions || this.resolveDataElementOptions(i, arc.active ? 'active' : mode);
          }
          startAngle += circumference;
          this.updateElement(arc, i, properties, mode);
        }
      }
    }, {
      key: "calculateTotal",
      value: function calculateTotal() {
        var meta = this._cachedMeta;
        var metaData = meta.data;
        var total = 0;
        var i;
        for (i = 0; i < metaData.length; i++) {
          var value = meta._parsed[i];
          if (value !== null && !isNaN(value) && this.chart.getDataVisibility(i) && !metaData[i].hidden) {
            total += Math.abs(value);
          }
        }
        return total;
      }
    }, {
      key: "calculateCircumference",
      value: function calculateCircumference(value) {
        var total = this._cachedMeta.total;
        if (total > 0 && !isNaN(value)) {
          return TAU * (Math.abs(value) / total);
        }
        return 0;
      }
    }, {
      key: "getLabelAndValue",
      value: function getLabelAndValue(index) {
        var meta = this._cachedMeta;
        var chart = this.chart;
        var labels = chart.data.labels || [];
        var value = formatNumber(meta._parsed[index], chart.options.locale);
        return {
          label: labels[index] || '',
          value: value
        };
      }
    }, {
      key: "getMaxBorderWidth",
      value: function getMaxBorderWidth(arcs) {
        var max = 0;
        var chart = this.chart;
        var i, ilen, meta, controller, options;
        if (!arcs) {
          for (i = 0, ilen = chart.data.datasets.length; i < ilen; ++i) {
            if (chart.isDatasetVisible(i)) {
              meta = chart.getDatasetMeta(i);
              arcs = meta.data;
              controller = meta.controller;
              break;
            }
          }
        }
        if (!arcs) {
          return 0;
        }
        for (i = 0, ilen = arcs.length; i < ilen; ++i) {
          options = controller.resolveDataElementOptions(i);
          if (options.borderAlign !== 'inner') {
            max = Math.max(max, options.borderWidth || 0, options.hoverBorderWidth || 0);
          }
        }
        return max;
      }
    }, {
      key: "getMaxOffset",
      value: function getMaxOffset(arcs) {
        var max = 0;
        for (var i = 0, ilen = arcs.length; i < ilen; ++i) {
          var options = this.resolveDataElementOptions(i);
          max = Math.max(max, options.offset || 0, options.hoverOffset || 0);
        }
        return max;
      }
    }, {
      key: "_getRingWeightOffset",
      value: function _getRingWeightOffset(datasetIndex) {
        var ringWeightOffset = 0;
        for (var i = 0; i < datasetIndex; ++i) {
          if (this.chart.isDatasetVisible(i)) {
            ringWeightOffset += this._getRingWeight(i);
          }
        }
        return ringWeightOffset;
      }
    }, {
      key: "_getRingWeight",
      value: function _getRingWeight(datasetIndex) {
        return Math.max(valueOrDefault(this.chart.data.datasets[datasetIndex].weight, 1), 0);
      }
    }, {
      key: "_getVisibleDatasetWeightTotal",
      value: function _getVisibleDatasetWeightTotal() {
        return this._getRingWeightOffset(this.chart.data.datasets.length) || 1;
      }
    }]);
  }(DatasetController);
  _defineProperty$1(DoughnutController, "id", 'doughnut');
  _defineProperty$1(DoughnutController, "defaults", {
    datasetElementType: false,
    dataElementType: 'arc',
    animation: {
      animateRotate: true,
      animateScale: false
    },
    animations: {
      numbers: {
        type: 'number',
        properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y', 'offset', 'borderWidth', 'spacing']
      }
    },
    cutout: '50%',
    rotation: 0,
    circumference: 360,
    radius: '100%',
    spacing: 0,
    indexAxis: 'r'
  });
  _defineProperty$1(DoughnutController, "descriptors", {
    _scriptable: function _scriptable(name) {
      return name !== 'spacing';
    },
    _indexable: function _indexable(name) {
      return name !== 'spacing' && !name.startsWith('borderDash') && !name.startsWith('hoverBorderDash');
    }
  });
  _defineProperty$1(DoughnutController, "overrides", {
    aspectRatio: 1,
    plugins: {
      legend: {
        labels: {
          generateLabels: function generateLabels(chart) {
            var data = chart.data;
            if (data.labels.length && data.datasets.length) {
              var _chart$legend$options2 = chart.legend.options.labels,
                pointStyle = _chart$legend$options2.pointStyle,
                color = _chart$legend$options2.color;
              return data.labels.map(function (label, i) {
                var meta = chart.getDatasetMeta(0);
                var style = meta.controller.getStyle(i);
                return {
                  text: label,
                  fillStyle: style.backgroundColor,
                  strokeStyle: style.borderColor,
                  fontColor: color,
                  lineWidth: style.borderWidth,
                  pointStyle: pointStyle,
                  hidden: !chart.getDataVisibility(i),
                  index: i
                };
              });
            }
            return [];
          }
        },
        onClick: function onClick(e, legendItem, legend) {
          legend.chart.toggleDataVisibility(legendItem.index);
          legend.chart.update();
        }
      }
    }
  });
  var LineController = /*#__PURE__*/function (_DatasetController5) {
    function LineController() {
      _classCallCheck$1(this, LineController);
      return _callSuper(this, LineController, arguments);
    }
    _inherits$1(LineController, _DatasetController5);
    return _createClass$1(LineController, [{
      key: "initialize",
      value: function initialize() {
        this.enableOptionSharing = true;
        this.supportsDecimation = true;
        _get(_getPrototypeOf$1(LineController.prototype), "initialize", this).call(this);
      }
    }, {
      key: "update",
      value: function update(mode) {
        var meta = this._cachedMeta;
        var line = meta.dataset,
          _meta$data = meta.data,
          points = _meta$data === void 0 ? [] : _meta$data,
          _dataset = meta._dataset;
        var animationsDisabled = this.chart._animationsDisabled;
        var _getStartAndCountOfVi = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled),
          start = _getStartAndCountOfVi.start,
          count = _getStartAndCountOfVi.count;
        this._drawStart = start;
        this._drawCount = count;
        if (_scaleRangesChanged(meta)) {
          start = 0;
          count = points.length;
        }
        line._chart = this.chart;
        line._datasetIndex = this.index;
        line._decimated = !!_dataset._decimated;
        line.points = points;
        var options = this.resolveDatasetElementOptions(mode);
        if (!this.options.showLine) {
          options.borderWidth = 0;
        }
        options.segment = this.options.segment;
        this.updateElement(line, undefined, {
          animated: !animationsDisabled,
          options: options
        }, mode);
        this.updateElements(points, start, count, mode);
      }
    }, {
      key: "updateElements",
      value: function updateElements(points, start, count, mode) {
        var reset = mode === 'reset';
        var _this$_cachedMeta3 = this._cachedMeta,
          iScale = _this$_cachedMeta3.iScale,
          vScale = _this$_cachedMeta3.vScale,
          _stacked = _this$_cachedMeta3._stacked,
          _dataset = _this$_cachedMeta3._dataset;
        var _this$_getSharedOptio4 = this._getSharedOptions(start, mode),
          sharedOptions = _this$_getSharedOptio4.sharedOptions,
          includeOptions = _this$_getSharedOptio4.includeOptions;
        var iAxis = iScale.axis;
        var vAxis = vScale.axis;
        var _this$options2 = this.options,
          spanGaps = _this$options2.spanGaps,
          segment = _this$options2.segment;
        var maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
        var directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
        var end = start + count;
        var pointsCount = points.length;
        var prevParsed = start > 0 && this.getParsed(start - 1);
        for (var i = 0; i < pointsCount; ++i) {
          var point = points[i];
          var properties = directUpdate ? point : {};
          if (i < start || i >= end) {
            properties.skip = true;
            continue;
          }
          var parsed = this.getParsed(i);
          var nullData = isNullOrUndef(parsed[vAxis]);
          var iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
          var vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
          properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
          properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
          if (segment) {
            properties.parsed = parsed;
            properties.raw = _dataset.data[i];
          }
          if (includeOptions) {
            properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
          }
          if (!directUpdate) {
            this.updateElement(point, i, properties, mode);
          }
          prevParsed = parsed;
        }
      }
    }, {
      key: "getMaxOverflow",
      value: function getMaxOverflow() {
        var meta = this._cachedMeta;
        var dataset = meta.dataset;
        var border = dataset.options && dataset.options.borderWidth || 0;
        var data = meta.data || [];
        if (!data.length) {
          return border;
        }
        var firstPoint = data[0].size(this.resolveDataElementOptions(0));
        var lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
        return Math.max(border, firstPoint, lastPoint) / 2;
      }
    }, {
      key: "draw",
      value: function draw() {
        var meta = this._cachedMeta;
        meta.dataset.updateControlPoints(this.chart.chartArea, meta.iScale.axis);
        _get(_getPrototypeOf$1(LineController.prototype), "draw", this).call(this);
      }
    }]);
  }(DatasetController);
  _defineProperty$1(LineController, "id", 'line');
  _defineProperty$1(LineController, "defaults", {
    datasetElementType: 'line',
    dataElementType: 'point',
    showLine: true,
    spanGaps: false
  });
  _defineProperty$1(LineController, "overrides", {
    scales: {
      _index_: {
        type: 'category'
      },
      _value_: {
        type: 'linear'
      }
    }
  });
  var PolarAreaController = /*#__PURE__*/function (_DatasetController6) {
    function PolarAreaController(chart, datasetIndex) {
      var _this5;
      _classCallCheck$1(this, PolarAreaController);
      _this5 = _callSuper(this, PolarAreaController, [chart, datasetIndex]);
      _this5.innerRadius = undefined;
      _this5.outerRadius = undefined;
      return _this5;
    }
    _inherits$1(PolarAreaController, _DatasetController6);
    return _createClass$1(PolarAreaController, [{
      key: "getLabelAndValue",
      value: function getLabelAndValue(index) {
        var meta = this._cachedMeta;
        var chart = this.chart;
        var labels = chart.data.labels || [];
        var value = formatNumber(meta._parsed[index].r, chart.options.locale);
        return {
          label: labels[index] || '',
          value: value
        };
      }
    }, {
      key: "parseObjectData",
      value: function parseObjectData(meta, data, start, count) {
        return _parseObjectDataRadialScale.bind(this)(meta, data, start, count);
      }
    }, {
      key: "update",
      value: function update(mode) {
        var arcs = this._cachedMeta.data;
        this._updateRadius();
        this.updateElements(arcs, 0, arcs.length, mode);
      }
    }, {
      key: "getMinMax",
      value: function getMinMax() {
        var _this6 = this;
        var meta = this._cachedMeta;
        var range = {
          min: Number.POSITIVE_INFINITY,
          max: Number.NEGATIVE_INFINITY
        };
        meta.data.forEach(function (element, index) {
          var parsed = _this6.getParsed(index).r;
          if (!isNaN(parsed) && _this6.chart.getDataVisibility(index)) {
            if (parsed < range.min) {
              range.min = parsed;
            }
            if (parsed > range.max) {
              range.max = parsed;
            }
          }
        });
        return range;
      }
    }, {
      key: "_updateRadius",
      value: function _updateRadius() {
        var chart = this.chart;
        var chartArea = chart.chartArea;
        var opts = chart.options;
        var minSize = Math.min(chartArea.right - chartArea.left, chartArea.bottom - chartArea.top);
        var outerRadius = Math.max(minSize / 2, 0);
        var innerRadius = Math.max(opts.cutoutPercentage ? outerRadius / 100 * opts.cutoutPercentage : 1, 0);
        var radiusLength = (outerRadius - innerRadius) / chart.getVisibleDatasetCount();
        this.outerRadius = outerRadius - radiusLength * this.index;
        this.innerRadius = this.outerRadius - radiusLength;
      }
    }, {
      key: "updateElements",
      value: function updateElements(arcs, start, count, mode) {
        var reset = mode === 'reset';
        var chart = this.chart;
        var opts = chart.options;
        var animationOpts = opts.animation;
        var scale = this._cachedMeta.rScale;
        var centerX = scale.xCenter;
        var centerY = scale.yCenter;
        var datasetStartAngle = scale.getIndexAngle(0) - 0.5 * PI;
        var angle = datasetStartAngle;
        var i;
        var defaultAngle = 360 / this.countVisibleElements();
        for (i = 0; i < start; ++i) {
          angle += this._computeAngle(i, mode, defaultAngle);
        }
        for (i = start; i < start + count; i++) {
          var arc = arcs[i];
          var startAngle = angle;
          var endAngle = angle + this._computeAngle(i, mode, defaultAngle);
          var outerRadius = chart.getDataVisibility(i) ? scale.getDistanceFromCenterForValue(this.getParsed(i).r) : 0;
          angle = endAngle;
          if (reset) {
            if (animationOpts.animateScale) {
              outerRadius = 0;
            }
            if (animationOpts.animateRotate) {
              startAngle = endAngle = datasetStartAngle;
            }
          }
          var properties = {
            x: centerX,
            y: centerY,
            innerRadius: 0,
            outerRadius: outerRadius,
            startAngle: startAngle,
            endAngle: endAngle,
            options: this.resolveDataElementOptions(i, arc.active ? 'active' : mode)
          };
          this.updateElement(arc, i, properties, mode);
        }
      }
    }, {
      key: "countVisibleElements",
      value: function countVisibleElements() {
        var _this7 = this;
        var meta = this._cachedMeta;
        var count = 0;
        meta.data.forEach(function (element, index) {
          if (!isNaN(_this7.getParsed(index).r) && _this7.chart.getDataVisibility(index)) {
            count++;
          }
        });
        return count;
      }
    }, {
      key: "_computeAngle",
      value: function _computeAngle(index, mode, defaultAngle) {
        return this.chart.getDataVisibility(index) ? toRadians(this.resolveDataElementOptions(index, mode).angle || defaultAngle) : 0;
      }
    }]);
  }(DatasetController);
  _defineProperty$1(PolarAreaController, "id", 'polarArea');
  _defineProperty$1(PolarAreaController, "defaults", {
    dataElementType: 'arc',
    animation: {
      animateRotate: true,
      animateScale: true
    },
    animations: {
      numbers: {
        type: 'number',
        properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius']
      }
    },
    indexAxis: 'r',
    startAngle: 0
  });
  _defineProperty$1(PolarAreaController, "overrides", {
    aspectRatio: 1,
    plugins: {
      legend: {
        labels: {
          generateLabels: function generateLabels(chart) {
            var data = chart.data;
            if (data.labels.length && data.datasets.length) {
              var _chart$legend$options3 = chart.legend.options.labels,
                pointStyle = _chart$legend$options3.pointStyle,
                color = _chart$legend$options3.color;
              return data.labels.map(function (label, i) {
                var meta = chart.getDatasetMeta(0);
                var style = meta.controller.getStyle(i);
                return {
                  text: label,
                  fillStyle: style.backgroundColor,
                  strokeStyle: style.borderColor,
                  fontColor: color,
                  lineWidth: style.borderWidth,
                  pointStyle: pointStyle,
                  hidden: !chart.getDataVisibility(i),
                  index: i
                };
              });
            }
            return [];
          }
        },
        onClick: function onClick(e, legendItem, legend) {
          legend.chart.toggleDataVisibility(legendItem.index);
          legend.chart.update();
        }
      }
    },
    scales: {
      r: {
        type: 'radialLinear',
        angleLines: {
          display: false
        },
        beginAtZero: true,
        grid: {
          circular: true
        },
        pointLabels: {
          display: false
        },
        startAngle: 0
      }
    }
  });
  var PieController = /*#__PURE__*/function (_DoughnutController2) {
    function PieController() {
      _classCallCheck$1(this, PieController);
      return _callSuper(this, PieController, arguments);
    }
    _inherits$1(PieController, _DoughnutController2);
    return _createClass$1(PieController);
  }(DoughnutController);
  _defineProperty$1(PieController, "id", 'pie');
  _defineProperty$1(PieController, "defaults", {
    cutout: 0,
    rotation: 0,
    circumference: 360,
    radius: '100%'
  });
  var RadarController = /*#__PURE__*/function (_DatasetController7) {
    function RadarController() {
      _classCallCheck$1(this, RadarController);
      return _callSuper(this, RadarController, arguments);
    }
    _inherits$1(RadarController, _DatasetController7);
    return _createClass$1(RadarController, [{
      key: "getLabelAndValue",
      value: function getLabelAndValue(index) {
        var vScale = this._cachedMeta.vScale;
        var parsed = this.getParsed(index);
        return {
          label: vScale.getLabels()[index],
          value: '' + vScale.getLabelForValue(parsed[vScale.axis])
        };
      }
    }, {
      key: "parseObjectData",
      value: function parseObjectData(meta, data, start, count) {
        return _parseObjectDataRadialScale.bind(this)(meta, data, start, count);
      }
    }, {
      key: "update",
      value: function update(mode) {
        var meta = this._cachedMeta;
        var line = meta.dataset;
        var points = meta.data || [];
        var labels = meta.iScale.getLabels();
        line.points = points;
        if (mode !== 'resize') {
          var options = this.resolveDatasetElementOptions(mode);
          if (!this.options.showLine) {
            options.borderWidth = 0;
          }
          var properties = {
            _loop: true,
            _fullLoop: labels.length === points.length,
            options: options
          };
          this.updateElement(line, undefined, properties, mode);
        }
        this.updateElements(points, 0, points.length, mode);
      }
    }, {
      key: "updateElements",
      value: function updateElements(points, start, count, mode) {
        var scale = this._cachedMeta.rScale;
        var reset = mode === 'reset';
        for (var i = start; i < start + count; i++) {
          var point = points[i];
          var options = this.resolveDataElementOptions(i, point.active ? 'active' : mode);
          var pointPosition = scale.getPointPositionForValue(i, this.getParsed(i).r);
          var x = reset ? scale.xCenter : pointPosition.x;
          var y = reset ? scale.yCenter : pointPosition.y;
          var properties = {
            x: x,
            y: y,
            angle: pointPosition.angle,
            skip: isNaN(x) || isNaN(y),
            options: options
          };
          this.updateElement(point, i, properties, mode);
        }
      }
    }]);
  }(DatasetController);
  _defineProperty$1(RadarController, "id", 'radar');
  _defineProperty$1(RadarController, "defaults", {
    datasetElementType: 'line',
    dataElementType: 'point',
    indexAxis: 'r',
    showLine: true,
    elements: {
      line: {
        fill: 'start'
      }
    }
  });
  _defineProperty$1(RadarController, "overrides", {
    aspectRatio: 1,
    scales: {
      r: {
        type: 'radialLinear'
      }
    }
  });
  var ScatterController = /*#__PURE__*/function (_DatasetController8) {
    function ScatterController() {
      _classCallCheck$1(this, ScatterController);
      return _callSuper(this, ScatterController, arguments);
    }
    _inherits$1(ScatterController, _DatasetController8);
    return _createClass$1(ScatterController, [{
      key: "getLabelAndValue",
      value: function getLabelAndValue(index) {
        var meta = this._cachedMeta;
        var labels = this.chart.data.labels || [];
        var xScale = meta.xScale,
          yScale = meta.yScale;
        var parsed = this.getParsed(index);
        var x = xScale.getLabelForValue(parsed.x);
        var y = yScale.getLabelForValue(parsed.y);
        return {
          label: labels[index] || '',
          value: '(' + x + ', ' + y + ')'
        };
      }
    }, {
      key: "update",
      value: function update(mode) {
        var meta = this._cachedMeta;
        var _meta$data2 = meta.data,
          points = _meta$data2 === void 0 ? [] : _meta$data2;
        var animationsDisabled = this.chart._animationsDisabled;
        var _getStartAndCountOfVi2 = _getStartAndCountOfVisiblePoints(meta, points, animationsDisabled),
          start = _getStartAndCountOfVi2.start,
          count = _getStartAndCountOfVi2.count;
        this._drawStart = start;
        this._drawCount = count;
        if (_scaleRangesChanged(meta)) {
          start = 0;
          count = points.length;
        }
        if (this.options.showLine) {
          if (!this.datasetElementType) {
            this.addElements();
          }
          var line = meta.dataset,
            _dataset = meta._dataset;
          line._chart = this.chart;
          line._datasetIndex = this.index;
          line._decimated = !!_dataset._decimated;
          line.points = points;
          var options = this.resolveDatasetElementOptions(mode);
          options.segment = this.options.segment;
          this.updateElement(line, undefined, {
            animated: !animationsDisabled,
            options: options
          }, mode);
        } else if (this.datasetElementType) {
          delete meta.dataset;
          this.datasetElementType = false;
        }
        this.updateElements(points, start, count, mode);
      }
    }, {
      key: "addElements",
      value: function addElements() {
        var showLine = this.options.showLine;
        if (!this.datasetElementType && showLine) {
          this.datasetElementType = this.chart.registry.getElement('line');
        }
        _get(_getPrototypeOf$1(ScatterController.prototype), "addElements", this).call(this);
      }
    }, {
      key: "updateElements",
      value: function updateElements(points, start, count, mode) {
        var reset = mode === 'reset';
        var _this$_cachedMeta4 = this._cachedMeta,
          iScale = _this$_cachedMeta4.iScale,
          vScale = _this$_cachedMeta4.vScale,
          _stacked = _this$_cachedMeta4._stacked,
          _dataset = _this$_cachedMeta4._dataset;
        var firstOpts = this.resolveDataElementOptions(start, mode);
        var sharedOptions = this.getSharedOptions(firstOpts);
        var includeOptions = this.includeOptions(mode, sharedOptions);
        var iAxis = iScale.axis;
        var vAxis = vScale.axis;
        var _this$options3 = this.options,
          spanGaps = _this$options3.spanGaps,
          segment = _this$options3.segment;
        var maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
        var directUpdate = this.chart._animationsDisabled || reset || mode === 'none';
        var prevParsed = start > 0 && this.getParsed(start - 1);
        for (var i = start; i < start + count; ++i) {
          var point = points[i];
          var parsed = this.getParsed(i);
          var properties = directUpdate ? point : {};
          var nullData = isNullOrUndef(parsed[vAxis]);
          var iPixel = properties[iAxis] = iScale.getPixelForValue(parsed[iAxis], i);
          var vPixel = properties[vAxis] = reset || nullData ? vScale.getBasePixel() : vScale.getPixelForValue(_stacked ? this.applyStack(vScale, parsed, _stacked) : parsed[vAxis], i);
          properties.skip = isNaN(iPixel) || isNaN(vPixel) || nullData;
          properties.stop = i > 0 && Math.abs(parsed[iAxis] - prevParsed[iAxis]) > maxGapLength;
          if (segment) {
            properties.parsed = parsed;
            properties.raw = _dataset.data[i];
          }
          if (includeOptions) {
            properties.options = sharedOptions || this.resolveDataElementOptions(i, point.active ? 'active' : mode);
          }
          if (!directUpdate) {
            this.updateElement(point, i, properties, mode);
          }
          prevParsed = parsed;
        }
        this.updateSharedOptions(sharedOptions, mode, firstOpts);
      }
    }, {
      key: "getMaxOverflow",
      value: function getMaxOverflow() {
        var meta = this._cachedMeta;
        var data = meta.data || [];
        if (!this.options.showLine) {
          var max = 0;
          for (var i = data.length - 1; i >= 0; --i) {
            max = Math.max(max, data[i].size(this.resolveDataElementOptions(i)) / 2);
          }
          return max > 0 && max;
        }
        var dataset = meta.dataset;
        var border = dataset.options && dataset.options.borderWidth || 0;
        if (!data.length) {
          return border;
        }
        var firstPoint = data[0].size(this.resolveDataElementOptions(0));
        var lastPoint = data[data.length - 1].size(this.resolveDataElementOptions(data.length - 1));
        return Math.max(border, firstPoint, lastPoint) / 2;
      }
    }]);
  }(DatasetController);
  _defineProperty$1(ScatterController, "id", 'scatter');
  _defineProperty$1(ScatterController, "defaults", {
    datasetElementType: false,
    dataElementType: 'point',
    showLine: false,
    fill: false
  });
  _defineProperty$1(ScatterController, "overrides", {
    interaction: {
      mode: 'point'
    },
    scales: {
      x: {
        type: 'linear'
      },
      y: {
        type: 'linear'
      }
    }
  });
  var controllers = /*#__PURE__*/Object.freeze({
    __proto__: null,
    BarController: BarController,
    BubbleController: BubbleController,
    DoughnutController: DoughnutController,
    LineController: LineController,
    PieController: PieController,
    PolarAreaController: PolarAreaController,
    RadarController: RadarController,
    ScatterController: ScatterController
  });

  /**
   * @namespace Chart._adapters
   * @since 2.8.0
   * @private
   */
  function _abstract() {
    throw new Error('This method is not implemented: Check that a complete date adapter is provided.');
  }
  /**
   * Date adapter (current used by the time scale)
   * @namespace Chart._adapters._date
   * @memberof Chart._adapters
   * @private
   */
  var DateAdapterBase = /*#__PURE__*/function () {
    function DateAdapterBase(options) {
      _classCallCheck$1(this, DateAdapterBase);
      _defineProperty$1(this, "options", void 0);
      this.options = options || {};
    }
    // eslint-disable-next-line @typescript-eslint/no-empty-function
    return _createClass$1(DateAdapterBase, [{
      key: "init",
      value: function init() {}
    }, {
      key: "formats",
      value: function formats() {
        return _abstract();
      }
    }, {
      key: "parse",
      value: function parse() {
        return _abstract();
      }
    }, {
      key: "format",
      value: function format() {
        return _abstract();
      }
    }, {
      key: "add",
      value: function add() {
        return _abstract();
      }
    }, {
      key: "diff",
      value: function diff() {
        return _abstract();
      }
    }, {
      key: "startOf",
      value: function startOf() {
        return _abstract();
      }
    }, {
      key: "endOf",
      value: function endOf() {
        return _abstract();
      }
    }], [{
      key: "override",
      value:
      /**
      * Override default date adapter methods.
      * Accepts type parameter to define options type.
      * @example
      * Chart._adapters._date.override<{myAdapterOption: string}>({
      *   init() {
      *     console.log(this.options.myAdapterOption);
      *   }
      * })
      */
      function override(members) {
        Object.assign(DateAdapterBase.prototype, members);
      }
    }]);
  }();
  var adapters = {
    _date: DateAdapterBase
  };
  function binarySearch(metaset, axis, value, intersect) {
    var controller = metaset.controller,
      data = metaset.data,
      _sorted = metaset._sorted;
    var iScale = controller._cachedMeta.iScale;
    if (iScale && axis === iScale.axis && axis !== 'r' && _sorted && data.length) {
      var lookupMethod = iScale._reversePixels ? _rlookupByKey : _lookupByKey;
      if (!intersect) {
        return lookupMethod(data, axis, value);
      } else if (controller._sharedOptions) {
        var el = data[0];
        var range = typeof el.getRange === 'function' && el.getRange(axis);
        if (range) {
          var start = lookupMethod(data, axis, value - range);
          var end = lookupMethod(data, axis, value + range);
          return {
            lo: start.lo,
            hi: end.hi
          };
        }
      }
    }
    return {
      lo: 0,
      hi: data.length - 1
    };
  }
  function evaluateInteractionItems(chart, axis, position, handler, intersect) {
    var metasets = chart.getSortedVisibleDatasetMetas();
    var value = position[axis];
    for (var i = 0, ilen = metasets.length; i < ilen; ++i) {
      var _metasets$i = metasets[i],
        _index2 = _metasets$i.index,
        data = _metasets$i.data;
      var _binarySearch = binarySearch(metasets[i], axis, value, intersect),
        lo = _binarySearch.lo,
        hi = _binarySearch.hi;
      for (var j = lo; j <= hi; ++j) {
        var element = data[j];
        if (!element.skip) {
          handler(element, _index2, j);
        }
      }
    }
  }
  function getDistanceMetricForAxis(axis) {
    var useX = axis.indexOf('x') !== -1;
    var useY = axis.indexOf('y') !== -1;
    return function (pt1, pt2) {
      var deltaX = useX ? Math.abs(pt1.x - pt2.x) : 0;
      var deltaY = useY ? Math.abs(pt1.y - pt2.y) : 0;
      return Math.sqrt(Math.pow(deltaX, 2) + Math.pow(deltaY, 2));
    };
  }
  function getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) {
    var items = [];
    if (!includeInvisible && !chart.isPointInArea(position)) {
      return items;
    }
    var evaluationFunc = function evaluationFunc(element, datasetIndex, index) {
      if (!includeInvisible && !_isPointInArea(element, chart.chartArea, 0)) {
        return;
      }
      if (element.inRange(position.x, position.y, useFinalPosition)) {
        items.push({
          element: element,
          datasetIndex: datasetIndex,
          index: index
        });
      }
    };
    evaluateInteractionItems(chart, axis, position, evaluationFunc, true);
    return items;
  }
  function getNearestRadialItems(chart, position, axis, useFinalPosition) {
    var items = [];
    function evaluationFunc(element, datasetIndex, index) {
      var _element$getProps = element.getProps(['startAngle', 'endAngle'], useFinalPosition),
        startAngle = _element$getProps.startAngle,
        endAngle = _element$getProps.endAngle;
      var _getAngleFromPoint = getAngleFromPoint(element, {
          x: position.x,
          y: position.y
        }),
        angle = _getAngleFromPoint.angle;
      if (_angleBetween(angle, startAngle, endAngle)) {
        items.push({
          element: element,
          datasetIndex: datasetIndex,
          index: index
        });
      }
    }
    evaluateInteractionItems(chart, axis, position, evaluationFunc);
    return items;
  }
  function getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
    var items = [];
    var distanceMetric = getDistanceMetricForAxis(axis);
    var minDistance = Number.POSITIVE_INFINITY;
    function evaluationFunc(element, datasetIndex, index) {
      var inRange = element.inRange(position.x, position.y, useFinalPosition);
      if (intersect && !inRange) {
        return;
      }
      var center = element.getCenterPoint(useFinalPosition);
      var pointInArea = !!includeInvisible || chart.isPointInArea(center);
      if (!pointInArea && !inRange) {
        return;
      }
      var distance = distanceMetric(position, center);
      if (distance < minDistance) {
        items = [{
          element: element,
          datasetIndex: datasetIndex,
          index: index
        }];
        minDistance = distance;
      } else if (distance === minDistance) {
        items.push({
          element: element,
          datasetIndex: datasetIndex,
          index: index
        });
      }
    }
    evaluateInteractionItems(chart, axis, position, evaluationFunc);
    return items;
  }
  function getNearestItems(chart, position, axis, intersect, useFinalPosition, includeInvisible) {
    if (!includeInvisible && !chart.isPointInArea(position)) {
      return [];
    }
    return axis === 'r' && !intersect ? getNearestRadialItems(chart, position, axis, useFinalPosition) : getNearestCartesianItems(chart, position, axis, intersect, useFinalPosition, includeInvisible);
  }
  function getAxisItems(chart, position, axis, intersect, useFinalPosition) {
    var items = [];
    var rangeMethod = axis === 'x' ? 'inXRange' : 'inYRange';
    var intersectsItem = false;
    evaluateInteractionItems(chart, axis, position, function (element, datasetIndex, index) {
      if (element[rangeMethod](position[axis], useFinalPosition)) {
        items.push({
          element: element,
          datasetIndex: datasetIndex,
          index: index
        });
        intersectsItem = intersectsItem || element.inRange(position.x, position.y, useFinalPosition);
      }
    });
    if (intersect && !intersectsItem) {
      return [];
    }
    return items;
  }
  var Interaction = {
    evaluateInteractionItems: evaluateInteractionItems,
    modes: {
      index: function index(chart, e, options, useFinalPosition) {
        var position = getRelativePosition(e, chart);
        var axis = options.axis || 'x';
        var includeInvisible = options.includeInvisible || false;
        var items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
        var elements = [];
        if (!items.length) {
          return [];
        }
        chart.getSortedVisibleDatasetMetas().forEach(function (meta) {
          var index = items[0].index;
          var element = meta.data[index];
          if (element && !element.skip) {
            elements.push({
              element: element,
              datasetIndex: meta.index,
              index: index
            });
          }
        });
        return elements;
      },
      dataset: function dataset(chart, e, options, useFinalPosition) {
        var position = getRelativePosition(e, chart);
        var axis = options.axis || 'xy';
        var includeInvisible = options.includeInvisible || false;
        var items = options.intersect ? getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible) : getNearestItems(chart, position, axis, false, useFinalPosition, includeInvisible);
        if (items.length > 0) {
          var datasetIndex = items[0].datasetIndex;
          var data = chart.getDatasetMeta(datasetIndex).data;
          items = [];
          for (var i = 0; i < data.length; ++i) {
            items.push({
              element: data[i],
              datasetIndex: datasetIndex,
              index: i
            });
          }
        }
        return items;
      },
      point: function point(chart, e, options, useFinalPosition) {
        var position = getRelativePosition(e, chart);
        var axis = options.axis || 'xy';
        var includeInvisible = options.includeInvisible || false;
        return getIntersectItems(chart, position, axis, useFinalPosition, includeInvisible);
      },
      nearest: function nearest(chart, e, options, useFinalPosition) {
        var position = getRelativePosition(e, chart);
        var axis = options.axis || 'xy';
        var includeInvisible = options.includeInvisible || false;
        return getNearestItems(chart, position, axis, options.intersect, useFinalPosition, includeInvisible);
      },
      x: function x(chart, e, options, useFinalPosition) {
        var position = getRelativePosition(e, chart);
        return getAxisItems(chart, position, 'x', options.intersect, useFinalPosition);
      },
      y: function y(chart, e, options, useFinalPosition) {
        var position = getRelativePosition(e, chart);
        return getAxisItems(chart, position, 'y', options.intersect, useFinalPosition);
      }
    }
  };
  var STATIC_POSITIONS = ['left', 'top', 'right', 'bottom'];
  function filterByPosition(array, position) {
    return array.filter(function (v) {
      return v.pos === position;
    });
  }
  function filterDynamicPositionByAxis(array, axis) {
    return array.filter(function (v) {
      return STATIC_POSITIONS.indexOf(v.pos) === -1 && v.box.axis === axis;
    });
  }
  function sortByWeight(array, reverse) {
    return array.sort(function (a, b) {
      var v0 = reverse ? b : a;
      var v1 = reverse ? a : b;
      return v0.weight === v1.weight ? v0.index - v1.index : v0.weight - v1.weight;
    });
  }
  function wrapBoxes(boxes) {
    var layoutBoxes = [];
    var i, ilen, box, pos, stack, stackWeight;
    for (i = 0, ilen = (boxes || []).length; i < ilen; ++i) {
      box = boxes[i];
      var _box = box;
      pos = _box.position;
      var _box$options = _box.options;
      stack = _box$options.stack;
      var _box$options$stackWei = _box$options.stackWeight;
      stackWeight = _box$options$stackWei === void 0 ? 1 : _box$options$stackWei;
      layoutBoxes.push({
        index: i,
        box: box,
        pos: pos,
        horizontal: box.isHorizontal(),
        weight: box.weight,
        stack: stack && pos + stack,
        stackWeight: stackWeight
      });
    }
    return layoutBoxes;
  }
  function buildStacks(layouts) {
    var stacks = {};
    var _iterator5 = _createForOfIteratorHelper$1(layouts),
      _step5;
    try {
      for (_iterator5.s(); !(_step5 = _iterator5.n()).done;) {
        var wrap = _step5.value;
        var stack = wrap.stack,
          pos = wrap.pos,
          stackWeight = wrap.stackWeight;
        if (!stack || !STATIC_POSITIONS.includes(pos)) {
          continue;
        }
        var _stack = stacks[stack] || (stacks[stack] = {
          count: 0,
          placed: 0,
          weight: 0,
          size: 0
        });
        _stack.count++;
        _stack.weight += stackWeight;
      }
    } catch (err) {
      _iterator5.e(err);
    } finally {
      _iterator5.f();
    }
    return stacks;
  }
  function setLayoutDims(layouts, params) {
    var stacks = buildStacks(layouts);
    var vBoxMaxWidth = params.vBoxMaxWidth,
      hBoxMaxHeight = params.hBoxMaxHeight;
    var i, ilen, layout;
    for (i = 0, ilen = layouts.length; i < ilen; ++i) {
      layout = layouts[i];
      var fullSize = layout.box.fullSize;
      var stack = stacks[layout.stack];
      var factor = stack && layout.stackWeight / stack.weight;
      if (layout.horizontal) {
        layout.width = factor ? factor * vBoxMaxWidth : fullSize && params.availableWidth;
        layout.height = hBoxMaxHeight;
      } else {
        layout.width = vBoxMaxWidth;
        layout.height = factor ? factor * hBoxMaxHeight : fullSize && params.availableHeight;
      }
    }
    return stacks;
  }
  function buildLayoutBoxes(boxes) {
    var layoutBoxes = wrapBoxes(boxes);
    var fullSize = sortByWeight(layoutBoxes.filter(function (wrap) {
      return wrap.box.fullSize;
    }), true);
    var left = sortByWeight(filterByPosition(layoutBoxes, 'left'), true);
    var right = sortByWeight(filterByPosition(layoutBoxes, 'right'));
    var top = sortByWeight(filterByPosition(layoutBoxes, 'top'), true);
    var bottom = sortByWeight(filterByPosition(layoutBoxes, 'bottom'));
    var centerHorizontal = filterDynamicPositionByAxis(layoutBoxes, 'x');
    var centerVertical = filterDynamicPositionByAxis(layoutBoxes, 'y');
    return {
      fullSize: fullSize,
      leftAndTop: left.concat(top),
      rightAndBottom: right.concat(centerVertical).concat(bottom).concat(centerHorizontal),
      chartArea: filterByPosition(layoutBoxes, 'chartArea'),
      vertical: left.concat(right).concat(centerVertical),
      horizontal: top.concat(bottom).concat(centerHorizontal)
    };
  }
  function getCombinedMax(maxPadding, chartArea, a, b) {
    return Math.max(maxPadding[a], chartArea[a]) + Math.max(maxPadding[b], chartArea[b]);
  }
  function updateMaxPadding(maxPadding, boxPadding) {
    maxPadding.top = Math.max(maxPadding.top, boxPadding.top);
    maxPadding.left = Math.max(maxPadding.left, boxPadding.left);
    maxPadding.bottom = Math.max(maxPadding.bottom, boxPadding.bottom);
    maxPadding.right = Math.max(maxPadding.right, boxPadding.right);
  }
  function updateDims(chartArea, params, layout, stacks) {
    var pos = layout.pos,
      box = layout.box;
    var maxPadding = chartArea.maxPadding;
    if (!isObject(pos)) {
      if (layout.size) {
        chartArea[pos] -= layout.size;
      }
      var stack = stacks[layout.stack] || {
        size: 0,
        count: 1
      };
      stack.size = Math.max(stack.size, layout.horizontal ? box.height : box.width);
      layout.size = stack.size / stack.count;
      chartArea[pos] += layout.size;
    }
    if (box.getPadding) {
      updateMaxPadding(maxPadding, box.getPadding());
    }
    var newWidth = Math.max(0, params.outerWidth - getCombinedMax(maxPadding, chartArea, 'left', 'right'));
    var newHeight = Math.max(0, params.outerHeight - getCombinedMax(maxPadding, chartArea, 'top', 'bottom'));
    var widthChanged = newWidth !== chartArea.w;
    var heightChanged = newHeight !== chartArea.h;
    chartArea.w = newWidth;
    chartArea.h = newHeight;
    return layout.horizontal ? {
      same: widthChanged,
      other: heightChanged
    } : {
      same: heightChanged,
      other: widthChanged
    };
  }
  function handleMaxPadding(chartArea) {
    var maxPadding = chartArea.maxPadding;
    function updatePos(pos) {
      var change = Math.max(maxPadding[pos] - chartArea[pos], 0);
      chartArea[pos] += change;
      return change;
    }
    chartArea.y += updatePos('top');
    chartArea.x += updatePos('left');
    updatePos('right');
    updatePos('bottom');
  }
  function getMargins(horizontal, chartArea) {
    var maxPadding = chartArea.maxPadding;
    function marginForPositions(positions) {
      var margin = {
        left: 0,
        top: 0,
        right: 0,
        bottom: 0
      };
      positions.forEach(function (pos) {
        margin[pos] = Math.max(chartArea[pos], maxPadding[pos]);
      });
      return margin;
    }
    return horizontal ? marginForPositions(['left', 'right']) : marginForPositions(['top', 'bottom']);
  }
  function fitBoxes(boxes, chartArea, params, stacks) {
    var refitBoxes = [];
    var i, ilen, layout, box, refit, changed;
    for (i = 0, ilen = boxes.length, refit = 0; i < ilen; ++i) {
      layout = boxes[i];
      box = layout.box;
      box.update(layout.width || chartArea.w, layout.height || chartArea.h, getMargins(layout.horizontal, chartArea));
      var _updateDims = updateDims(chartArea, params, layout, stacks),
        same = _updateDims.same,
        other = _updateDims.other;
      refit |= same && refitBoxes.length;
      changed = changed || other;
      if (!box.fullSize) {
        refitBoxes.push(layout);
      }
    }
    return refit && fitBoxes(refitBoxes, chartArea, params, stacks) || changed;
  }
  function setBoxDims(box, left, top, width, height) {
    box.top = top;
    box.left = left;
    box.right = left + width;
    box.bottom = top + height;
    box.width = width;
    box.height = height;
  }
  function placeBoxes(boxes, chartArea, params, stacks) {
    var userPadding = params.padding;
    var x = chartArea.x,
      y = chartArea.y;
    var _iterator6 = _createForOfIteratorHelper$1(boxes),
      _step6;
    try {
      for (_iterator6.s(); !(_step6 = _iterator6.n()).done;) {
        var layout = _step6.value;
        var box = layout.box;
        var stack = stacks[layout.stack] || {
          count: 1,
          placed: 0,
          weight: 1
        };
        var weight = layout.stackWeight / stack.weight || 1;
        if (layout.horizontal) {
          var width = chartArea.w * weight;
          var height = stack.size || box.height;
          if (defined(stack.start)) {
            y = stack.start;
          }
          if (box.fullSize) {
            setBoxDims(box, userPadding.left, y, params.outerWidth - userPadding.right - userPadding.left, height);
          } else {
            setBoxDims(box, chartArea.left + stack.placed, y, width, height);
          }
          stack.start = y;
          stack.placed += width;
          y = box.bottom;
        } else {
          var _height = chartArea.h * weight;
          var _width = stack.size || box.width;
          if (defined(stack.start)) {
            x = stack.start;
          }
          if (box.fullSize) {
            setBoxDims(box, x, userPadding.top, _width, params.outerHeight - userPadding.bottom - userPadding.top);
          } else {
            setBoxDims(box, x, chartArea.top + stack.placed, _width, _height);
          }
          stack.start = x;
          stack.placed += _height;
          x = box.right;
        }
      }
    } catch (err) {
      _iterator6.e(err);
    } finally {
      _iterator6.f();
    }
    chartArea.x = x;
    chartArea.y = y;
  }
  var layouts = {
    addBox: function addBox(chart, item) {
      if (!chart.boxes) {
        chart.boxes = [];
      }
      item.fullSize = item.fullSize || false;
      item.position = item.position || 'top';
      item.weight = item.weight || 0;
      item._layers = item._layers || function () {
        return [{
          z: 0,
          draw: function draw(chartArea) {
            item.draw(chartArea);
          }
        }];
      };
      chart.boxes.push(item);
    },
    removeBox: function removeBox(chart, layoutItem) {
      var index = chart.boxes ? chart.boxes.indexOf(layoutItem) : -1;
      if (index !== -1) {
        chart.boxes.splice(index, 1);
      }
    },
    configure: function configure(chart, item, options) {
      item.fullSize = options.fullSize;
      item.position = options.position;
      item.weight = options.weight;
    },
    update: function update(chart, width, height, minPadding) {
      if (!chart) {
        return;
      }
      var padding = toPadding(chart.options.layout.padding);
      var availableWidth = Math.max(width - padding.width, 0);
      var availableHeight = Math.max(height - padding.height, 0);
      var boxes = buildLayoutBoxes(chart.boxes);
      var verticalBoxes = boxes.vertical;
      var horizontalBoxes = boxes.horizontal;
      each(chart.boxes, function (box) {
        if (typeof box.beforeLayout === 'function') {
          box.beforeLayout();
        }
      });
      var visibleVerticalBoxCount = verticalBoxes.reduce(function (total, wrap) {
        return wrap.box.options && wrap.box.options.display === false ? total : total + 1;
      }, 0) || 1;
      var params = Object.freeze({
        outerWidth: width,
        outerHeight: height,
        padding: padding,
        availableWidth: availableWidth,
        availableHeight: availableHeight,
        vBoxMaxWidth: availableWidth / 2 / visibleVerticalBoxCount,
        hBoxMaxHeight: availableHeight / 2
      });
      var maxPadding = Object.assign({}, padding);
      updateMaxPadding(maxPadding, toPadding(minPadding));
      var chartArea = Object.assign({
        maxPadding: maxPadding,
        w: availableWidth,
        h: availableHeight,
        x: padding.left,
        y: padding.top
      }, padding);
      var stacks = setLayoutDims(verticalBoxes.concat(horizontalBoxes), params);
      fitBoxes(boxes.fullSize, chartArea, params, stacks);
      fitBoxes(verticalBoxes, chartArea, params, stacks);
      if (fitBoxes(horizontalBoxes, chartArea, params, stacks)) {
        fitBoxes(verticalBoxes, chartArea, params, stacks);
      }
      handleMaxPadding(chartArea);
      placeBoxes(boxes.leftAndTop, chartArea, params, stacks);
      chartArea.x += chartArea.w;
      chartArea.y += chartArea.h;
      placeBoxes(boxes.rightAndBottom, chartArea, params, stacks);
      chart.chartArea = {
        left: chartArea.left,
        top: chartArea.top,
        right: chartArea.left + chartArea.w,
        bottom: chartArea.top + chartArea.h,
        height: chartArea.h,
        width: chartArea.w
      };
      each(boxes.chartArea, function (layout) {
        var box = layout.box;
        Object.assign(box, chart.chartArea);
        box.update(chartArea.w, chartArea.h, {
          left: 0,
          top: 0,
          right: 0,
          bottom: 0
        });
      });
    }
  };
  var BasePlatform = /*#__PURE__*/function () {
    function BasePlatform() {
      _classCallCheck$1(this, BasePlatform);
    }
    return _createClass$1(BasePlatform, [{
      key: "acquireContext",
      value: function acquireContext(canvas, aspectRatio) {}
    }, {
      key: "releaseContext",
      value: function releaseContext(context) {
        return false;
      }
    }, {
      key: "addEventListener",
      value: function addEventListener(chart, type, listener) {}
    }, {
      key: "removeEventListener",
      value: function removeEventListener(chart, type, listener) {}
    }, {
      key: "getDevicePixelRatio",
      value: function getDevicePixelRatio() {
        return 1;
      }
    }, {
      key: "getMaximumSize",
      value: function getMaximumSize(element, width, height, aspectRatio) {
        width = Math.max(0, width || element.width);
        height = height || element.height;
        return {
          width: width,
          height: Math.max(0, aspectRatio ? Math.floor(width / aspectRatio) : height)
        };
      }
    }, {
      key: "isAttached",
      value: function isAttached(canvas) {
        return true;
      }
    }, {
      key: "updateConfig",
      value: function updateConfig(config) {}
    }]);
  }();
  var BasicPlatform = /*#__PURE__*/function (_BasePlatform) {
    function BasicPlatform() {
      _classCallCheck$1(this, BasicPlatform);
      return _callSuper(this, BasicPlatform, arguments);
    }
    _inherits$1(BasicPlatform, _BasePlatform);
    return _createClass$1(BasicPlatform, [{
      key: "acquireContext",
      value: function acquireContext(item) {
        return item && item.getContext && item.getContext('2d') || null;
      }
    }, {
      key: "updateConfig",
      value: function updateConfig(config) {
        config.options.animation = false;
      }
    }]);
  }(BasePlatform);
  var EXPANDO_KEY = '$chartjs';
  var EVENT_TYPES = {
    touchstart: 'mousedown',
    touchmove: 'mousemove',
    touchend: 'mouseup',
    pointerenter: 'mouseenter',
    pointerdown: 'mousedown',
    pointermove: 'mousemove',
    pointerup: 'mouseup',
    pointerleave: 'mouseout',
    pointerout: 'mouseout'
  };
  var isNullOrEmpty = function isNullOrEmpty(value) {
    return value === null || value === '';
  };
  function initCanvas(canvas, aspectRatio) {
    var style = canvas.style;
    var renderHeight = canvas.getAttribute('height');
    var renderWidth = canvas.getAttribute('width');
    canvas[EXPANDO_KEY] = {
      initial: {
        height: renderHeight,
        width: renderWidth,
        style: {
          display: style.display,
          height: style.height,
          width: style.width
        }
      }
    };
    style.display = style.display || 'block';
    style.boxSizing = style.boxSizing || 'border-box';
    if (isNullOrEmpty(renderWidth)) {
      var displayWidth = readUsedSize(canvas, 'width');
      if (displayWidth !== undefined) {
        canvas.width = displayWidth;
      }
    }
    if (isNullOrEmpty(renderHeight)) {
      if (canvas.style.height === '') {
        canvas.height = canvas.width / (aspectRatio || 2);
      } else {
        var displayHeight = readUsedSize(canvas, 'height');
        if (displayHeight !== undefined) {
          canvas.height = displayHeight;
        }
      }
    }
    return canvas;
  }
  var eventListenerOptions = supportsEventListenerOptions ? {
    passive: true
  } : false;
  function addListener(node, type, listener) {
    if (node) {
      node.addEventListener(type, listener, eventListenerOptions);
    }
  }
  function removeListener(chart, type, listener) {
    if (chart && chart.canvas) {
      chart.canvas.removeEventListener(type, listener, eventListenerOptions);
    }
  }
  function fromNativeEvent(event, chart) {
    var type = EVENT_TYPES[event.type] || event.type;
    var _getRelativePosition = getRelativePosition(event, chart),
      x = _getRelativePosition.x,
      y = _getRelativePosition.y;
    return {
      type: type,
      chart: chart,
      "native": event,
      x: x !== undefined ? x : null,
      y: y !== undefined ? y : null
    };
  }
  function nodeListContains(nodeList, canvas) {
    var _iterator7 = _createForOfIteratorHelper$1(nodeList),
      _step7;
    try {
      for (_iterator7.s(); !(_step7 = _iterator7.n()).done;) {
        var node = _step7.value;
        if (node === canvas || node.contains(canvas)) {
          return true;
        }
      }
    } catch (err) {
      _iterator7.e(err);
    } finally {
      _iterator7.f();
    }
  }
  function createAttachObserver(chart, type, listener) {
    var canvas = chart.canvas;
    var observer = new MutationObserver(function (entries) {
      var trigger = false;
      var _iterator8 = _createForOfIteratorHelper$1(entries),
        _step8;
      try {
        for (_iterator8.s(); !(_step8 = _iterator8.n()).done;) {
          var entry = _step8.value;
          trigger = trigger || nodeListContains(entry.addedNodes, canvas);
          trigger = trigger && !nodeListContains(entry.removedNodes, canvas);
        }
      } catch (err) {
        _iterator8.e(err);
      } finally {
        _iterator8.f();
      }
      if (trigger) {
        listener();
      }
    });
    observer.observe(document, {
      childList: true,
      subtree: true
    });
    return observer;
  }
  function createDetachObserver(chart, type, listener) {
    var canvas = chart.canvas;
    var observer = new MutationObserver(function (entries) {
      var trigger = false;
      var _iterator9 = _createForOfIteratorHelper$1(entries),
        _step9;
      try {
        for (_iterator9.s(); !(_step9 = _iterator9.n()).done;) {
          var entry = _step9.value;
          trigger = trigger || nodeListContains(entry.removedNodes, canvas);
          trigger = trigger && !nodeListContains(entry.addedNodes, canvas);
        }
      } catch (err) {
        _iterator9.e(err);
      } finally {
        _iterator9.f();
      }
      if (trigger) {
        listener();
      }
    });
    observer.observe(document, {
      childList: true,
      subtree: true
    });
    return observer;
  }
  var drpListeningCharts = new Map();
  var oldDevicePixelRatio = 0;
  function onWindowResize() {
    var dpr = window.devicePixelRatio;
    if (dpr === oldDevicePixelRatio) {
      return;
    }
    oldDevicePixelRatio = dpr;
    drpListeningCharts.forEach(function (resize, chart) {
      if (chart.currentDevicePixelRatio !== dpr) {
        resize();
      }
    });
  }
  function listenDevicePixelRatioChanges(chart, resize) {
    if (!drpListeningCharts.size) {
      window.addEventListener('resize', onWindowResize);
    }
    drpListeningCharts.set(chart, resize);
  }
  function unlistenDevicePixelRatioChanges(chart) {
    drpListeningCharts["delete"](chart);
    if (!drpListeningCharts.size) {
      window.removeEventListener('resize', onWindowResize);
    }
  }
  function createResizeObserver(chart, type, listener) {
    var canvas = chart.canvas;
    var container = canvas && _getParentNode(canvas);
    if (!container) {
      return;
    }
    var resize = throttled(function (width, height) {
      var w = container.clientWidth;
      listener(width, height);
      if (w < container.clientWidth) {
        listener();
      }
    }, window);
    var observer = new ResizeObserver(function (entries) {
      var entry = entries[0];
      var width = entry.contentRect.width;
      var height = entry.contentRect.height;
      if (width === 0 && height === 0) {
        return;
      }
      resize(width, height);
    });
    observer.observe(container);
    listenDevicePixelRatioChanges(chart, resize);
    return observer;
  }
  function releaseObserver(chart, type, observer) {
    if (observer) {
      observer.disconnect();
    }
    if (type === 'resize') {
      unlistenDevicePixelRatioChanges(chart);
    }
  }
  function createProxyAndListen(chart, type, listener) {
    var canvas = chart.canvas;
    var proxy = throttled(function (event) {
      if (chart.ctx !== null) {
        listener(fromNativeEvent(event, chart));
      }
    }, chart);
    addListener(canvas, type, proxy);
    return proxy;
  }
  var DomPlatform = /*#__PURE__*/function (_BasePlatform2) {
    function DomPlatform() {
      _classCallCheck$1(this, DomPlatform);
      return _callSuper(this, DomPlatform, arguments);
    }
    _inherits$1(DomPlatform, _BasePlatform2);
    return _createClass$1(DomPlatform, [{
      key: "acquireContext",
      value: function acquireContext(canvas, aspectRatio) {
        var context = canvas && canvas.getContext && canvas.getContext('2d');
        if (context && context.canvas === canvas) {
          initCanvas(canvas, aspectRatio);
          return context;
        }
        return null;
      }
    }, {
      key: "releaseContext",
      value: function releaseContext(context) {
        var canvas = context.canvas;
        if (!canvas[EXPANDO_KEY]) {
          return false;
        }
        var initial = canvas[EXPANDO_KEY].initial;
        ['height', 'width'].forEach(function (prop) {
          var value = initial[prop];
          if (isNullOrUndef(value)) {
            canvas.removeAttribute(prop);
          } else {
            canvas.setAttribute(prop, value);
          }
        });
        var style = initial.style || {};
        Object.keys(style).forEach(function (key) {
          canvas.style[key] = style[key];
        });
        canvas.width = canvas.width;
        delete canvas[EXPANDO_KEY];
        return true;
      }
    }, {
      key: "addEventListener",
      value: function addEventListener(chart, type, listener) {
        this.removeEventListener(chart, type);
        var proxies = chart.$proxies || (chart.$proxies = {});
        var handlers = {
          attach: createAttachObserver,
          detach: createDetachObserver,
          resize: createResizeObserver
        };
        var handler = handlers[type] || createProxyAndListen;
        proxies[type] = handler(chart, type, listener);
      }
    }, {
      key: "removeEventListener",
      value: function removeEventListener(chart, type) {
        var proxies = chart.$proxies || (chart.$proxies = {});
        var proxy = proxies[type];
        if (!proxy) {
          return;
        }
        var handlers = {
          attach: releaseObserver,
          detach: releaseObserver,
          resize: releaseObserver
        };
        var handler = handlers[type] || removeListener;
        handler(chart, type, proxy);
        proxies[type] = undefined;
      }
    }, {
      key: "getDevicePixelRatio",
      value: function getDevicePixelRatio() {
        return window.devicePixelRatio;
      }
    }, {
      key: "getMaximumSize",
      value: function getMaximumSize$1(canvas, width, height, aspectRatio) {
        return getMaximumSize(canvas, width, height, aspectRatio);
      }
    }, {
      key: "isAttached",
      value: function isAttached(canvas) {
        var container = canvas && _getParentNode(canvas);
        return !!(container && container.isConnected);
      }
    }]);
  }(BasePlatform);
  function _detectPlatform(canvas) {
    if (!_isDomSupported() || typeof OffscreenCanvas !== 'undefined' && canvas instanceof OffscreenCanvas) {
      return BasicPlatform;
    }
    return DomPlatform;
  }
  var Element = /*#__PURE__*/function () {
    function Element() {
      _classCallCheck$1(this, Element);
      _defineProperty$1(this, "x", void 0);
      _defineProperty$1(this, "y", void 0);
      _defineProperty$1(this, "active", false);
      _defineProperty$1(this, "options", void 0);
      _defineProperty$1(this, "$animations", void 0);
    }
    return _createClass$1(Element, [{
      key: "tooltipPosition",
      value: function tooltipPosition(useFinalPosition) {
        var _this$getProps = this.getProps(['x', 'y'], useFinalPosition),
          x = _this$getProps.x,
          y = _this$getProps.y;
        return {
          x: x,
          y: y
        };
      }
    }, {
      key: "hasValue",
      value: function hasValue() {
        return isNumber(this.x) && isNumber(this.y);
      }
    }, {
      key: "getProps",
      value: function getProps(props, _final) {
        var _this8 = this;
        var anims = this.$animations;
        if (!_final || !anims) {
          // let's not create an object, if not needed
          return this;
        }
        var ret = {};
        props.forEach(function (prop) {
          ret[prop] = anims[prop] && anims[prop].active() ? anims[prop]._to : _this8[prop];
        });
        return ret;
      }
    }]);
  }();
  _defineProperty$1(Element, "defaults", {});
  _defineProperty$1(Element, "defaultRoutes", undefined);
  function autoSkip(scale, ticks) {
    var tickOpts = scale.options.ticks;
    var determinedMaxTicks = determineMaxTicks(scale);
    var ticksLimit = Math.min(tickOpts.maxTicksLimit || determinedMaxTicks, determinedMaxTicks);
    var majorIndices = tickOpts.major.enabled ? getMajorIndices(ticks) : [];
    var numMajorIndices = majorIndices.length;
    var first = majorIndices[0];
    var last = majorIndices[numMajorIndices - 1];
    var newTicks = [];
    if (numMajorIndices > ticksLimit) {
      skipMajors(ticks, newTicks, majorIndices, numMajorIndices / ticksLimit);
      return newTicks;
    }
    var spacing = calculateSpacing(majorIndices, ticks, ticksLimit);
    if (numMajorIndices > 0) {
      var i, ilen;
      var avgMajorSpacing = numMajorIndices > 1 ? Math.round((last - first) / (numMajorIndices - 1)) : null;
      skip(ticks, newTicks, spacing, isNullOrUndef(avgMajorSpacing) ? 0 : first - avgMajorSpacing, first);
      for (i = 0, ilen = numMajorIndices - 1; i < ilen; i++) {
        skip(ticks, newTicks, spacing, majorIndices[i], majorIndices[i + 1]);
      }
      skip(ticks, newTicks, spacing, last, isNullOrUndef(avgMajorSpacing) ? ticks.length : last + avgMajorSpacing);
      return newTicks;
    }
    skip(ticks, newTicks, spacing);
    return newTicks;
  }
  function determineMaxTicks(scale) {
    var offset = scale.options.offset;
    var tickLength = scale._tickSize();
    var maxScale = scale._length / tickLength + (offset ? 0 : 1);
    var maxChart = scale._maxLength / tickLength;
    return Math.floor(Math.min(maxScale, maxChart));
  }
  function calculateSpacing(majorIndices, ticks, ticksLimit) {
    var evenMajorSpacing = getEvenSpacing(majorIndices);
    var spacing = ticks.length / ticksLimit;
    if (!evenMajorSpacing) {
      return Math.max(spacing, 1);
    }
    var factors = _factorize(evenMajorSpacing);
    for (var i = 0, ilen = factors.length - 1; i < ilen; i++) {
      var factor = factors[i];
      if (factor > spacing) {
        return factor;
      }
    }
    return Math.max(spacing, 1);
  }
  function getMajorIndices(ticks) {
    var result = [];
    var i, ilen;
    for (i = 0, ilen = ticks.length; i < ilen; i++) {
      if (ticks[i].major) {
        result.push(i);
      }
    }
    return result;
  }
  function skipMajors(ticks, newTicks, majorIndices, spacing) {
    var count = 0;
    var next = majorIndices[0];
    var i;
    spacing = Math.ceil(spacing);
    for (i = 0; i < ticks.length; i++) {
      if (i === next) {
        newTicks.push(ticks[i]);
        count++;
        next = majorIndices[count * spacing];
      }
    }
  }
  function skip(ticks, newTicks, spacing, majorStart, majorEnd) {
    var start = valueOrDefault(majorStart, 0);
    var end = Math.min(valueOrDefault(majorEnd, ticks.length), ticks.length);
    var count = 0;
    var length, i, next;
    spacing = Math.ceil(spacing);
    if (majorEnd) {
      length = majorEnd - majorStart;
      spacing = length / Math.floor(length / spacing);
    }
    next = start;
    while (next < 0) {
      count++;
      next = Math.round(start + count * spacing);
    }
    for (i = Math.max(start, 0); i < end; i++) {
      if (i === next) {
        newTicks.push(ticks[i]);
        count++;
        next = Math.round(start + count * spacing);
      }
    }
  }
  function getEvenSpacing(arr) {
    var len = arr.length;
    var i, diff;
    if (len < 2) {
      return false;
    }
    for (diff = arr[0], i = 1; i < len; ++i) {
      if (arr[i] - arr[i - 1] !== diff) {
        return false;
      }
    }
    return diff;
  }
  var reverseAlign = function reverseAlign(align) {
    return align === 'left' ? 'right' : align === 'right' ? 'left' : align;
  };
  var offsetFromEdge = function offsetFromEdge(scale, edge, offset) {
    return edge === 'top' || edge === 'left' ? scale[edge] + offset : scale[edge] - offset;
  };
  var getTicksLimit = function getTicksLimit(ticksLength, maxTicksLimit) {
    return Math.min(maxTicksLimit || ticksLength, ticksLength);
  };
  function sample(arr, numItems) {
    var result = [];
    var increment = arr.length / numItems;
    var len = arr.length;
    var i = 0;
    for (; i < len; i += increment) {
      result.push(arr[Math.floor(i)]);
    }
    return result;
  }
  function getPixelForGridLine(scale, index, offsetGridLines) {
    var length = scale.ticks.length;
    var validIndex = Math.min(index, length - 1);
    var start = scale._startPixel;
    var end = scale._endPixel;
    var epsilon = 1e-6;
    var lineValue = scale.getPixelForTick(validIndex);
    var offset;
    if (offsetGridLines) {
      if (length === 1) {
        offset = Math.max(lineValue - start, end - lineValue);
      } else if (index === 0) {
        offset = (scale.getPixelForTick(1) - lineValue) / 2;
      } else {
        offset = (lineValue - scale.getPixelForTick(validIndex - 1)) / 2;
      }
      lineValue += validIndex < index ? offset : -offset;
      if (lineValue < start - epsilon || lineValue > end + epsilon) {
        return;
      }
    }
    return lineValue;
  }
  function garbageCollect(caches, length) {
    each(caches, function (cache) {
      var gc = cache.gc;
      var gcLen = gc.length / 2;
      var i;
      if (gcLen > length) {
        for (i = 0; i < gcLen; ++i) {
          delete cache.data[gc[i]];
        }
        gc.splice(0, gcLen);
      }
    });
  }
  function getTickMarkLength(options) {
    return options.drawTicks ? options.tickLength : 0;
  }
  function getTitleHeight(options, fallback) {
    if (!options.display) {
      return 0;
    }
    var font = toFont(options.font, fallback);
    var padding = toPadding(options.padding);
    var lines = isArray(options.text) ? options.text.length : 1;
    return lines * font.lineHeight + padding.height;
  }
  function createScaleContext(parent, scale) {
    return createContext(parent, {
      scale: scale,
      type: 'scale'
    });
  }
  function createTickContext(parent, index, tick) {
    return createContext(parent, {
      tick: tick,
      index: index,
      type: 'tick'
    });
  }
  function titleAlign(align, position, reverse) {
    var ret = _toLeftRightCenter(align);
    if (reverse && position !== 'right' || !reverse && position === 'right') {
      ret = reverseAlign(ret);
    }
    return ret;
  }
  function titleArgs(scale, offset, position, align) {
    var top = scale.top,
      left = scale.left,
      bottom = scale.bottom,
      right = scale.right,
      chart = scale.chart;
    var chartArea = chart.chartArea,
      scales = chart.scales;
    var rotation = 0;
    var maxWidth, titleX, titleY;
    var height = bottom - top;
    var width = right - left;
    if (scale.isHorizontal()) {
      titleX = _alignStartEnd(align, left, right);
      if (isObject(position)) {
        var positionAxisID = Object.keys(position)[0];
        var value = position[positionAxisID];
        titleY = scales[positionAxisID].getPixelForValue(value) + height - offset;
      } else if (position === 'center') {
        titleY = (chartArea.bottom + chartArea.top) / 2 + height - offset;
      } else {
        titleY = offsetFromEdge(scale, position, offset);
      }
      maxWidth = right - left;
    } else {
      if (isObject(position)) {
        var _positionAxisID = Object.keys(position)[0];
        var _value = position[_positionAxisID];
        titleX = scales[_positionAxisID].getPixelForValue(_value) - width + offset;
      } else if (position === 'center') {
        titleX = (chartArea.left + chartArea.right) / 2 - width + offset;
      } else {
        titleX = offsetFromEdge(scale, position, offset);
      }
      titleY = _alignStartEnd(align, bottom, top);
      rotation = position === 'left' ? -HALF_PI : HALF_PI;
    }
    return {
      titleX: titleX,
      titleY: titleY,
      maxWidth: maxWidth,
      rotation: rotation
    };
  }
  var Scale = /*#__PURE__*/function (_Element2) {
    function Scale(cfg) {
      var _this9;
      _classCallCheck$1(this, Scale);
      _this9 = _callSuper(this, Scale);
      _this9.id = cfg.id;
      _this9.type = cfg.type;
      _this9.options = undefined;
      _this9.ctx = cfg.ctx;
      _this9.chart = cfg.chart;
      _this9.top = undefined;
      _this9.bottom = undefined;
      _this9.left = undefined;
      _this9.right = undefined;
      _this9.width = undefined;
      _this9.height = undefined;
      _this9._margins = {
        left: 0,
        right: 0,
        top: 0,
        bottom: 0
      };
      _this9.maxWidth = undefined;
      _this9.maxHeight = undefined;
      _this9.paddingTop = undefined;
      _this9.paddingBottom = undefined;
      _this9.paddingLeft = undefined;
      _this9.paddingRight = undefined;
      _this9.axis = undefined;
      _this9.labelRotation = undefined;
      _this9.min = undefined;
      _this9.max = undefined;
      _this9._range = undefined;
      _this9.ticks = [];
      _this9._gridLineItems = null;
      _this9._labelItems = null;
      _this9._labelSizes = null;
      _this9._length = 0;
      _this9._maxLength = 0;
      _this9._longestTextCache = {};
      _this9._startPixel = undefined;
      _this9._endPixel = undefined;
      _this9._reversePixels = false;
      _this9._userMax = undefined;
      _this9._userMin = undefined;
      _this9._suggestedMax = undefined;
      _this9._suggestedMin = undefined;
      _this9._ticksLength = 0;
      _this9._borderValue = 0;
      _this9._cache = {};
      _this9._dataLimitsCached = false;
      _this9.$context = undefined;
      return _this9;
    }
    _inherits$1(Scale, _Element2);
    return _createClass$1(Scale, [{
      key: "init",
      value: function init(options) {
        this.options = options.setContext(this.getContext());
        this.axis = options.axis;
        this._userMin = this.parse(options.min);
        this._userMax = this.parse(options.max);
        this._suggestedMin = this.parse(options.suggestedMin);
        this._suggestedMax = this.parse(options.suggestedMax);
      }
    }, {
      key: "parse",
      value: function parse(raw, index) {
        return raw;
      }
    }, {
      key: "getUserBounds",
      value: function getUserBounds() {
        var _userMin = this._userMin,
          _userMax = this._userMax,
          _suggestedMin = this._suggestedMin,
          _suggestedMax = this._suggestedMax;
        _userMin = finiteOrDefault(_userMin, Number.POSITIVE_INFINITY);
        _userMax = finiteOrDefault(_userMax, Number.NEGATIVE_INFINITY);
        _suggestedMin = finiteOrDefault(_suggestedMin, Number.POSITIVE_INFINITY);
        _suggestedMax = finiteOrDefault(_suggestedMax, Number.NEGATIVE_INFINITY);
        return {
          min: finiteOrDefault(_userMin, _suggestedMin),
          max: finiteOrDefault(_userMax, _suggestedMax),
          minDefined: isNumberFinite(_userMin),
          maxDefined: isNumberFinite(_userMax)
        };
      }
    }, {
      key: "getMinMax",
      value: function getMinMax(canStack) {
        var _this$getUserBounds = this.getUserBounds(),
          min = _this$getUserBounds.min,
          max = _this$getUserBounds.max,
          minDefined = _this$getUserBounds.minDefined,
          maxDefined = _this$getUserBounds.maxDefined;
        var range;
        if (minDefined && maxDefined) {
          return {
            min: min,
            max: max
          };
        }
        var metas = this.getMatchingVisibleMetas();
        for (var i = 0, ilen = metas.length; i < ilen; ++i) {
          range = metas[i].controller.getMinMax(this, canStack);
          if (!minDefined) {
            min = Math.min(min, range.min);
          }
          if (!maxDefined) {
            max = Math.max(max, range.max);
          }
        }
        min = maxDefined && min > max ? max : min;
        max = minDefined && min > max ? min : max;
        return {
          min: finiteOrDefault(min, finiteOrDefault(max, min)),
          max: finiteOrDefault(max, finiteOrDefault(min, max))
        };
      }
    }, {
      key: "getPadding",
      value: function getPadding() {
        return {
          left: this.paddingLeft || 0,
          top: this.paddingTop || 0,
          right: this.paddingRight || 0,
          bottom: this.paddingBottom || 0
        };
      }
    }, {
      key: "getTicks",
      value: function getTicks() {
        return this.ticks;
      }
    }, {
      key: "getLabels",
      value: function getLabels() {
        var data = this.chart.data;
        return this.options.labels || (this.isHorizontal() ? data.xLabels : data.yLabels) || data.labels || [];
      }
    }, {
      key: "getLabelItems",
      value: function getLabelItems() {
        var chartArea = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : this.chart.chartArea;
        var items = this._labelItems || (this._labelItems = this._computeLabelItems(chartArea));
        return items;
      }
    }, {
      key: "beforeLayout",
      value: function beforeLayout() {
        this._cache = {};
        this._dataLimitsCached = false;
      }
    }, {
      key: "beforeUpdate",
      value: function beforeUpdate() {
        callback(this.options.beforeUpdate, [this]);
      }
    }, {
      key: "update",
      value: function update(maxWidth, maxHeight, margins) {
        var _this$options4 = this.options,
          beginAtZero = _this$options4.beginAtZero,
          grace = _this$options4.grace,
          tickOpts = _this$options4.ticks;
        var sampleSize = tickOpts.sampleSize;
        this.beforeUpdate();
        this.maxWidth = maxWidth;
        this.maxHeight = maxHeight;
        this._margins = margins = Object.assign({
          left: 0,
          right: 0,
          top: 0,
          bottom: 0
        }, margins);
        this.ticks = null;
        this._labelSizes = null;
        this._gridLineItems = null;
        this._labelItems = null;
        this.beforeSetDimensions();
        this.setDimensions();
        this.afterSetDimensions();
        this._maxLength = this.isHorizontal() ? this.width + margins.left + margins.right : this.height + margins.top + margins.bottom;
        if (!this._dataLimitsCached) {
          this.beforeDataLimits();
          this.determineDataLimits();
          this.afterDataLimits();
          this._range = _addGrace(this, grace, beginAtZero);
          this._dataLimitsCached = true;
        }
        this.beforeBuildTicks();
        this.ticks = this.buildTicks() || [];
        this.afterBuildTicks();
        var samplingEnabled = sampleSize < this.ticks.length;
        this._convertTicksToLabels(samplingEnabled ? sample(this.ticks, sampleSize) : this.ticks);
        this.configure();
        this.beforeCalculateLabelRotation();
        this.calculateLabelRotation();
        this.afterCalculateLabelRotation();
        if (tickOpts.display && (tickOpts.autoSkip || tickOpts.source === 'auto')) {
          this.ticks = autoSkip(this, this.ticks);
          this._labelSizes = null;
          this.afterAutoSkip();
        }
        if (samplingEnabled) {
          this._convertTicksToLabels(this.ticks);
        }
        this.beforeFit();
        this.fit();
        this.afterFit();
        this.afterUpdate();
      }
    }, {
      key: "configure",
      value: function configure() {
        var reversePixels = this.options.reverse;
        var startPixel, endPixel;
        if (this.isHorizontal()) {
          startPixel = this.left;
          endPixel = this.right;
        } else {
          startPixel = this.top;
          endPixel = this.bottom;
          reversePixels = !reversePixels;
        }
        this._startPixel = startPixel;
        this._endPixel = endPixel;
        this._reversePixels = reversePixels;
        this._length = endPixel - startPixel;
        this._alignToPixels = this.options.alignToPixels;
      }
    }, {
      key: "afterUpdate",
      value: function afterUpdate() {
        callback(this.options.afterUpdate, [this]);
      }
    }, {
      key: "beforeSetDimensions",
      value: function beforeSetDimensions() {
        callback(this.options.beforeSetDimensions, [this]);
      }
    }, {
      key: "setDimensions",
      value: function setDimensions() {
        if (this.isHorizontal()) {
          this.width = this.maxWidth;
          this.left = 0;
          this.right = this.width;
        } else {
          this.height = this.maxHeight;
          this.top = 0;
          this.bottom = this.height;
        }
        this.paddingLeft = 0;
        this.paddingTop = 0;
        this.paddingRight = 0;
        this.paddingBottom = 0;
      }
    }, {
      key: "afterSetDimensions",
      value: function afterSetDimensions() {
        callback(this.options.afterSetDimensions, [this]);
      }
    }, {
      key: "_callHooks",
      value: function _callHooks(name) {
        this.chart.notifyPlugins(name, this.getContext());
        callback(this.options[name], [this]);
      }
    }, {
      key: "beforeDataLimits",
      value: function beforeDataLimits() {
        this._callHooks('beforeDataLimits');
      }
    }, {
      key: "determineDataLimits",
      value: function determineDataLimits() {}
    }, {
      key: "afterDataLimits",
      value: function afterDataLimits() {
        this._callHooks('afterDataLimits');
      }
    }, {
      key: "beforeBuildTicks",
      value: function beforeBuildTicks() {
        this._callHooks('beforeBuildTicks');
      }
    }, {
      key: "buildTicks",
      value: function buildTicks() {
        return [];
      }
    }, {
      key: "afterBuildTicks",
      value: function afterBuildTicks() {
        this._callHooks('afterBuildTicks');
      }
    }, {
      key: "beforeTickToLabelConversion",
      value: function beforeTickToLabelConversion() {
        callback(this.options.beforeTickToLabelConversion, [this]);
      }
    }, {
      key: "generateTickLabels",
      value: function generateTickLabels(ticks) {
        var tickOpts = this.options.ticks;
        var i, ilen, tick;
        for (i = 0, ilen = ticks.length; i < ilen; i++) {
          tick = ticks[i];
          tick.label = callback(tickOpts.callback, [tick.value, i, ticks], this);
        }
      }
    }, {
      key: "afterTickToLabelConversion",
      value: function afterTickToLabelConversion() {
        callback(this.options.afterTickToLabelConversion, [this]);
      }
    }, {
      key: "beforeCalculateLabelRotation",
      value: function beforeCalculateLabelRotation() {
        callback(this.options.beforeCalculateLabelRotation, [this]);
      }
    }, {
      key: "calculateLabelRotation",
      value: function calculateLabelRotation() {
        var options = this.options;
        var tickOpts = options.ticks;
        var numTicks = getTicksLimit(this.ticks.length, options.ticks.maxTicksLimit);
        var minRotation = tickOpts.minRotation || 0;
        var maxRotation = tickOpts.maxRotation;
        var labelRotation = minRotation;
        var tickWidth, maxHeight, maxLabelDiagonal;
        if (!this._isVisible() || !tickOpts.display || minRotation >= maxRotation || numTicks <= 1 || !this.isHorizontal()) {
          this.labelRotation = minRotation;
          return;
        }
        var labelSizes = this._getLabelSizes();
        var maxLabelWidth = labelSizes.widest.width;
        var maxLabelHeight = labelSizes.highest.height;
        var maxWidth = _limitValue(this.chart.width - maxLabelWidth, 0, this.maxWidth);
        tickWidth = options.offset ? this.maxWidth / numTicks : maxWidth / (numTicks - 1);
        if (maxLabelWidth + 6 > tickWidth) {
          tickWidth = maxWidth / (numTicks - (options.offset ? 0.5 : 1));
          maxHeight = this.maxHeight - getTickMarkLength(options.grid) - tickOpts.padding - getTitleHeight(options.title, this.chart.options.font);
          maxLabelDiagonal = Math.sqrt(maxLabelWidth * maxLabelWidth + maxLabelHeight * maxLabelHeight);
          labelRotation = toDegrees(Math.min(Math.asin(_limitValue((labelSizes.highest.height + 6) / tickWidth, -1, 1)), Math.asin(_limitValue(maxHeight / maxLabelDiagonal, -1, 1)) - Math.asin(_limitValue(maxLabelHeight / maxLabelDiagonal, -1, 1))));
          labelRotation = Math.max(minRotation, Math.min(maxRotation, labelRotation));
        }
        this.labelRotation = labelRotation;
      }
    }, {
      key: "afterCalculateLabelRotation",
      value: function afterCalculateLabelRotation() {
        callback(this.options.afterCalculateLabelRotation, [this]);
      }
    }, {
      key: "afterAutoSkip",
      value: function afterAutoSkip() {}
    }, {
      key: "beforeFit",
      value: function beforeFit() {
        callback(this.options.beforeFit, [this]);
      }
    }, {
      key: "fit",
      value: function fit() {
        var minSize = {
          width: 0,
          height: 0
        };
        var chart = this.chart,
          _this$options5 = this.options,
          tickOpts = _this$options5.ticks,
          titleOpts = _this$options5.title,
          gridOpts = _this$options5.grid;
        var display = this._isVisible();
        var isHorizontal = this.isHorizontal();
        if (display) {
          var titleHeight = getTitleHeight(titleOpts, chart.options.font);
          if (isHorizontal) {
            minSize.width = this.maxWidth;
            minSize.height = getTickMarkLength(gridOpts) + titleHeight;
          } else {
            minSize.height = this.maxHeight;
            minSize.width = getTickMarkLength(gridOpts) + titleHeight;
          }
          if (tickOpts.display && this.ticks.length) {
            var _this$_getLabelSizes = this._getLabelSizes(),
              first = _this$_getLabelSizes.first,
              last = _this$_getLabelSizes.last,
              widest = _this$_getLabelSizes.widest,
              highest = _this$_getLabelSizes.highest;
            var tickPadding = tickOpts.padding * 2;
            var angleRadians = toRadians(this.labelRotation);
            var cos = Math.cos(angleRadians);
            var sin = Math.sin(angleRadians);
            if (isHorizontal) {
              var labelHeight = tickOpts.mirror ? 0 : sin * widest.width + cos * highest.height;
              minSize.height = Math.min(this.maxHeight, minSize.height + labelHeight + tickPadding);
            } else {
              var labelWidth = tickOpts.mirror ? 0 : cos * widest.width + sin * highest.height;
              minSize.width = Math.min(this.maxWidth, minSize.width + labelWidth + tickPadding);
            }
            this._calculatePadding(first, last, sin, cos);
          }
        }
        this._handleMargins();
        if (isHorizontal) {
          this.width = this._length = chart.width - this._margins.left - this._margins.right;
          this.height = minSize.height;
        } else {
          this.width = minSize.width;
          this.height = this._length = chart.height - this._margins.top - this._margins.bottom;
        }
      }
    }, {
      key: "_calculatePadding",
      value: function _calculatePadding(first, last, sin, cos) {
        var _this$options6 = this.options,
          _this$options6$ticks = _this$options6.ticks,
          align = _this$options6$ticks.align,
          padding = _this$options6$ticks.padding,
          position = _this$options6.position;
        var isRotated = this.labelRotation !== 0;
        var labelsBelowTicks = position !== 'top' && this.axis === 'x';
        if (this.isHorizontal()) {
          var offsetLeft = this.getPixelForTick(0) - this.left;
          var offsetRight = this.right - this.getPixelForTick(this.ticks.length - 1);
          var paddingLeft = 0;
          var paddingRight = 0;
          if (isRotated) {
            if (labelsBelowTicks) {
              paddingLeft = cos * first.width;
              paddingRight = sin * last.height;
            } else {
              paddingLeft = sin * first.height;
              paddingRight = cos * last.width;
            }
          } else if (align === 'start') {
            paddingRight = last.width;
          } else if (align === 'end') {
            paddingLeft = first.width;
          } else if (align !== 'inner') {
            paddingLeft = first.width / 2;
            paddingRight = last.width / 2;
          }
          this.paddingLeft = Math.max((paddingLeft - offsetLeft + padding) * this.width / (this.width - offsetLeft), 0);
          this.paddingRight = Math.max((paddingRight - offsetRight + padding) * this.width / (this.width - offsetRight), 0);
        } else {
          var paddingTop = last.height / 2;
          var paddingBottom = first.height / 2;
          if (align === 'start') {
            paddingTop = 0;
            paddingBottom = first.height;
          } else if (align === 'end') {
            paddingTop = last.height;
            paddingBottom = 0;
          }
          this.paddingTop = paddingTop + padding;
          this.paddingBottom = paddingBottom + padding;
        }
      }
    }, {
      key: "_handleMargins",
      value: function _handleMargins() {
        if (this._margins) {
          this._margins.left = Math.max(this.paddingLeft, this._margins.left);
          this._margins.top = Math.max(this.paddingTop, this._margins.top);
          this._margins.right = Math.max(this.paddingRight, this._margins.right);
          this._margins.bottom = Math.max(this.paddingBottom, this._margins.bottom);
        }
      }
    }, {
      key: "afterFit",
      value: function afterFit() {
        callback(this.options.afterFit, [this]);
      }
    }, {
      key: "isHorizontal",
      value: function isHorizontal() {
        var _this$options7 = this.options,
          axis = _this$options7.axis,
          position = _this$options7.position;
        return position === 'top' || position === 'bottom' || axis === 'x';
      }
    }, {
      key: "isFullSize",
      value: function isFullSize() {
        return this.options.fullSize;
      }
    }, {
      key: "_convertTicksToLabels",
      value: function _convertTicksToLabels(ticks) {
        this.beforeTickToLabelConversion();
        this.generateTickLabels(ticks);
        var i, ilen;
        for (i = 0, ilen = ticks.length; i < ilen; i++) {
          if (isNullOrUndef(ticks[i].label)) {
            ticks.splice(i, 1);
            ilen--;
            i--;
          }
        }
        this.afterTickToLabelConversion();
      }
    }, {
      key: "_getLabelSizes",
      value: function _getLabelSizes() {
        var labelSizes = this._labelSizes;
        if (!labelSizes) {
          var sampleSize = this.options.ticks.sampleSize;
          var ticks = this.ticks;
          if (sampleSize < ticks.length) {
            ticks = sample(ticks, sampleSize);
          }
          this._labelSizes = labelSizes = this._computeLabelSizes(ticks, ticks.length, this.options.ticks.maxTicksLimit);
        }
        return labelSizes;
      }
    }, {
      key: "_computeLabelSizes",
      value: function _computeLabelSizes(ticks, length, maxTicksLimit) {
        var ctx = this.ctx,
          caches = this._longestTextCache;
        var widths = [];
        var heights = [];
        var increment = Math.floor(length / getTicksLimit(length, maxTicksLimit));
        var widestLabelSize = 0;
        var highestLabelSize = 0;
        var i, j, jlen, label, tickFont, fontString, cache, lineHeight, width, height, nestedLabel;
        for (i = 0; i < length; i += increment) {
          label = ticks[i].label;
          tickFont = this._resolveTickFontOptions(i);
          ctx.font = fontString = tickFont.string;
          cache = caches[fontString] = caches[fontString] || {
            data: {},
            gc: []
          };
          lineHeight = tickFont.lineHeight;
          width = height = 0;
          if (!isNullOrUndef(label) && !isArray(label)) {
            width = _measureText(ctx, cache.data, cache.gc, width, label);
            height = lineHeight;
          } else if (isArray(label)) {
            for (j = 0, jlen = label.length; j < jlen; ++j) {
              nestedLabel = label[j];
              if (!isNullOrUndef(nestedLabel) && !isArray(nestedLabel)) {
                width = _measureText(ctx, cache.data, cache.gc, width, nestedLabel);
                height += lineHeight;
              }
            }
          }
          widths.push(width);
          heights.push(height);
          widestLabelSize = Math.max(width, widestLabelSize);
          highestLabelSize = Math.max(height, highestLabelSize);
        }
        garbageCollect(caches, length);
        var widest = widths.indexOf(widestLabelSize);
        var highest = heights.indexOf(highestLabelSize);
        var valueAt = function valueAt(idx) {
          return {
            width: widths[idx] || 0,
            height: heights[idx] || 0
          };
        };
        return {
          first: valueAt(0),
          last: valueAt(length - 1),
          widest: valueAt(widest),
          highest: valueAt(highest),
          widths: widths,
          heights: heights
        };
      }
    }, {
      key: "getLabelForValue",
      value: function getLabelForValue(value) {
        return value;
      }
    }, {
      key: "getPixelForValue",
      value: function getPixelForValue(value, index) {
        return NaN;
      }
    }, {
      key: "getValueForPixel",
      value: function getValueForPixel(pixel) {}
    }, {
      key: "getPixelForTick",
      value: function getPixelForTick(index) {
        var ticks = this.ticks;
        if (index < 0 || index > ticks.length - 1) {
          return null;
        }
        return this.getPixelForValue(ticks[index].value);
      }
    }, {
      key: "getPixelForDecimal",
      value: function getPixelForDecimal(decimal) {
        if (this._reversePixels) {
          decimal = 1 - decimal;
        }
        var pixel = this._startPixel + decimal * this._length;
        return _int16Range(this._alignToPixels ? _alignPixel(this.chart, pixel, 0) : pixel);
      }
    }, {
      key: "getDecimalForPixel",
      value: function getDecimalForPixel(pixel) {
        var decimal = (pixel - this._startPixel) / this._length;
        return this._reversePixels ? 1 - decimal : decimal;
      }
    }, {
      key: "getBasePixel",
      value: function getBasePixel() {
        return this.getPixelForValue(this.getBaseValue());
      }
    }, {
      key: "getBaseValue",
      value: function getBaseValue() {
        var min = this.min,
          max = this.max;
        return min < 0 && max < 0 ? max : min > 0 && max > 0 ? min : 0;
      }
    }, {
      key: "getContext",
      value: function getContext(index) {
        var ticks = this.ticks || [];
        if (index >= 0 && index < ticks.length) {
          var tick = ticks[index];
          return tick.$context || (tick.$context = createTickContext(this.getContext(), index, tick));
        }
        return this.$context || (this.$context = createScaleContext(this.chart.getContext(), this));
      }
    }, {
      key: "_tickSize",
      value: function _tickSize() {
        var optionTicks = this.options.ticks;
        var rot = toRadians(this.labelRotation);
        var cos = Math.abs(Math.cos(rot));
        var sin = Math.abs(Math.sin(rot));
        var labelSizes = this._getLabelSizes();
        var padding = optionTicks.autoSkipPadding || 0;
        var w = labelSizes ? labelSizes.widest.width + padding : 0;
        var h = labelSizes ? labelSizes.highest.height + padding : 0;
        return this.isHorizontal() ? h * cos > w * sin ? w / cos : h / sin : h * sin < w * cos ? h / cos : w / sin;
      }
    }, {
      key: "_isVisible",
      value: function _isVisible() {
        var display = this.options.display;
        if (display !== 'auto') {
          return !!display;
        }
        return this.getMatchingVisibleMetas().length > 0;
      }
    }, {
      key: "_computeGridLineItems",
      value: function _computeGridLineItems(chartArea) {
        var axis = this.axis;
        var chart = this.chart;
        var options = this.options;
        var grid = options.grid,
          position = options.position,
          border = options.border;
        var offset = grid.offset;
        var isHorizontal = this.isHorizontal();
        var ticks = this.ticks;
        var ticksLength = ticks.length + (offset ? 1 : 0);
        var tl = getTickMarkLength(grid);
        var items = [];
        var borderOpts = border.setContext(this.getContext());
        var axisWidth = borderOpts.display ? borderOpts.width : 0;
        var axisHalfWidth = axisWidth / 2;
        var alignBorderValue = function alignBorderValue(pixel) {
          return _alignPixel(chart, pixel, axisWidth);
        };
        var borderValue, i, lineValue, alignedLineValue;
        var tx1, ty1, tx2, ty2, x1, y1, x2, y2;
        if (position === 'top') {
          borderValue = alignBorderValue(this.bottom);
          ty1 = this.bottom - tl;
          ty2 = borderValue - axisHalfWidth;
          y1 = alignBorderValue(chartArea.top) + axisHalfWidth;
          y2 = chartArea.bottom;
        } else if (position === 'bottom') {
          borderValue = alignBorderValue(this.top);
          y1 = chartArea.top;
          y2 = alignBorderValue(chartArea.bottom) - axisHalfWidth;
          ty1 = borderValue + axisHalfWidth;
          ty2 = this.top + tl;
        } else if (position === 'left') {
          borderValue = alignBorderValue(this.right);
          tx1 = this.right - tl;
          tx2 = borderValue - axisHalfWidth;
          x1 = alignBorderValue(chartArea.left) + axisHalfWidth;
          x2 = chartArea.right;
        } else if (position === 'right') {
          borderValue = alignBorderValue(this.left);
          x1 = chartArea.left;
          x2 = alignBorderValue(chartArea.right) - axisHalfWidth;
          tx1 = borderValue + axisHalfWidth;
          tx2 = this.left + tl;
        } else if (axis === 'x') {
          if (position === 'center') {
            borderValue = alignBorderValue((chartArea.top + chartArea.bottom) / 2 + 0.5);
          } else if (isObject(position)) {
            var positionAxisID = Object.keys(position)[0];
            var value = position[positionAxisID];
            borderValue = alignBorderValue(this.chart.scales[positionAxisID].getPixelForValue(value));
          }
          y1 = chartArea.top;
          y2 = chartArea.bottom;
          ty1 = borderValue + axisHalfWidth;
          ty2 = ty1 + tl;
        } else if (axis === 'y') {
          if (position === 'center') {
            borderValue = alignBorderValue((chartArea.left + chartArea.right) / 2);
          } else if (isObject(position)) {
            var _positionAxisID2 = Object.keys(position)[0];
            var _value2 = position[_positionAxisID2];
            borderValue = alignBorderValue(this.chart.scales[_positionAxisID2].getPixelForValue(_value2));
          }
          tx1 = borderValue - axisHalfWidth;
          tx2 = tx1 - tl;
          x1 = chartArea.left;
          x2 = chartArea.right;
        }
        var limit = valueOrDefault(options.ticks.maxTicksLimit, ticksLength);
        var step = Math.max(1, Math.ceil(ticksLength / limit));
        for (i = 0; i < ticksLength; i += step) {
          var context = this.getContext(i);
          var optsAtIndex = grid.setContext(context);
          var optsAtIndexBorder = border.setContext(context);
          var lineWidth = optsAtIndex.lineWidth;
          var lineColor = optsAtIndex.color;
          var borderDash = optsAtIndexBorder.dash || [];
          var borderDashOffset = optsAtIndexBorder.dashOffset;
          var tickWidth = optsAtIndex.tickWidth;
          var tickColor = optsAtIndex.tickColor;
          var tickBorderDash = optsAtIndex.tickBorderDash || [];
          var tickBorderDashOffset = optsAtIndex.tickBorderDashOffset;
          lineValue = getPixelForGridLine(this, i, offset);
          if (lineValue === undefined) {
            continue;
          }
          alignedLineValue = _alignPixel(chart, lineValue, lineWidth);
          if (isHorizontal) {
            tx1 = tx2 = x1 = x2 = alignedLineValue;
          } else {
            ty1 = ty2 = y1 = y2 = alignedLineValue;
          }
          items.push({
            tx1: tx1,
            ty1: ty1,
            tx2: tx2,
            ty2: ty2,
            x1: x1,
            y1: y1,
            x2: x2,
            y2: y2,
            width: lineWidth,
            color: lineColor,
            borderDash: borderDash,
            borderDashOffset: borderDashOffset,
            tickWidth: tickWidth,
            tickColor: tickColor,
            tickBorderDash: tickBorderDash,
            tickBorderDashOffset: tickBorderDashOffset
          });
        }
        this._ticksLength = ticksLength;
        this._borderValue = borderValue;
        return items;
      }
    }, {
      key: "_computeLabelItems",
      value: function _computeLabelItems(chartArea) {
        var axis = this.axis;
        var options = this.options;
        var position = options.position,
          optionTicks = options.ticks;
        var isHorizontal = this.isHorizontal();
        var ticks = this.ticks;
        var align = optionTicks.align,
          crossAlign = optionTicks.crossAlign,
          padding = optionTicks.padding,
          mirror = optionTicks.mirror;
        var tl = getTickMarkLength(options.grid);
        var tickAndPadding = tl + padding;
        var hTickAndPadding = mirror ? -padding : tickAndPadding;
        var rotation = -toRadians(this.labelRotation);
        var items = [];
        var i, ilen, tick, label, x, y, textAlign, pixel, font, lineHeight, lineCount, textOffset;
        var textBaseline = 'middle';
        if (position === 'top') {
          y = this.bottom - hTickAndPadding;
          textAlign = this._getXAxisLabelAlignment();
        } else if (position === 'bottom') {
          y = this.top + hTickAndPadding;
          textAlign = this._getXAxisLabelAlignment();
        } else if (position === 'left') {
          var ret = this._getYAxisLabelAlignment(tl);
          textAlign = ret.textAlign;
          x = ret.x;
        } else if (position === 'right') {
          var _ret = this._getYAxisLabelAlignment(tl);
          textAlign = _ret.textAlign;
          x = _ret.x;
        } else if (axis === 'x') {
          if (position === 'center') {
            y = (chartArea.top + chartArea.bottom) / 2 + tickAndPadding;
          } else if (isObject(position)) {
            var positionAxisID = Object.keys(position)[0];
            var value = position[positionAxisID];
            y = this.chart.scales[positionAxisID].getPixelForValue(value) + tickAndPadding;
          }
          textAlign = this._getXAxisLabelAlignment();
        } else if (axis === 'y') {
          if (position === 'center') {
            x = (chartArea.left + chartArea.right) / 2 - tickAndPadding;
          } else if (isObject(position)) {
            var _positionAxisID3 = Object.keys(position)[0];
            var _value3 = position[_positionAxisID3];
            x = this.chart.scales[_positionAxisID3].getPixelForValue(_value3);
          }
          textAlign = this._getYAxisLabelAlignment(tl).textAlign;
        }
        if (axis === 'y') {
          if (align === 'start') {
            textBaseline = 'top';
          } else if (align === 'end') {
            textBaseline = 'bottom';
          }
        }
        var labelSizes = this._getLabelSizes();
        for (i = 0, ilen = ticks.length; i < ilen; ++i) {
          tick = ticks[i];
          label = tick.label;
          var optsAtIndex = optionTicks.setContext(this.getContext(i));
          pixel = this.getPixelForTick(i) + optionTicks.labelOffset;
          font = this._resolveTickFontOptions(i);
          lineHeight = font.lineHeight;
          lineCount = isArray(label) ? label.length : 1;
          var halfCount = lineCount / 2;
          var color = optsAtIndex.color;
          var strokeColor = optsAtIndex.textStrokeColor;
          var strokeWidth = optsAtIndex.textStrokeWidth;
          var tickTextAlign = textAlign;
          if (isHorizontal) {
            x = pixel;
            if (textAlign === 'inner') {
              if (i === ilen - 1) {
                tickTextAlign = !this.options.reverse ? 'right' : 'left';
              } else if (i === 0) {
                tickTextAlign = !this.options.reverse ? 'left' : 'right';
              } else {
                tickTextAlign = 'center';
              }
            }
            if (position === 'top') {
              if (crossAlign === 'near' || rotation !== 0) {
                textOffset = -lineCount * lineHeight + lineHeight / 2;
              } else if (crossAlign === 'center') {
                textOffset = -labelSizes.highest.height / 2 - halfCount * lineHeight + lineHeight;
              } else {
                textOffset = -labelSizes.highest.height + lineHeight / 2;
              }
            } else {
              if (crossAlign === 'near' || rotation !== 0) {
                textOffset = lineHeight / 2;
              } else if (crossAlign === 'center') {
                textOffset = labelSizes.highest.height / 2 - halfCount * lineHeight;
              } else {
                textOffset = labelSizes.highest.height - lineCount * lineHeight;
              }
            }
            if (mirror) {
              textOffset *= -1;
            }
            if (rotation !== 0 && !optsAtIndex.showLabelBackdrop) {
              x += lineHeight / 2 * Math.sin(rotation);
            }
          } else {
            y = pixel;
            textOffset = (1 - lineCount) * lineHeight / 2;
          }
          var backdrop = void 0;
          if (optsAtIndex.showLabelBackdrop) {
            var labelPadding = toPadding(optsAtIndex.backdropPadding);
            var height = labelSizes.heights[i];
            var width = labelSizes.widths[i];
            var top = textOffset - labelPadding.top;
            var left = 0 - labelPadding.left;
            switch (textBaseline) {
              case 'middle':
                top -= height / 2;
                break;
              case 'bottom':
                top -= height;
                break;
            }
            switch (textAlign) {
              case 'center':
                left -= width / 2;
                break;
              case 'right':
                left -= width;
                break;
              case 'inner':
                if (i === ilen - 1) {
                  left -= width;
                } else if (i > 0) {
                  left -= width / 2;
                }
                break;
            }
            backdrop = {
              left: left,
              top: top,
              width: width + labelPadding.width,
              height: height + labelPadding.height,
              color: optsAtIndex.backdropColor
            };
          }
          items.push({
            label: label,
            font: font,
            textOffset: textOffset,
            options: {
              rotation: rotation,
              color: color,
              strokeColor: strokeColor,
              strokeWidth: strokeWidth,
              textAlign: tickTextAlign,
              textBaseline: textBaseline,
              translation: [x, y],
              backdrop: backdrop
            }
          });
        }
        return items;
      }
    }, {
      key: "_getXAxisLabelAlignment",
      value: function _getXAxisLabelAlignment() {
        var _this$options8 = this.options,
          position = _this$options8.position,
          ticks = _this$options8.ticks;
        var rotation = -toRadians(this.labelRotation);
        if (rotation) {
          return position === 'top' ? 'left' : 'right';
        }
        var align = 'center';
        if (ticks.align === 'start') {
          align = 'left';
        } else if (ticks.align === 'end') {
          align = 'right';
        } else if (ticks.align === 'inner') {
          align = 'inner';
        }
        return align;
      }
    }, {
      key: "_getYAxisLabelAlignment",
      value: function _getYAxisLabelAlignment(tl) {
        var _this$options9 = this.options,
          position = _this$options9.position,
          _this$options9$ticks = _this$options9.ticks,
          crossAlign = _this$options9$ticks.crossAlign,
          mirror = _this$options9$ticks.mirror,
          padding = _this$options9$ticks.padding;
        var labelSizes = this._getLabelSizes();
        var tickAndPadding = tl + padding;
        var widest = labelSizes.widest.width;
        var textAlign;
        var x;
        if (position === 'left') {
          if (mirror) {
            x = this.right + padding;
            if (crossAlign === 'near') {
              textAlign = 'left';
            } else if (crossAlign === 'center') {
              textAlign = 'center';
              x += widest / 2;
            } else {
              textAlign = 'right';
              x += widest;
            }
          } else {
            x = this.right - tickAndPadding;
            if (crossAlign === 'near') {
              textAlign = 'right';
            } else if (crossAlign === 'center') {
              textAlign = 'center';
              x -= widest / 2;
            } else {
              textAlign = 'left';
              x = this.left;
            }
          }
        } else if (position === 'right') {
          if (mirror) {
            x = this.left + padding;
            if (crossAlign === 'near') {
              textAlign = 'right';
            } else if (crossAlign === 'center') {
              textAlign = 'center';
              x -= widest / 2;
            } else {
              textAlign = 'left';
              x -= widest;
            }
          } else {
            x = this.left + tickAndPadding;
            if (crossAlign === 'near') {
              textAlign = 'left';
            } else if (crossAlign === 'center') {
              textAlign = 'center';
              x += widest / 2;
            } else {
              textAlign = 'right';
              x = this.right;
            }
          }
        } else {
          textAlign = 'right';
        }
        return {
          textAlign: textAlign,
          x: x
        };
      }
    }, {
      key: "_computeLabelArea",
      value: function _computeLabelArea() {
        if (this.options.ticks.mirror) {
          return;
        }
        var chart = this.chart;
        var position = this.options.position;
        if (position === 'left' || position === 'right') {
          return {
            top: 0,
            left: this.left,
            bottom: chart.height,
            right: this.right
          };
        }
        if (position === 'top' || position === 'bottom') {
          return {
            top: this.top,
            left: 0,
            bottom: this.bottom,
            right: chart.width
          };
        }
      }
    }, {
      key: "drawBackground",
      value: function drawBackground() {
        var ctx = this.ctx,
          backgroundColor = this.options.backgroundColor,
          left = this.left,
          top = this.top,
          width = this.width,
          height = this.height;
        if (backgroundColor) {
          ctx.save();
          ctx.fillStyle = backgroundColor;
          ctx.fillRect(left, top, width, height);
          ctx.restore();
        }
      }
    }, {
      key: "getLineWidthForValue",
      value: function getLineWidthForValue(value) {
        var grid = this.options.grid;
        if (!this._isVisible() || !grid.display) {
          return 0;
        }
        var ticks = this.ticks;
        var index = ticks.findIndex(function (t) {
          return t.value === value;
        });
        if (index >= 0) {
          var opts = grid.setContext(this.getContext(index));
          return opts.lineWidth;
        }
        return 0;
      }
    }, {
      key: "drawGrid",
      value: function drawGrid(chartArea) {
        var grid = this.options.grid;
        var ctx = this.ctx;
        var items = this._gridLineItems || (this._gridLineItems = this._computeGridLineItems(chartArea));
        var i, ilen;
        var drawLine = function drawLine(p1, p2, style) {
          if (!style.width || !style.color) {
            return;
          }
          ctx.save();
          ctx.lineWidth = style.width;
          ctx.strokeStyle = style.color;
          ctx.setLineDash(style.borderDash || []);
          ctx.lineDashOffset = style.borderDashOffset;
          ctx.beginPath();
          ctx.moveTo(p1.x, p1.y);
          ctx.lineTo(p2.x, p2.y);
          ctx.stroke();
          ctx.restore();
        };
        if (grid.display) {
          for (i = 0, ilen = items.length; i < ilen; ++i) {
            var item = items[i];
            if (grid.drawOnChartArea) {
              drawLine({
                x: item.x1,
                y: item.y1
              }, {
                x: item.x2,
                y: item.y2
              }, item);
            }
            if (grid.drawTicks) {
              drawLine({
                x: item.tx1,
                y: item.ty1
              }, {
                x: item.tx2,
                y: item.ty2
              }, {
                color: item.tickColor,
                width: item.tickWidth,
                borderDash: item.tickBorderDash,
                borderDashOffset: item.tickBorderDashOffset
              });
            }
          }
        }
      }
    }, {
      key: "drawBorder",
      value: function drawBorder() {
        var chart = this.chart,
          ctx = this.ctx,
          _this$options10 = this.options,
          border = _this$options10.border,
          grid = _this$options10.grid;
        var borderOpts = border.setContext(this.getContext());
        var axisWidth = border.display ? borderOpts.width : 0;
        if (!axisWidth) {
          return;
        }
        var lastLineWidth = grid.setContext(this.getContext(0)).lineWidth;
        var borderValue = this._borderValue;
        var x1, x2, y1, y2;
        if (this.isHorizontal()) {
          x1 = _alignPixel(chart, this.left, axisWidth) - axisWidth / 2;
          x2 = _alignPixel(chart, this.right, lastLineWidth) + lastLineWidth / 2;
          y1 = y2 = borderValue;
        } else {
          y1 = _alignPixel(chart, this.top, axisWidth) - axisWidth / 2;
          y2 = _alignPixel(chart, this.bottom, lastLineWidth) + lastLineWidth / 2;
          x1 = x2 = borderValue;
        }
        ctx.save();
        ctx.lineWidth = borderOpts.width;
        ctx.strokeStyle = borderOpts.color;
        ctx.beginPath();
        ctx.moveTo(x1, y1);
        ctx.lineTo(x2, y2);
        ctx.stroke();
        ctx.restore();
      }
    }, {
      key: "drawLabels",
      value: function drawLabels(chartArea) {
        var optionTicks = this.options.ticks;
        if (!optionTicks.display) {
          return;
        }
        var ctx = this.ctx;
        var area = this._computeLabelArea();
        if (area) {
          clipArea(ctx, area);
        }
        var items = this.getLabelItems(chartArea);
        var _iterator10 = _createForOfIteratorHelper$1(items),
          _step10;
        try {
          for (_iterator10.s(); !(_step10 = _iterator10.n()).done;) {
            var item = _step10.value;
            var renderTextOptions = item.options;
            var tickFont = item.font;
            var label = item.label;
            var y = item.textOffset;
            renderText(ctx, label, 0, y, tickFont, renderTextOptions);
          }
        } catch (err) {
          _iterator10.e(err);
        } finally {
          _iterator10.f();
        }
        if (area) {
          unclipArea(ctx);
        }
      }
    }, {
      key: "drawTitle",
      value: function drawTitle() {
        var ctx = this.ctx,
          _this$options11 = this.options,
          position = _this$options11.position,
          title = _this$options11.title,
          reverse = _this$options11.reverse;
        if (!title.display) {
          return;
        }
        var font = toFont(title.font);
        var padding = toPadding(title.padding);
        var align = title.align;
        var offset = font.lineHeight / 2;
        if (position === 'bottom' || position === 'center' || isObject(position)) {
          offset += padding.bottom;
          if (isArray(title.text)) {
            offset += font.lineHeight * (title.text.length - 1);
          }
        } else {
          offset += padding.top;
        }
        var _titleArgs = titleArgs(this, offset, position, align),
          titleX = _titleArgs.titleX,
          titleY = _titleArgs.titleY,
          maxWidth = _titleArgs.maxWidth,
          rotation = _titleArgs.rotation;
        renderText(ctx, title.text, 0, 0, font, {
          color: title.color,
          maxWidth: maxWidth,
          rotation: rotation,
          textAlign: titleAlign(align, position, reverse),
          textBaseline: 'middle',
          translation: [titleX, titleY]
        });
      }
    }, {
      key: "draw",
      value: function draw(chartArea) {
        if (!this._isVisible()) {
          return;
        }
        this.drawBackground();
        this.drawGrid(chartArea);
        this.drawBorder();
        this.drawTitle();
        this.drawLabels(chartArea);
      }
    }, {
      key: "_layers",
      value: function _layers() {
        var _this10 = this;
        var opts = this.options;
        var tz = opts.ticks && opts.ticks.z || 0;
        var gz = valueOrDefault(opts.grid && opts.grid.z, -1);
        var bz = valueOrDefault(opts.border && opts.border.z, 0);
        if (!this._isVisible() || this.draw !== Scale.prototype.draw) {
          return [{
            z: tz,
            draw: function draw(chartArea) {
              _this10.draw(chartArea);
            }
          }];
        }
        return [{
          z: gz,
          draw: function draw(chartArea) {
            _this10.drawBackground();
            _this10.drawGrid(chartArea);
            _this10.drawTitle();
          }
        }, {
          z: bz,
          draw: function draw() {
            _this10.drawBorder();
          }
        }, {
          z: tz,
          draw: function draw(chartArea) {
            _this10.drawLabels(chartArea);
          }
        }];
      }
    }, {
      key: "getMatchingVisibleMetas",
      value: function getMatchingVisibleMetas(type) {
        var metas = this.chart.getSortedVisibleDatasetMetas();
        var axisID = this.axis + 'AxisID';
        var result = [];
        var i, ilen;
        for (i = 0, ilen = metas.length; i < ilen; ++i) {
          var meta = metas[i];
          if (meta[axisID] === this.id && (!type || meta.type === type)) {
            result.push(meta);
          }
        }
        return result;
      }
    }, {
      key: "_resolveTickFontOptions",
      value: function _resolveTickFontOptions(index) {
        var opts = this.options.ticks.setContext(this.getContext(index));
        return toFont(opts.font);
      }
    }, {
      key: "_maxDigits",
      value: function _maxDigits() {
        var fontSize = this._resolveTickFontOptions(0).lineHeight;
        return (this.isHorizontal() ? this.width : this.height) / fontSize;
      }
    }]);
  }(Element);
  var TypedRegistry = /*#__PURE__*/function () {
    function TypedRegistry(type, scope, override) {
      _classCallCheck$1(this, TypedRegistry);
      this.type = type;
      this.scope = scope;
      this.override = override;
      this.items = Object.create(null);
    }
    return _createClass$1(TypedRegistry, [{
      key: "isForType",
      value: function isForType(type) {
        return Object.prototype.isPrototypeOf.call(this.type.prototype, type.prototype);
      }
    }, {
      key: "register",
      value: function register(item) {
        var proto = Object.getPrototypeOf(item);
        var parentScope;
        if (isIChartComponent(proto)) {
          parentScope = this.register(proto);
        }
        var items = this.items;
        var id = item.id;
        var scope = this.scope + '.' + id;
        if (!id) {
          throw new Error('class does not have id: ' + item);
        }
        if (id in items) {
          return scope;
        }
        items[id] = item;
        registerDefaults(item, scope, parentScope);
        if (this.override) {
          defaults.override(item.id, item.overrides);
        }
        return scope;
      }
    }, {
      key: "get",
      value: function get(id) {
        return this.items[id];
      }
    }, {
      key: "unregister",
      value: function unregister(item) {
        var items = this.items;
        var id = item.id;
        var scope = this.scope;
        if (id in items) {
          delete items[id];
        }
        if (scope && id in defaults[scope]) {
          delete defaults[scope][id];
          if (this.override) {
            delete overrides[id];
          }
        }
      }
    }]);
  }();
  function registerDefaults(item, scope, parentScope) {
    var itemDefaults = merge(Object.create(null), [parentScope ? defaults.get(parentScope) : {}, defaults.get(scope), item.defaults]);
    defaults.set(scope, itemDefaults);
    if (item.defaultRoutes) {
      routeDefaults(scope, item.defaultRoutes);
    }
    if (item.descriptors) {
      defaults.describe(scope, item.descriptors);
    }
  }
  function routeDefaults(scope, routes) {
    Object.keys(routes).forEach(function (property) {
      var propertyParts = property.split('.');
      var sourceName = propertyParts.pop();
      var sourceScope = [scope].concat(propertyParts).join('.');
      var parts = routes[property].split('.');
      var targetName = parts.pop();
      var targetScope = parts.join('.');
      defaults.route(sourceScope, sourceName, targetScope, targetName);
    });
  }
  function isIChartComponent(proto) {
    return 'id' in proto && 'defaults' in proto;
  }
  var Registry = /*#__PURE__*/function () {
    function Registry() {
      _classCallCheck$1(this, Registry);
      this.controllers = new TypedRegistry(DatasetController, 'datasets', true);
      this.elements = new TypedRegistry(Element, 'elements');
      this.plugins = new TypedRegistry(Object, 'plugins');
      this.scales = new TypedRegistry(Scale, 'scales');
      this._typedRegistries = [this.controllers, this.scales, this.elements];
    }
    return _createClass$1(Registry, [{
      key: "add",
      value: function add() {
        for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
          args[_key] = arguments[_key];
        }
        this._each('register', args);
      }
    }, {
      key: "remove",
      value: function remove() {
        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
          args[_key2] = arguments[_key2];
        }
        this._each('unregister', args);
      }
    }, {
      key: "addControllers",
      value: function addControllers() {
        for (var _len3 = arguments.length, args = new Array(_len3), _key3 = 0; _key3 < _len3; _key3++) {
          args[_key3] = arguments[_key3];
        }
        this._each('register', args, this.controllers);
      }
    }, {
      key: "addElements",
      value: function addElements() {
        for (var _len4 = arguments.length, args = new Array(_len4), _key4 = 0; _key4 < _len4; _key4++) {
          args[_key4] = arguments[_key4];
        }
        this._each('register', args, this.elements);
      }
    }, {
      key: "addPlugins",
      value: function addPlugins() {
        for (var _len5 = arguments.length, args = new Array(_len5), _key5 = 0; _key5 < _len5; _key5++) {
          args[_key5] = arguments[_key5];
        }
        this._each('register', args, this.plugins);
      }
    }, {
      key: "addScales",
      value: function addScales() {
        for (var _len6 = arguments.length, args = new Array(_len6), _key6 = 0; _key6 < _len6; _key6++) {
          args[_key6] = arguments[_key6];
        }
        this._each('register', args, this.scales);
      }
    }, {
      key: "getController",
      value: function getController(id) {
        return this._get(id, this.controllers, 'controller');
      }
    }, {
      key: "getElement",
      value: function getElement(id) {
        return this._get(id, this.elements, 'element');
      }
    }, {
      key: "getPlugin",
      value: function getPlugin(id) {
        return this._get(id, this.plugins, 'plugin');
      }
    }, {
      key: "getScale",
      value: function getScale(id) {
        return this._get(id, this.scales, 'scale');
      }
    }, {
      key: "removeControllers",
      value: function removeControllers() {
        for (var _len7 = arguments.length, args = new Array(_len7), _key7 = 0; _key7 < _len7; _key7++) {
          args[_key7] = arguments[_key7];
        }
        this._each('unregister', args, this.controllers);
      }
    }, {
      key: "removeElements",
      value: function removeElements() {
        for (var _len8 = arguments.length, args = new Array(_len8), _key8 = 0; _key8 < _len8; _key8++) {
          args[_key8] = arguments[_key8];
        }
        this._each('unregister', args, this.elements);
      }
    }, {
      key: "removePlugins",
      value: function removePlugins() {
        for (var _len9 = arguments.length, args = new Array(_len9), _key9 = 0; _key9 < _len9; _key9++) {
          args[_key9] = arguments[_key9];
        }
        this._each('unregister', args, this.plugins);
      }
    }, {
      key: "removeScales",
      value: function removeScales() {
        for (var _len10 = arguments.length, args = new Array(_len10), _key10 = 0; _key10 < _len10; _key10++) {
          args[_key10] = arguments[_key10];
        }
        this._each('unregister', args, this.scales);
      }
    }, {
      key: "_each",
      value: function _each(method, args, typedRegistry) {
        var _this11 = this;
        _toConsumableArray(args).forEach(function (arg) {
          var reg = typedRegistry || _this11._getRegistryForType(arg);
          if (typedRegistry || reg.isForType(arg) || reg === _this11.plugins && arg.id) {
            _this11._exec(method, reg, arg);
          } else {
            each(arg, function (item) {
              var itemReg = typedRegistry || _this11._getRegistryForType(item);
              _this11._exec(method, itemReg, item);
            });
          }
        });
      }
    }, {
      key: "_exec",
      value: function _exec(method, registry, component) {
        var camelMethod = _capitalize(method);
        callback(component['before' + camelMethod], [], component);
        registry[method](component);
        callback(component['after' + camelMethod], [], component);
      }
    }, {
      key: "_getRegistryForType",
      value: function _getRegistryForType(type) {
        for (var i = 0; i < this._typedRegistries.length; i++) {
          var reg = this._typedRegistries[i];
          if (reg.isForType(type)) {
            return reg;
          }
        }
        return this.plugins;
      }
    }, {
      key: "_get",
      value: function _get(id, typedRegistry, type) {
        var item = typedRegistry.get(id);
        if (item === undefined) {
          throw new Error('"' + id + '" is not a registered ' + type + '.');
        }
        return item;
      }
    }]);
  }();
  var registry = /* #__PURE__ */new Registry();
  var PluginService = /*#__PURE__*/function () {
    function PluginService() {
      _classCallCheck$1(this, PluginService);
      this._init = [];
    }
    return _createClass$1(PluginService, [{
      key: "notify",
      value: function notify(chart, hook, args, filter) {
        if (hook === 'beforeInit') {
          this._init = this._createDescriptors(chart, true);
          this._notify(this._init, chart, 'install');
        }
        var descriptors = filter ? this._descriptors(chart).filter(filter) : this._descriptors(chart);
        var result = this._notify(descriptors, chart, hook, args);
        if (hook === 'afterDestroy') {
          this._notify(descriptors, chart, 'stop');
          this._notify(this._init, chart, 'uninstall');
        }
        return result;
      }
    }, {
      key: "_notify",
      value: function _notify(descriptors, chart, hook, args) {
        args = args || {};
        var _iterator11 = _createForOfIteratorHelper$1(descriptors),
          _step11;
        try {
          for (_iterator11.s(); !(_step11 = _iterator11.n()).done;) {
            var descriptor = _step11.value;
            var plugin = descriptor.plugin;
            var method = plugin[hook];
            var params = [chart, args, descriptor.options];
            if (callback(method, params, plugin) === false && args.cancelable) {
              return false;
            }
          }
        } catch (err) {
          _iterator11.e(err);
        } finally {
          _iterator11.f();
        }
        return true;
      }
    }, {
      key: "invalidate",
      value: function invalidate() {
        if (!isNullOrUndef(this._cache)) {
          this._oldCache = this._cache;
          this._cache = undefined;
        }
      }
    }, {
      key: "_descriptors",
      value: function _descriptors(chart) {
        if (this._cache) {
          return this._cache;
        }
        var descriptors = this._cache = this._createDescriptors(chart);
        this._notifyStateChanges(chart);
        return descriptors;
      }
    }, {
      key: "_createDescriptors",
      value: function _createDescriptors(chart, all) {
        var config = chart && chart.config;
        var options = valueOrDefault(config.options && config.options.plugins, {});
        var plugins = allPlugins(config);
        return options === false && !all ? [] : createDescriptors(chart, plugins, options, all);
      }
    }, {
      key: "_notifyStateChanges",
      value: function _notifyStateChanges(chart) {
        var previousDescriptors = this._oldCache || [];
        var descriptors = this._cache;
        var diff = function diff(a, b) {
          return a.filter(function (x) {
            return !b.some(function (y) {
              return x.plugin.id === y.plugin.id;
            });
          });
        };
        this._notify(diff(previousDescriptors, descriptors), chart, 'stop');
        this._notify(diff(descriptors, previousDescriptors), chart, 'start');
      }
    }]);
  }();
  function allPlugins(config) {
    var localIds = {};
    var plugins = [];
    var keys = Object.keys(registry.plugins.items);
    for (var i = 0; i < keys.length; i++) {
      plugins.push(registry.getPlugin(keys[i]));
    }
    var local = config.plugins || [];
    for (var _i2 = 0; _i2 < local.length; _i2++) {
      var plugin = local[_i2];
      if (plugins.indexOf(plugin) === -1) {
        plugins.push(plugin);
        localIds[plugin.id] = true;
      }
    }
    return {
      plugins: plugins,
      localIds: localIds
    };
  }
  function getOpts(options, all) {
    if (!all && options === false) {
      return null;
    }
    if (options === true) {
      return {};
    }
    return options;
  }
  function createDescriptors(chart, _ref2, options, all) {
    var plugins = _ref2.plugins,
      localIds = _ref2.localIds;
    var result = [];
    var context = chart.getContext();
    var _iterator12 = _createForOfIteratorHelper$1(plugins),
      _step12;
    try {
      for (_iterator12.s(); !(_step12 = _iterator12.n()).done;) {
        var plugin = _step12.value;
        var id = plugin.id;
        var opts = getOpts(options[id], all);
        if (opts === null) {
          continue;
        }
        result.push({
          plugin: plugin,
          options: pluginOpts(chart.config, {
            plugin: plugin,
            local: localIds[id]
          }, opts, context)
        });
      }
    } catch (err) {
      _iterator12.e(err);
    } finally {
      _iterator12.f();
    }
    return result;
  }
  function pluginOpts(config, _ref3, opts, context) {
    var plugin = _ref3.plugin,
      local = _ref3.local;
    var keys = config.pluginScopeKeys(plugin);
    var scopes = config.getOptionScopes(opts, keys);
    if (local && plugin.defaults) {
      scopes.push(plugin.defaults);
    }
    return config.createResolver(scopes, context, [''], {
      scriptable: false,
      indexable: false,
      allKeys: true
    });
  }
  function getIndexAxis(type, options) {
    var datasetDefaults = defaults.datasets[type] || {};
    var datasetOptions = (options.datasets || {})[type] || {};
    return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';
  }
  function getAxisFromDefaultScaleID(id, indexAxis) {
    var axis = id;
    if (id === '_index_') {
      axis = indexAxis;
    } else if (id === '_value_') {
      axis = indexAxis === 'x' ? 'y' : 'x';
    }
    return axis;
  }
  function getDefaultScaleIDFromAxis(axis, indexAxis) {
    return axis === indexAxis ? '_index_' : '_value_';
  }
  function idMatchesAxis(id) {
    if (id === 'x' || id === 'y' || id === 'r') {
      return id;
    }
  }
  function axisFromPosition(position) {
    if (position === 'top' || position === 'bottom') {
      return 'x';
    }
    if (position === 'left' || position === 'right') {
      return 'y';
    }
  }
  function determineAxis(id) {
    if (idMatchesAxis(id)) {
      return id;
    }
    for (var _len11 = arguments.length, scaleOptions = new Array(_len11 > 1 ? _len11 - 1 : 0), _key11 = 1; _key11 < _len11; _key11++) {
      scaleOptions[_key11 - 1] = arguments[_key11];
    }
    for (var _i3 = 0, _scaleOptions = scaleOptions; _i3 < _scaleOptions.length; _i3++) {
      var opts = _scaleOptions[_i3];
      var axis = opts.axis || axisFromPosition(opts.position) || id.length > 1 && idMatchesAxis(id[0].toLowerCase());
      if (axis) {
        return axis;
      }
    }
    throw new Error("Cannot determine type of '".concat(id, "' axis. Please provide 'axis' or 'position' option."));
  }
  function getAxisFromDataset(id, axis, dataset) {
    if (dataset[axis + 'AxisID'] === id) {
      return {
        axis: axis
      };
    }
  }
  function retrieveAxisFromDatasets(id, config) {
    if (config.data && config.data.datasets) {
      var boundDs = config.data.datasets.filter(function (d) {
        return d.xAxisID === id || d.yAxisID === id;
      });
      if (boundDs.length) {
        return getAxisFromDataset(id, 'x', boundDs[0]) || getAxisFromDataset(id, 'y', boundDs[0]);
      }
    }
    return {};
  }
  function mergeScaleConfig(config, options) {
    var chartDefaults = overrides[config.type] || {
      scales: {}
    };
    var configScales = options.scales || {};
    var chartIndexAxis = getIndexAxis(config.type, options);
    var scales = Object.create(null);
    Object.keys(configScales).forEach(function (id) {
      var scaleConf = configScales[id];
      if (!isObject(scaleConf)) {
        return console.error("Invalid scale configuration for scale: ".concat(id));
      }
      if (scaleConf._proxy) {
        return console.warn("Ignoring resolver passed as options for scale: ".concat(id));
      }
      var axis = determineAxis(id, scaleConf, retrieveAxisFromDatasets(id, config), defaults.scales[scaleConf.type]);
      var defaultId = getDefaultScaleIDFromAxis(axis, chartIndexAxis);
      var defaultScaleOptions = chartDefaults.scales || {};
      scales[id] = mergeIf(Object.create(null), [{
        axis: axis
      }, scaleConf, defaultScaleOptions[axis], defaultScaleOptions[defaultId]]);
    });
    config.data.datasets.forEach(function (dataset) {
      var type = dataset.type || config.type;
      var indexAxis = dataset.indexAxis || getIndexAxis(type, options);
      var datasetDefaults = overrides[type] || {};
      var defaultScaleOptions = datasetDefaults.scales || {};
      Object.keys(defaultScaleOptions).forEach(function (defaultID) {
        var axis = getAxisFromDefaultScaleID(defaultID, indexAxis);
        var id = dataset[axis + 'AxisID'] || axis;
        scales[id] = scales[id] || Object.create(null);
        mergeIf(scales[id], [{
          axis: axis
        }, configScales[id], defaultScaleOptions[defaultID]]);
      });
    });
    Object.keys(scales).forEach(function (key) {
      var scale = scales[key];
      mergeIf(scale, [defaults.scales[scale.type], defaults.scale]);
    });
    return scales;
  }
  function initOptions(config) {
    var options = config.options || (config.options = {});
    options.plugins = valueOrDefault(options.plugins, {});
    options.scales = mergeScaleConfig(config, options);
  }
  function initData(data) {
    data = data || {};
    data.datasets = data.datasets || [];
    data.labels = data.labels || [];
    return data;
  }
  function initConfig(config) {
    config = config || {};
    config.data = initData(config.data);
    initOptions(config);
    return config;
  }
  var keyCache = new Map();
  var keysCached = new Set();
  function cachedKeys(cacheKey, generate) {
    var keys = keyCache.get(cacheKey);
    if (!keys) {
      keys = generate();
      keyCache.set(cacheKey, keys);
      keysCached.add(keys);
    }
    return keys;
  }
  var addIfFound = function addIfFound(set, obj, key) {
    var opts = resolveObjectKey(obj, key);
    if (opts !== undefined) {
      set.add(opts);
    }
  };
  var Config = /*#__PURE__*/function () {
    function Config(config) {
      _classCallCheck$1(this, Config);
      this._config = initConfig(config);
      this._scopeCache = new Map();
      this._resolverCache = new Map();
    }
    return _createClass$1(Config, [{
      key: "platform",
      get: function get() {
        return this._config.platform;
      }
    }, {
      key: "type",
      get: function get() {
        return this._config.type;
      },
      set: function set(type) {
        this._config.type = type;
      }
    }, {
      key: "data",
      get: function get() {
        return this._config.data;
      },
      set: function set(data) {
        this._config.data = initData(data);
      }
    }, {
      key: "options",
      get: function get() {
        return this._config.options;
      },
      set: function set(options) {
        this._config.options = options;
      }
    }, {
      key: "plugins",
      get: function get() {
        return this._config.plugins;
      }
    }, {
      key: "update",
      value: function update() {
        var config = this._config;
        this.clearCache();
        initOptions(config);
      }
    }, {
      key: "clearCache",
      value: function clearCache() {
        this._scopeCache.clear();
        this._resolverCache.clear();
      }
    }, {
      key: "datasetScopeKeys",
      value: function datasetScopeKeys(datasetType) {
        return cachedKeys(datasetType, function () {
          return [["datasets.".concat(datasetType), '']];
        });
      }
    }, {
      key: "datasetAnimationScopeKeys",
      value: function datasetAnimationScopeKeys(datasetType, transition) {
        return cachedKeys("".concat(datasetType, ".transition.").concat(transition), function () {
          return [["datasets.".concat(datasetType, ".transitions.").concat(transition), "transitions.".concat(transition)], ["datasets.".concat(datasetType), '']];
        });
      }
    }, {
      key: "datasetElementScopeKeys",
      value: function datasetElementScopeKeys(datasetType, elementType) {
        return cachedKeys("".concat(datasetType, "-").concat(elementType), function () {
          return [["datasets.".concat(datasetType, ".elements.").concat(elementType), "datasets.".concat(datasetType), "elements.".concat(elementType), '']];
        });
      }
    }, {
      key: "pluginScopeKeys",
      value: function pluginScopeKeys(plugin) {
        var id = plugin.id;
        var type = this.type;
        return cachedKeys("".concat(type, "-plugin-").concat(id), function () {
          return [["plugins.".concat(id)].concat(_toConsumableArray(plugin.additionalOptionScopes || []))];
        });
      }
    }, {
      key: "_cachedScopes",
      value: function _cachedScopes(mainScope, resetCache) {
        var _scopeCache = this._scopeCache;
        var cache = _scopeCache.get(mainScope);
        if (!cache || resetCache) {
          cache = new Map();
          _scopeCache.set(mainScope, cache);
        }
        return cache;
      }
    }, {
      key: "getOptionScopes",
      value: function getOptionScopes(mainScope, keyLists, resetCache) {
        var options = this.options,
          type = this.type;
        var cache = this._cachedScopes(mainScope, resetCache);
        var cached = cache.get(keyLists);
        if (cached) {
          return cached;
        }
        var scopes = new Set();
        keyLists.forEach(function (keys) {
          if (mainScope) {
            scopes.add(mainScope);
            keys.forEach(function (key) {
              return addIfFound(scopes, mainScope, key);
            });
          }
          keys.forEach(function (key) {
            return addIfFound(scopes, options, key);
          });
          keys.forEach(function (key) {
            return addIfFound(scopes, overrides[type] || {}, key);
          });
          keys.forEach(function (key) {
            return addIfFound(scopes, defaults, key);
          });
          keys.forEach(function (key) {
            return addIfFound(scopes, descriptors, key);
          });
        });
        var array = Array.from(scopes);
        if (array.length === 0) {
          array.push(Object.create(null));
        }
        if (keysCached.has(keyLists)) {
          cache.set(keyLists, array);
        }
        return array;
      }
    }, {
      key: "chartOptionScopes",
      value: function chartOptionScopes() {
        var options = this.options,
          type = this.type;
        return [options, overrides[type] || {}, defaults.datasets[type] || {}, {
          type: type
        }, defaults, descriptors];
      }
    }, {
      key: "resolveNamedOptions",
      value: function resolveNamedOptions(scopes, names, context) {
        var prefixes = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [''];
        var result = {
          $shared: true
        };
        var _getResolver = getResolver(this._resolverCache, scopes, prefixes),
          resolver = _getResolver.resolver,
          subPrefixes = _getResolver.subPrefixes;
        var options = resolver;
        if (needContext(resolver, names)) {
          result.$shared = false;
          context = isFunction(context) ? context() : context;
          var subResolver = this.createResolver(scopes, context, subPrefixes);
          options = _attachContext(resolver, context, subResolver);
        }
        var _iterator13 = _createForOfIteratorHelper$1(names),
          _step13;
        try {
          for (_iterator13.s(); !(_step13 = _iterator13.n()).done;) {
            var prop = _step13.value;
            result[prop] = options[prop];
          }
        } catch (err) {
          _iterator13.e(err);
        } finally {
          _iterator13.f();
        }
        return result;
      }
    }, {
      key: "createResolver",
      value: function createResolver(scopes, context) {
        var prefixes = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [''];
        var descriptorDefaults = arguments.length > 3 ? arguments[3] : undefined;
        var _getResolver2 = getResolver(this._resolverCache, scopes, prefixes),
          resolver = _getResolver2.resolver;
        return isObject(context) ? _attachContext(resolver, context, undefined, descriptorDefaults) : resolver;
      }
    }]);
  }();
  function getResolver(resolverCache, scopes, prefixes) {
    var cache = resolverCache.get(scopes);
    if (!cache) {
      cache = new Map();
      resolverCache.set(scopes, cache);
    }
    var cacheKey = prefixes.join();
    var cached = cache.get(cacheKey);
    if (!cached) {
      var resolver = _createResolver(scopes, prefixes);
      cached = {
        resolver: resolver,
        subPrefixes: prefixes.filter(function (p) {
          return !p.toLowerCase().includes('hover');
        })
      };
      cache.set(cacheKey, cached);
    }
    return cached;
  }
  var hasFunction = function hasFunction(value) {
    return isObject(value) && Object.getOwnPropertyNames(value).some(function (key) {
      return isFunction(value[key]);
    });
  };
  function needContext(proxy, names) {
    var _descriptors2 = _descriptors(proxy),
      isScriptable = _descriptors2.isScriptable,
      isIndexable = _descriptors2.isIndexable;
    var _iterator14 = _createForOfIteratorHelper$1(names),
      _step14;
    try {
      for (_iterator14.s(); !(_step14 = _iterator14.n()).done;) {
        var prop = _step14.value;
        var scriptable = isScriptable(prop);
        var indexable = isIndexable(prop);
        var value = (indexable || scriptable) && proxy[prop];
        if (scriptable && (isFunction(value) || hasFunction(value)) || indexable && isArray(value)) {
          return true;
        }
      }
    } catch (err) {
      _iterator14.e(err);
    } finally {
      _iterator14.f();
    }
    return false;
  }
  var version = "4.4.3";
  var KNOWN_POSITIONS = ['top', 'bottom', 'left', 'right', 'chartArea'];
  function positionIsHorizontal(position, axis) {
    return position === 'top' || position === 'bottom' || KNOWN_POSITIONS.indexOf(position) === -1 && axis === 'x';
  }
  function compare2Level(l1, l2) {
    return function (a, b) {
      return a[l1] === b[l1] ? a[l2] - b[l2] : a[l1] - b[l1];
    };
  }
  function onAnimationsComplete(context) {
    var chart = context.chart;
    var animationOptions = chart.options.animation;
    chart.notifyPlugins('afterRender');
    callback(animationOptions && animationOptions.onComplete, [context], chart);
  }
  function onAnimationProgress(context) {
    var chart = context.chart;
    var animationOptions = chart.options.animation;
    callback(animationOptions && animationOptions.onProgress, [context], chart);
  }
  function getCanvas(item) {
    if (_isDomSupported() && typeof item === 'string') {
      item = document.getElementById(item);
    } else if (item && item.length) {
      item = item[0];
    }
    if (item && item.canvas) {
      item = item.canvas;
    }
    return item;
  }
  var instances = {};
  var getChart = function getChart(key) {
    var canvas = getCanvas(key);
    return Object.values(instances).filter(function (c) {
      return c.canvas === canvas;
    }).pop();
  };
  function moveNumericKeys(obj, start, move) {
    var keys = Object.keys(obj);
    for (var _i4 = 0, _keys = keys; _i4 < _keys.length; _i4++) {
      var key = _keys[_i4];
      var intKey = +key;
      if (intKey >= start) {
        var value = obj[key];
        delete obj[key];
        if (move > 0 || intKey > start) {
          obj[intKey + move] = value;
        }
      }
    }
  }
  function determineLastEvent(e, lastEvent, inChartArea, isClick) {
    if (!inChartArea || e.type === 'mouseout') {
      return null;
    }
    if (isClick) {
      return lastEvent;
    }
    return e;
  }
  function getSizeForArea(scale, chartArea, field) {
    return scale.options.clip ? scale[field] : chartArea[field];
  }
  function getDatasetArea(meta, chartArea) {
    var xScale = meta.xScale,
      yScale = meta.yScale;
    if (xScale && yScale) {
      return {
        left: getSizeForArea(xScale, chartArea, 'left'),
        right: getSizeForArea(xScale, chartArea, 'right'),
        top: getSizeForArea(yScale, chartArea, 'top'),
        bottom: getSizeForArea(yScale, chartArea, 'bottom')
      };
    }
    return chartArea;
  }
  var Chart = /*#__PURE__*/function () {
    function Chart(item, userConfig) {
      var _this12 = this;
      _classCallCheck$1(this, Chart);
      var config = this.config = new Config(userConfig);
      var initialCanvas = getCanvas(item);
      var existingChart = getChart(initialCanvas);
      if (existingChart) {
        throw new Error('Canvas is already in use. Chart with ID \'' + existingChart.id + '\'' + ' must be destroyed before the canvas with ID \'' + existingChart.canvas.id + '\' can be reused.');
      }
      var options = config.createResolver(config.chartOptionScopes(), this.getContext());
      this.platform = new (config.platform || _detectPlatform(initialCanvas))();
      this.platform.updateConfig(config);
      var context = this.platform.acquireContext(initialCanvas, options.aspectRatio);
      var canvas = context && context.canvas;
      var height = canvas && canvas.height;
      var width = canvas && canvas.width;
      this.id = uid();
      this.ctx = context;
      this.canvas = canvas;
      this.width = width;
      this.height = height;
      this._options = options;
      this._aspectRatio = this.aspectRatio;
      this._layers = [];
      this._metasets = [];
      this._stacks = undefined;
      this.boxes = [];
      this.currentDevicePixelRatio = undefined;
      this.chartArea = undefined;
      this._active = [];
      this._lastEvent = undefined;
      this._listeners = {};
      this._responsiveListeners = undefined;
      this._sortedMetasets = [];
      this.scales = {};
      this._plugins = new PluginService();
      this.$proxies = {};
      this._hiddenIndices = {};
      this.attached = false;
      this._animationsDisabled = undefined;
      this.$context = undefined;
      this._doResize = debounce(function (mode) {
        return _this12.update(mode);
      }, options.resizeDelay || 0);
      this._dataChanges = [];
      instances[this.id] = this;
      if (!context || !canvas) {
        console.error("Failed to create chart: can't acquire context from the given item");
        return;
      }
      animator.listen(this, 'complete', onAnimationsComplete);
      animator.listen(this, 'progress', onAnimationProgress);
      this._initialize();
      if (this.attached) {
        this.update();
      }
    }
    return _createClass$1(Chart, [{
      key: "aspectRatio",
      get: function get() {
        var _this$options12 = this.options,
          aspectRatio = _this$options12.aspectRatio,
          maintainAspectRatio = _this$options12.maintainAspectRatio,
          width = this.width,
          height = this.height,
          _aspectRatio = this._aspectRatio;
        if (!isNullOrUndef(aspectRatio)) {
          return aspectRatio;
        }
        if (maintainAspectRatio && _aspectRatio) {
          return _aspectRatio;
        }
        return height ? width / height : null;
      }
    }, {
      key: "data",
      get: function get() {
        return this.config.data;
      },
      set: function set(data) {
        this.config.data = data;
      }
    }, {
      key: "options",
      get: function get() {
        return this._options;
      },
      set: function set(options) {
        this.config.options = options;
      }
    }, {
      key: "registry",
      get: function get() {
        return registry;
      }
    }, {
      key: "_initialize",
      value: function _initialize() {
        this.notifyPlugins('beforeInit');
        if (this.options.responsive) {
          this.resize();
        } else {
          retinaScale(this, this.options.devicePixelRatio);
        }
        this.bindEvents();
        this.notifyPlugins('afterInit');
        return this;
      }
    }, {
      key: "clear",
      value: function clear() {
        clearCanvas(this.canvas, this.ctx);
        return this;
      }
    }, {
      key: "stop",
      value: function stop() {
        animator.stop(this);
        return this;
      }
    }, {
      key: "resize",
      value: function resize(width, height) {
        if (!animator.running(this)) {
          this._resize(width, height);
        } else {
          this._resizeBeforeDraw = {
            width: width,
            height: height
          };
        }
      }
    }, {
      key: "_resize",
      value: function _resize(width, height) {
        var options = this.options;
        var canvas = this.canvas;
        var aspectRatio = options.maintainAspectRatio && this.aspectRatio;
        var newSize = this.platform.getMaximumSize(canvas, width, height, aspectRatio);
        var newRatio = options.devicePixelRatio || this.platform.getDevicePixelRatio();
        var mode = this.width ? 'resize' : 'attach';
        this.width = newSize.width;
        this.height = newSize.height;
        this._aspectRatio = this.aspectRatio;
        if (!retinaScale(this, newRatio, true)) {
          return;
        }
        this.notifyPlugins('resize', {
          size: newSize
        });
        callback(options.onResize, [this, newSize], this);
        if (this.attached) {
          if (this._doResize(mode)) {
            this.render();
          }
        }
      }
    }, {
      key: "ensureScalesHaveIDs",
      value: function ensureScalesHaveIDs() {
        var options = this.options;
        var scalesOptions = options.scales || {};
        each(scalesOptions, function (axisOptions, axisID) {
          axisOptions.id = axisID;
        });
      }
    }, {
      key: "buildOrUpdateScales",
      value: function buildOrUpdateScales() {
        var _this13 = this;
        var options = this.options;
        var scaleOpts = options.scales;
        var scales = this.scales;
        var updated = Object.keys(scales).reduce(function (obj, id) {
          obj[id] = false;
          return obj;
        }, {});
        var items = [];
        if (scaleOpts) {
          items = items.concat(Object.keys(scaleOpts).map(function (id) {
            var scaleOptions = scaleOpts[id];
            var axis = determineAxis(id, scaleOptions);
            var isRadial = axis === 'r';
            var isHorizontal = axis === 'x';
            return {
              options: scaleOptions,
              dposition: isRadial ? 'chartArea' : isHorizontal ? 'bottom' : 'left',
              dtype: isRadial ? 'radialLinear' : isHorizontal ? 'category' : 'linear'
            };
          }));
        }
        each(items, function (item) {
          var scaleOptions = item.options;
          var id = scaleOptions.id;
          var axis = determineAxis(id, scaleOptions);
          var scaleType = valueOrDefault(scaleOptions.type, item.dtype);
          if (scaleOptions.position === undefined || positionIsHorizontal(scaleOptions.position, axis) !== positionIsHorizontal(item.dposition)) {
            scaleOptions.position = item.dposition;
          }
          updated[id] = true;
          var scale = null;
          if (id in scales && scales[id].type === scaleType) {
            scale = scales[id];
          } else {
            var scaleClass = registry.getScale(scaleType);
            scale = new scaleClass({
              id: id,
              type: scaleType,
              ctx: _this13.ctx,
              chart: _this13
            });
            scales[scale.id] = scale;
          }
          scale.init(scaleOptions, options);
        });
        each(updated, function (hasUpdated, id) {
          if (!hasUpdated) {
            delete scales[id];
          }
        });
        each(scales, function (scale) {
          layouts.configure(_this13, scale, scale.options);
          layouts.addBox(_this13, scale);
        });
      }
    }, {
      key: "_updateMetasets",
      value: function _updateMetasets() {
        var metasets = this._metasets;
        var numData = this.data.datasets.length;
        var numMeta = metasets.length;
        metasets.sort(function (a, b) {
          return a.index - b.index;
        });
        if (numMeta > numData) {
          for (var i = numData; i < numMeta; ++i) {
            this._destroyDatasetMeta(i);
          }
          metasets.splice(numData, numMeta - numData);
        }
        this._sortedMetasets = metasets.slice(0).sort(compare2Level('order', 'index'));
      }
    }, {
      key: "_removeUnreferencedMetasets",
      value: function _removeUnreferencedMetasets() {
        var _this14 = this;
        var metasets = this._metasets,
          datasets = this.data.datasets;
        if (metasets.length > datasets.length) {
          delete this._stacks;
        }
        metasets.forEach(function (meta, index) {
          if (datasets.filter(function (x) {
            return x === meta._dataset;
          }).length === 0) {
            _this14._destroyDatasetMeta(index);
          }
        });
      }
    }, {
      key: "buildOrUpdateControllers",
      value: function buildOrUpdateControllers() {
        var newControllers = [];
        var datasets = this.data.datasets;
        var i, ilen;
        this._removeUnreferencedMetasets();
        for (i = 0, ilen = datasets.length; i < ilen; i++) {
          var dataset = datasets[i];
          var meta = this.getDatasetMeta(i);
          var type = dataset.type || this.config.type;
          if (meta.type && meta.type !== type) {
            this._destroyDatasetMeta(i);
            meta = this.getDatasetMeta(i);
          }
          meta.type = type;
          meta.indexAxis = dataset.indexAxis || getIndexAxis(type, this.options);
          meta.order = dataset.order || 0;
          meta.index = i;
          meta.label = '' + dataset.label;
          meta.visible = this.isDatasetVisible(i);
          if (meta.controller) {
            meta.controller.updateIndex(i);
            meta.controller.linkScales();
          } else {
            var ControllerClass = registry.getController(type);
            var _defaults$datasets$ty = defaults.datasets[type],
              datasetElementType = _defaults$datasets$ty.datasetElementType,
              dataElementType = _defaults$datasets$ty.dataElementType;
            Object.assign(ControllerClass, {
              dataElementType: registry.getElement(dataElementType),
              datasetElementType: datasetElementType && registry.getElement(datasetElementType)
            });
            meta.controller = new ControllerClass(this, i);
            newControllers.push(meta.controller);
          }
        }
        this._updateMetasets();
        return newControllers;
      }
    }, {
      key: "_resetElements",
      value: function _resetElements() {
        var _this15 = this;
        each(this.data.datasets, function (dataset, datasetIndex) {
          _this15.getDatasetMeta(datasetIndex).controller.reset();
        }, this);
      }
    }, {
      key: "reset",
      value: function reset() {
        this._resetElements();
        this.notifyPlugins('reset');
      }
    }, {
      key: "update",
      value: function update(mode) {
        var config = this.config;
        config.update();
        var options = this._options = config.createResolver(config.chartOptionScopes(), this.getContext());
        var animsDisabled = this._animationsDisabled = !options.animation;
        this._updateScales();
        this._checkEventBindings();
        this._updateHiddenIndices();
        this._plugins.invalidate();
        if (this.notifyPlugins('beforeUpdate', {
          mode: mode,
          cancelable: true
        }) === false) {
          return;
        }
        var newControllers = this.buildOrUpdateControllers();
        this.notifyPlugins('beforeElementsUpdate');
        var minPadding = 0;
        for (var i = 0, ilen = this.data.datasets.length; i < ilen; i++) {
          var _this$getDatasetMeta = this.getDatasetMeta(i),
            controller = _this$getDatasetMeta.controller;
          var reset = !animsDisabled && newControllers.indexOf(controller) === -1;
          controller.buildOrUpdateElements(reset);
          minPadding = Math.max(+controller.getMaxOverflow(), minPadding);
        }
        minPadding = this._minPadding = options.layout.autoPadding ? minPadding : 0;
        this._updateLayout(minPadding);
        if (!animsDisabled) {
          each(newControllers, function (controller) {
            controller.reset();
          });
        }
        this._updateDatasets(mode);
        this.notifyPlugins('afterUpdate', {
          mode: mode
        });
        this._layers.sort(compare2Level('z', '_idx'));
        var _active = this._active,
          _lastEvent = this._lastEvent;
        if (_lastEvent) {
          this._eventHandler(_lastEvent, true);
        } else if (_active.length) {
          this._updateHoverStyles(_active, _active, true);
        }
        this.render();
      }
    }, {
      key: "_updateScales",
      value: function _updateScales() {
        var _this16 = this;
        each(this.scales, function (scale) {
          layouts.removeBox(_this16, scale);
        });
        this.ensureScalesHaveIDs();
        this.buildOrUpdateScales();
      }
    }, {
      key: "_checkEventBindings",
      value: function _checkEventBindings() {
        var options = this.options;
        var existingEvents = new Set(Object.keys(this._listeners));
        var newEvents = new Set(options.events);
        if (!setsEqual(existingEvents, newEvents) || !!this._responsiveListeners !== options.responsive) {
          this.unbindEvents();
          this.bindEvents();
        }
      }
    }, {
      key: "_updateHiddenIndices",
      value: function _updateHiddenIndices() {
        var _hiddenIndices = this._hiddenIndices;
        var changes = this._getUniformDataChanges() || [];
        var _iterator15 = _createForOfIteratorHelper$1(changes),
          _step15;
        try {
          for (_iterator15.s(); !(_step15 = _iterator15.n()).done;) {
            var _step15$value = _step15.value,
              method = _step15$value.method,
              start = _step15$value.start,
              count = _step15$value.count;
            var move = method === '_removeElements' ? -count : count;
            moveNumericKeys(_hiddenIndices, start, move);
          }
        } catch (err) {
          _iterator15.e(err);
        } finally {
          _iterator15.f();
        }
      }
    }, {
      key: "_getUniformDataChanges",
      value: function _getUniformDataChanges() {
        var _dataChanges = this._dataChanges;
        if (!_dataChanges || !_dataChanges.length) {
          return;
        }
        this._dataChanges = [];
        var datasetCount = this.data.datasets.length;
        var makeSet = function makeSet(idx) {
          return new Set(_dataChanges.filter(function (c) {
            return c[0] === idx;
          }).map(function (c, i) {
            return i + ',' + c.splice(1).join(',');
          }));
        };
        var changeSet = makeSet(0);
        for (var i = 1; i < datasetCount; i++) {
          if (!setsEqual(changeSet, makeSet(i))) {
            return;
          }
        }
        return Array.from(changeSet).map(function (c) {
          return c.split(',');
        }).map(function (a) {
          return {
            method: a[1],
            start: +a[2],
            count: +a[3]
          };
        });
      }
    }, {
      key: "_updateLayout",
      value: function _updateLayout(minPadding) {
        var _this17 = this;
        if (this.notifyPlugins('beforeLayout', {
          cancelable: true
        }) === false) {
          return;
        }
        layouts.update(this, this.width, this.height, minPadding);
        var area = this.chartArea;
        var noArea = area.width <= 0 || area.height <= 0;
        this._layers = [];
        each(this.boxes, function (box) {
          var _this17$_layers;
          if (noArea && box.position === 'chartArea') {
            return;
          }
          if (box.configure) {
            box.configure();
          }
          (_this17$_layers = _this17._layers).push.apply(_this17$_layers, _toConsumableArray(box._layers()));
        }, this);
        this._layers.forEach(function (item, index) {
          item._idx = index;
        });
        this.notifyPlugins('afterLayout');
      }
    }, {
      key: "_updateDatasets",
      value: function _updateDatasets(mode) {
        if (this.notifyPlugins('beforeDatasetsUpdate', {
          mode: mode,
          cancelable: true
        }) === false) {
          return;
        }
        for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
          this.getDatasetMeta(i).controller.configure();
        }
        for (var _i5 = 0, _ilen = this.data.datasets.length; _i5 < _ilen; ++_i5) {
          this._updateDataset(_i5, isFunction(mode) ? mode({
            datasetIndex: _i5
          }) : mode);
        }
        this.notifyPlugins('afterDatasetsUpdate', {
          mode: mode
        });
      }
    }, {
      key: "_updateDataset",
      value: function _updateDataset(index, mode) {
        var meta = this.getDatasetMeta(index);
        var args = {
          meta: meta,
          index: index,
          mode: mode,
          cancelable: true
        };
        if (this.notifyPlugins('beforeDatasetUpdate', args) === false) {
          return;
        }
        meta.controller._update(mode);
        args.cancelable = false;
        this.notifyPlugins('afterDatasetUpdate', args);
      }
    }, {
      key: "render",
      value: function render() {
        if (this.notifyPlugins('beforeRender', {
          cancelable: true
        }) === false) {
          return;
        }
        if (animator.has(this)) {
          if (this.attached && !animator.running(this)) {
            animator.start(this);
          }
        } else {
          this.draw();
          onAnimationsComplete({
            chart: this
          });
        }
      }
    }, {
      key: "draw",
      value: function draw() {
        var i;
        if (this._resizeBeforeDraw) {
          var _this$_resizeBeforeDr = this._resizeBeforeDraw,
            width = _this$_resizeBeforeDr.width,
            height = _this$_resizeBeforeDr.height;
          this._resize(width, height);
          this._resizeBeforeDraw = null;
        }
        this.clear();
        if (this.width <= 0 || this.height <= 0) {
          return;
        }
        if (this.notifyPlugins('beforeDraw', {
          cancelable: true
        }) === false) {
          return;
        }
        var layers = this._layers;
        for (i = 0; i < layers.length && layers[i].z <= 0; ++i) {
          layers[i].draw(this.chartArea);
        }
        this._drawDatasets();
        for (; i < layers.length; ++i) {
          layers[i].draw(this.chartArea);
        }
        this.notifyPlugins('afterDraw');
      }
    }, {
      key: "_getSortedDatasetMetas",
      value: function _getSortedDatasetMetas(filterVisible) {
        var metasets = this._sortedMetasets;
        var result = [];
        var i, ilen;
        for (i = 0, ilen = metasets.length; i < ilen; ++i) {
          var meta = metasets[i];
          if (!filterVisible || meta.visible) {
            result.push(meta);
          }
        }
        return result;
      }
    }, {
      key: "getSortedVisibleDatasetMetas",
      value: function getSortedVisibleDatasetMetas() {
        return this._getSortedDatasetMetas(true);
      }
    }, {
      key: "_drawDatasets",
      value: function _drawDatasets() {
        if (this.notifyPlugins('beforeDatasetsDraw', {
          cancelable: true
        }) === false) {
          return;
        }
        var metasets = this.getSortedVisibleDatasetMetas();
        for (var i = metasets.length - 1; i >= 0; --i) {
          this._drawDataset(metasets[i]);
        }
        this.notifyPlugins('afterDatasetsDraw');
      }
    }, {
      key: "_drawDataset",
      value: function _drawDataset(meta) {
        var ctx = this.ctx;
        var clip = meta._clip;
        var useClip = !clip.disabled;
        var area = getDatasetArea(meta, this.chartArea);
        var args = {
          meta: meta,
          index: meta.index,
          cancelable: true
        };
        if (this.notifyPlugins('beforeDatasetDraw', args) === false) {
          return;
        }
        if (useClip) {
          clipArea(ctx, {
            left: clip.left === false ? 0 : area.left - clip.left,
            right: clip.right === false ? this.width : area.right + clip.right,
            top: clip.top === false ? 0 : area.top - clip.top,
            bottom: clip.bottom === false ? this.height : area.bottom + clip.bottom
          });
        }
        meta.controller.draw();
        if (useClip) {
          unclipArea(ctx);
        }
        args.cancelable = false;
        this.notifyPlugins('afterDatasetDraw', args);
      }
    }, {
      key: "isPointInArea",
      value: function isPointInArea(point) {
        return _isPointInArea(point, this.chartArea, this._minPadding);
      }
    }, {
      key: "getElementsAtEventForMode",
      value: function getElementsAtEventForMode(e, mode, options, useFinalPosition) {
        var method = Interaction.modes[mode];
        if (typeof method === 'function') {
          return method(this, e, options, useFinalPosition);
        }
        return [];
      }
    }, {
      key: "getDatasetMeta",
      value: function getDatasetMeta(datasetIndex) {
        var dataset = this.data.datasets[datasetIndex];
        var metasets = this._metasets;
        var meta = metasets.filter(function (x) {
          return x && x._dataset === dataset;
        }).pop();
        if (!meta) {
          meta = {
            type: null,
            data: [],
            dataset: null,
            controller: null,
            hidden: null,
            xAxisID: null,
            yAxisID: null,
            order: dataset && dataset.order || 0,
            index: datasetIndex,
            _dataset: dataset,
            _parsed: [],
            _sorted: false
          };
          metasets.push(meta);
        }
        return meta;
      }
    }, {
      key: "getContext",
      value: function getContext() {
        return this.$context || (this.$context = createContext(null, {
          chart: this,
          type: 'chart'
        }));
      }
    }, {
      key: "getVisibleDatasetCount",
      value: function getVisibleDatasetCount() {
        return this.getSortedVisibleDatasetMetas().length;
      }
    }, {
      key: "isDatasetVisible",
      value: function isDatasetVisible(datasetIndex) {
        var dataset = this.data.datasets[datasetIndex];
        if (!dataset) {
          return false;
        }
        var meta = this.getDatasetMeta(datasetIndex);
        return typeof meta.hidden === 'boolean' ? !meta.hidden : !dataset.hidden;
      }
    }, {
      key: "setDatasetVisibility",
      value: function setDatasetVisibility(datasetIndex, visible) {
        var meta = this.getDatasetMeta(datasetIndex);
        meta.hidden = !visible;
      }
    }, {
      key: "toggleDataVisibility",
      value: function toggleDataVisibility(index) {
        this._hiddenIndices[index] = !this._hiddenIndices[index];
      }
    }, {
      key: "getDataVisibility",
      value: function getDataVisibility(index) {
        return !this._hiddenIndices[index];
      }
    }, {
      key: "_updateVisibility",
      value: function _updateVisibility(datasetIndex, dataIndex, visible) {
        var mode = visible ? 'show' : 'hide';
        var meta = this.getDatasetMeta(datasetIndex);
        var anims = meta.controller._resolveAnimations(undefined, mode);
        if (defined(dataIndex)) {
          meta.data[dataIndex].hidden = !visible;
          this.update();
        } else {
          this.setDatasetVisibility(datasetIndex, visible);
          anims.update(meta, {
            visible: visible
          });
          this.update(function (ctx) {
            return ctx.datasetIndex === datasetIndex ? mode : undefined;
          });
        }
      }
    }, {
      key: "hide",
      value: function hide(datasetIndex, dataIndex) {
        this._updateVisibility(datasetIndex, dataIndex, false);
      }
    }, {
      key: "show",
      value: function show(datasetIndex, dataIndex) {
        this._updateVisibility(datasetIndex, dataIndex, true);
      }
    }, {
      key: "_destroyDatasetMeta",
      value: function _destroyDatasetMeta(datasetIndex) {
        var meta = this._metasets[datasetIndex];
        if (meta && meta.controller) {
          meta.controller._destroy();
        }
        delete this._metasets[datasetIndex];
      }
    }, {
      key: "_stop",
      value: function _stop() {
        var i, ilen;
        this.stop();
        animator.remove(this);
        for (i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
          this._destroyDatasetMeta(i);
        }
      }
    }, {
      key: "destroy",
      value: function destroy() {
        this.notifyPlugins('beforeDestroy');
        var canvas = this.canvas,
          ctx = this.ctx;
        this._stop();
        this.config.clearCache();
        if (canvas) {
          this.unbindEvents();
          clearCanvas(canvas, ctx);
          this.platform.releaseContext(ctx);
          this.canvas = null;
          this.ctx = null;
        }
        delete instances[this.id];
        this.notifyPlugins('afterDestroy');
      }
    }, {
      key: "toBase64Image",
      value: function toBase64Image() {
        var _this$canvas;
        return (_this$canvas = this.canvas).toDataURL.apply(_this$canvas, arguments);
      }
    }, {
      key: "bindEvents",
      value: function bindEvents() {
        this.bindUserEvents();
        if (this.options.responsive) {
          this.bindResponsiveEvents();
        } else {
          this.attached = true;
        }
      }
    }, {
      key: "bindUserEvents",
      value: function bindUserEvents() {
        var _this18 = this;
        var listeners = this._listeners;
        var platform = this.platform;
        var _add = function _add(type, listener) {
          platform.addEventListener(_this18, type, listener);
          listeners[type] = listener;
        };
        var listener = function listener(e, x, y) {
          e.offsetX = x;
          e.offsetY = y;
          _this18._eventHandler(e);
        };
        each(this.options.events, function (type) {
          return _add(type, listener);
        });
      }
    }, {
      key: "bindResponsiveEvents",
      value: function bindResponsiveEvents() {
        var _this19 = this;
        if (!this._responsiveListeners) {
          this._responsiveListeners = {};
        }
        var listeners = this._responsiveListeners;
        var platform = this.platform;
        var _add = function _add(type, listener) {
          platform.addEventListener(_this19, type, listener);
          listeners[type] = listener;
        };
        var _remove = function _remove(type, listener) {
          if (listeners[type]) {
            platform.removeEventListener(_this19, type, listener);
            delete listeners[type];
          }
        };
        var listener = function listener(width, height) {
          if (_this19.canvas) {
            _this19.resize(width, height);
          }
        };
        var detached;
        var attached = function attached() {
          _remove('attach', attached);
          _this19.attached = true;
          _this19.resize();
          _add('resize', listener);
          _add('detach', detached);
        };
        detached = function detached() {
          _this19.attached = false;
          _remove('resize', listener);
          _this19._stop();
          _this19._resize(0, 0);
          _add('attach', attached);
        };
        if (platform.isAttached(this.canvas)) {
          attached();
        } else {
          detached();
        }
      }
    }, {
      key: "unbindEvents",
      value: function unbindEvents() {
        var _this20 = this;
        each(this._listeners, function (listener, type) {
          _this20.platform.removeEventListener(_this20, type, listener);
        });
        this._listeners = {};
        each(this._responsiveListeners, function (listener, type) {
          _this20.platform.removeEventListener(_this20, type, listener);
        });
        this._responsiveListeners = undefined;
      }
    }, {
      key: "updateHoverStyle",
      value: function updateHoverStyle(items, mode, enabled) {
        var prefix = enabled ? 'set' : 'remove';
        var meta, item, i, ilen;
        if (mode === 'dataset') {
          meta = this.getDatasetMeta(items[0].datasetIndex);
          meta.controller['_' + prefix + 'DatasetHoverStyle']();
        }
        for (i = 0, ilen = items.length; i < ilen; ++i) {
          item = items[i];
          var controller = item && this.getDatasetMeta(item.datasetIndex).controller;
          if (controller) {
            controller[prefix + 'HoverStyle'](item.element, item.datasetIndex, item.index);
          }
        }
      }
    }, {
      key: "getActiveElements",
      value: function getActiveElements() {
        return this._active || [];
      }
    }, {
      key: "setActiveElements",
      value: function setActiveElements(activeElements) {
        var _this21 = this;
        var lastActive = this._active || [];
        var active = activeElements.map(function (_ref4) {
          var datasetIndex = _ref4.datasetIndex,
            index = _ref4.index;
          var meta = _this21.getDatasetMeta(datasetIndex);
          if (!meta) {
            throw new Error('No dataset found at index ' + datasetIndex);
          }
          return {
            datasetIndex: datasetIndex,
            element: meta.data[index],
            index: index
          };
        });
        var changed = !_elementsEqual(active, lastActive);
        if (changed) {
          this._active = active;
          this._lastEvent = null;
          this._updateHoverStyles(active, lastActive);
        }
      }
    }, {
      key: "notifyPlugins",
      value: function notifyPlugins(hook, args, filter) {
        return this._plugins.notify(this, hook, args, filter);
      }
    }, {
      key: "isPluginEnabled",
      value: function isPluginEnabled(pluginId) {
        return this._plugins._cache.filter(function (p) {
          return p.plugin.id === pluginId;
        }).length === 1;
      }
    }, {
      key: "_updateHoverStyles",
      value: function _updateHoverStyles(active, lastActive, replay) {
        var hoverOptions = this.options.hover;
        var diff = function diff(a, b) {
          return a.filter(function (x) {
            return !b.some(function (y) {
              return x.datasetIndex === y.datasetIndex && x.index === y.index;
            });
          });
        };
        var deactivated = diff(lastActive, active);
        var activated = replay ? active : diff(active, lastActive);
        if (deactivated.length) {
          this.updateHoverStyle(deactivated, hoverOptions.mode, false);
        }
        if (activated.length && hoverOptions.mode) {
          this.updateHoverStyle(activated, hoverOptions.mode, true);
        }
      }
    }, {
      key: "_eventHandler",
      value: function _eventHandler(e, replay) {
        var _this22 = this;
        var args = {
          event: e,
          replay: replay,
          cancelable: true,
          inChartArea: this.isPointInArea(e)
        };
        var eventFilter = function eventFilter(plugin) {
          return (plugin.options.events || _this22.options.events).includes(e["native"].type);
        };
        if (this.notifyPlugins('beforeEvent', args, eventFilter) === false) {
          return;
        }
        var changed = this._handleEvent(e, replay, args.inChartArea);
        args.cancelable = false;
        this.notifyPlugins('afterEvent', args, eventFilter);
        if (changed || args.changed) {
          this.render();
        }
        return this;
      }
    }, {
      key: "_handleEvent",
      value: function _handleEvent(e, replay, inChartArea) {
        var _this$_active = this._active,
          lastActive = _this$_active === void 0 ? [] : _this$_active,
          options = this.options;
        var useFinalPosition = replay;
        var active = this._getActiveElements(e, lastActive, inChartArea, useFinalPosition);
        var isClick = _isClickEvent(e);
        var lastEvent = determineLastEvent(e, this._lastEvent, inChartArea, isClick);
        if (inChartArea) {
          this._lastEvent = null;
          callback(options.onHover, [e, active, this], this);
          if (isClick) {
            callback(options.onClick, [e, active, this], this);
          }
        }
        var changed = !_elementsEqual(active, lastActive);
        if (changed || replay) {
          this._active = active;
          this._updateHoverStyles(active, lastActive, replay);
        }
        this._lastEvent = lastEvent;
        return changed;
      }
    }, {
      key: "_getActiveElements",
      value: function _getActiveElements(e, lastActive, inChartArea, useFinalPosition) {
        if (e.type === 'mouseout') {
          return [];
        }
        if (!inChartArea) {
          return lastActive;
        }
        var hoverOptions = this.options.hover;
        return this.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions, useFinalPosition);
      }
    }], [{
      key: "register",
      value: function register() {
        registry.add.apply(registry, arguments);
        invalidatePlugins();
      }
    }, {
      key: "unregister",
      value: function unregister() {
        registry.remove.apply(registry, arguments);
        invalidatePlugins();
      }
    }]);
  }();
  _defineProperty$1(Chart, "defaults", defaults);
  _defineProperty$1(Chart, "instances", instances);
  _defineProperty$1(Chart, "overrides", overrides);
  _defineProperty$1(Chart, "registry", registry);
  _defineProperty$1(Chart, "version", version);
  _defineProperty$1(Chart, "getChart", getChart);
  function invalidatePlugins() {
    return each(Chart.instances, function (chart) {
      return chart._plugins.invalidate();
    });
  }
  function clipArc(ctx, element, endAngle) {
    var startAngle = element.startAngle,
      pixelMargin = element.pixelMargin,
      x = element.x,
      y = element.y,
      outerRadius = element.outerRadius,
      innerRadius = element.innerRadius;
    var angleMargin = pixelMargin / outerRadius;
    // Draw an inner border by clipping the arc and drawing a double-width border
    // Enlarge the clipping arc by 0.33 pixels to eliminate glitches between borders
    ctx.beginPath();
    ctx.arc(x, y, outerRadius, startAngle - angleMargin, endAngle + angleMargin);
    if (innerRadius > pixelMargin) {
      angleMargin = pixelMargin / innerRadius;
      ctx.arc(x, y, innerRadius, endAngle + angleMargin, startAngle - angleMargin, true);
    } else {
      ctx.arc(x, y, pixelMargin, endAngle + HALF_PI, startAngle - HALF_PI);
    }
    ctx.closePath();
    ctx.clip();
  }
  function toRadiusCorners(value) {
    return _readValueToProps(value, ['outerStart', 'outerEnd', 'innerStart', 'innerEnd']);
  }
  /**
   * Parse border radius from the provided options
   */
  function parseBorderRadius$1(arc, innerRadius, outerRadius, angleDelta) {
    var o = toRadiusCorners(arc.options.borderRadius);
    var halfThickness = (outerRadius - innerRadius) / 2;
    var innerLimit = Math.min(halfThickness, angleDelta * innerRadius / 2);
    // Outer limits are complicated. We want to compute the available angular distance at
    // a radius of outerRadius - borderRadius because for small angular distances, this term limits.
    // We compute at r = outerRadius - borderRadius because this circle defines the center of the border corners.
    //
    // If the borderRadius is large, that value can become negative.
    // This causes the outer borders to lose their radius entirely, which is rather unexpected. To solve that, if borderRadius > outerRadius
    // we know that the thickness term will dominate and compute the limits at that point
    var computeOuterLimit = function computeOuterLimit(val) {
      var outerArcLimit = (outerRadius - Math.min(halfThickness, val)) * angleDelta / 2;
      return _limitValue(val, 0, Math.min(halfThickness, outerArcLimit));
    };
    return {
      outerStart: computeOuterLimit(o.outerStart),
      outerEnd: computeOuterLimit(o.outerEnd),
      innerStart: _limitValue(o.innerStart, 0, innerLimit),
      innerEnd: _limitValue(o.innerEnd, 0, innerLimit)
    };
  }
  /**
   * Convert (r, 𝜃) to (x, y)
   */
  function rThetaToXY(r, theta, x, y) {
    return {
      x: x + r * Math.cos(theta),
      y: y + r * Math.sin(theta)
    };
  }
  /**
   * Path the arc, respecting border radius by separating into left and right halves.
   *
   *   Start      End
   *
   *    1--->a--->2    Outer
   *   /           \
   *   8           3
   *   |           |
   *   |           |
   *   7           4
   *   \           /
   *    6<---b<---5    Inner
   */
  function pathArc(ctx, element, offset, spacing, end, circular) {
    var x = element.x,
      y = element.y,
      start = element.startAngle,
      pixelMargin = element.pixelMargin,
      innerR = element.innerRadius;
    var outerRadius = Math.max(element.outerRadius + spacing + offset - pixelMargin, 0);
    var innerRadius = innerR > 0 ? innerR + spacing + offset + pixelMargin : 0;
    var spacingOffset = 0;
    var alpha = end - start;
    if (spacing) {
      // When spacing is present, it is the same for all items
      // So we adjust the start and end angle of the arc such that
      // the distance is the same as it would be without the spacing
      var noSpacingInnerRadius = innerR > 0 ? innerR - spacing : 0;
      var noSpacingOuterRadius = outerRadius > 0 ? outerRadius - spacing : 0;
      var avNogSpacingRadius = (noSpacingInnerRadius + noSpacingOuterRadius) / 2;
      var adjustedAngle = avNogSpacingRadius !== 0 ? alpha * avNogSpacingRadius / (avNogSpacingRadius + spacing) : alpha;
      spacingOffset = (alpha - adjustedAngle) / 2;
    }
    var beta = Math.max(0.001, alpha * outerRadius - offset / PI) / outerRadius;
    var angleOffset = (alpha - beta) / 2;
    var startAngle = start + angleOffset + spacingOffset;
    var endAngle = end - angleOffset - spacingOffset;
    var _parseBorderRadius$ = parseBorderRadius$1(element, innerRadius, outerRadius, endAngle - startAngle),
      outerStart = _parseBorderRadius$.outerStart,
      outerEnd = _parseBorderRadius$.outerEnd,
      innerStart = _parseBorderRadius$.innerStart,
      innerEnd = _parseBorderRadius$.innerEnd;
    var outerStartAdjustedRadius = outerRadius - outerStart;
    var outerEndAdjustedRadius = outerRadius - outerEnd;
    var outerStartAdjustedAngle = startAngle + outerStart / outerStartAdjustedRadius;
    var outerEndAdjustedAngle = endAngle - outerEnd / outerEndAdjustedRadius;
    var innerStartAdjustedRadius = innerRadius + innerStart;
    var innerEndAdjustedRadius = innerRadius + innerEnd;
    var innerStartAdjustedAngle = startAngle + innerStart / innerStartAdjustedRadius;
    var innerEndAdjustedAngle = endAngle - innerEnd / innerEndAdjustedRadius;
    ctx.beginPath();
    if (circular) {
      // The first arc segments from point 1 to point a to point 2
      var outerMidAdjustedAngle = (outerStartAdjustedAngle + outerEndAdjustedAngle) / 2;
      ctx.arc(x, y, outerRadius, outerStartAdjustedAngle, outerMidAdjustedAngle);
      ctx.arc(x, y, outerRadius, outerMidAdjustedAngle, outerEndAdjustedAngle);
      // The corner segment from point 2 to point 3
      if (outerEnd > 0) {
        var pCenter = rThetaToXY(outerEndAdjustedRadius, outerEndAdjustedAngle, x, y);
        ctx.arc(pCenter.x, pCenter.y, outerEnd, outerEndAdjustedAngle, endAngle + HALF_PI);
      }
      // The line from point 3 to point 4
      var p4 = rThetaToXY(innerEndAdjustedRadius, endAngle, x, y);
      ctx.lineTo(p4.x, p4.y);
      // The corner segment from point 4 to point 5
      if (innerEnd > 0) {
        var _pCenter = rThetaToXY(innerEndAdjustedRadius, innerEndAdjustedAngle, x, y);
        ctx.arc(_pCenter.x, _pCenter.y, innerEnd, endAngle + HALF_PI, innerEndAdjustedAngle + Math.PI);
      }
      // The inner arc from point 5 to point b to point 6
      var innerMidAdjustedAngle = (endAngle - innerEnd / innerRadius + (startAngle + innerStart / innerRadius)) / 2;
      ctx.arc(x, y, innerRadius, endAngle - innerEnd / innerRadius, innerMidAdjustedAngle, true);
      ctx.arc(x, y, innerRadius, innerMidAdjustedAngle, startAngle + innerStart / innerRadius, true);
      // The corner segment from point 6 to point 7
      if (innerStart > 0) {
        var _pCenter2 = rThetaToXY(innerStartAdjustedRadius, innerStartAdjustedAngle, x, y);
        ctx.arc(_pCenter2.x, _pCenter2.y, innerStart, innerStartAdjustedAngle + Math.PI, startAngle - HALF_PI);
      }
      // The line from point 7 to point 8
      var p8 = rThetaToXY(outerStartAdjustedRadius, startAngle, x, y);
      ctx.lineTo(p8.x, p8.y);
      // The corner segment from point 8 to point 1
      if (outerStart > 0) {
        var _pCenter3 = rThetaToXY(outerStartAdjustedRadius, outerStartAdjustedAngle, x, y);
        ctx.arc(_pCenter3.x, _pCenter3.y, outerStart, startAngle - HALF_PI, outerStartAdjustedAngle);
      }
    } else {
      ctx.moveTo(x, y);
      var outerStartX = Math.cos(outerStartAdjustedAngle) * outerRadius + x;
      var outerStartY = Math.sin(outerStartAdjustedAngle) * outerRadius + y;
      ctx.lineTo(outerStartX, outerStartY);
      var outerEndX = Math.cos(outerEndAdjustedAngle) * outerRadius + x;
      var outerEndY = Math.sin(outerEndAdjustedAngle) * outerRadius + y;
      ctx.lineTo(outerEndX, outerEndY);
    }
    ctx.closePath();
  }
  function drawArc(ctx, element, offset, spacing, circular) {
    var fullCircles = element.fullCircles,
      startAngle = element.startAngle,
      circumference = element.circumference;
    var endAngle = element.endAngle;
    if (fullCircles) {
      pathArc(ctx, element, offset, spacing, endAngle, circular);
      for (var i = 0; i < fullCircles; ++i) {
        ctx.fill();
      }
      if (!isNaN(circumference)) {
        endAngle = startAngle + (circumference % TAU || TAU);
      }
    }
    pathArc(ctx, element, offset, spacing, endAngle, circular);
    ctx.fill();
    return endAngle;
  }
  function drawBorder(ctx, element, offset, spacing, circular) {
    var fullCircles = element.fullCircles,
      startAngle = element.startAngle,
      circumference = element.circumference,
      options = element.options;
    var borderWidth = options.borderWidth,
      borderJoinStyle = options.borderJoinStyle,
      borderDash = options.borderDash,
      borderDashOffset = options.borderDashOffset;
    var inner = options.borderAlign === 'inner';
    if (!borderWidth) {
      return;
    }
    ctx.setLineDash(borderDash || []);
    ctx.lineDashOffset = borderDashOffset;
    if (inner) {
      ctx.lineWidth = borderWidth * 2;
      ctx.lineJoin = borderJoinStyle || 'round';
    } else {
      ctx.lineWidth = borderWidth;
      ctx.lineJoin = borderJoinStyle || 'bevel';
    }
    var endAngle = element.endAngle;
    if (fullCircles) {
      pathArc(ctx, element, offset, spacing, endAngle, circular);
      for (var i = 0; i < fullCircles; ++i) {
        ctx.stroke();
      }
      if (!isNaN(circumference)) {
        endAngle = startAngle + (circumference % TAU || TAU);
      }
    }
    if (inner) {
      clipArc(ctx, element, endAngle);
    }
    if (!fullCircles) {
      pathArc(ctx, element, offset, spacing, endAngle, circular);
      ctx.stroke();
    }
  }
  var ArcElement = /*#__PURE__*/function (_Element3) {
    function ArcElement(cfg) {
      var _this23;
      _classCallCheck$1(this, ArcElement);
      _this23 = _callSuper(this, ArcElement);
      _defineProperty$1(_this23, "circumference", void 0);
      _defineProperty$1(_this23, "endAngle", void 0);
      _defineProperty$1(_this23, "fullCircles", void 0);
      _defineProperty$1(_this23, "innerRadius", void 0);
      _defineProperty$1(_this23, "outerRadius", void 0);
      _defineProperty$1(_this23, "pixelMargin", void 0);
      _defineProperty$1(_this23, "startAngle", void 0);
      _this23.options = undefined;
      _this23.circumference = undefined;
      _this23.startAngle = undefined;
      _this23.endAngle = undefined;
      _this23.innerRadius = undefined;
      _this23.outerRadius = undefined;
      _this23.pixelMargin = 0;
      _this23.fullCircles = 0;
      if (cfg) {
        Object.assign(_this23, cfg);
      }
      return _this23;
    }
    _inherits$1(ArcElement, _Element3);
    return _createClass$1(ArcElement, [{
      key: "inRange",
      value: function inRange(chartX, chartY, useFinalPosition) {
        var point = this.getProps(['x', 'y'], useFinalPosition);
        var _getAngleFromPoint2 = getAngleFromPoint(point, {
            x: chartX,
            y: chartY
          }),
          angle = _getAngleFromPoint2.angle,
          distance = _getAngleFromPoint2.distance;
        var _this$getProps2 = this.getProps(['startAngle', 'endAngle', 'innerRadius', 'outerRadius', 'circumference'], useFinalPosition),
          startAngle = _this$getProps2.startAngle,
          endAngle = _this$getProps2.endAngle,
          innerRadius = _this$getProps2.innerRadius,
          outerRadius = _this$getProps2.outerRadius,
          circumference = _this$getProps2.circumference;
        var rAdjust = (this.options.spacing + this.options.borderWidth) / 2;
        var _circumference = valueOrDefault(circumference, endAngle - startAngle);
        var betweenAngles = _circumference >= TAU || _angleBetween(angle, startAngle, endAngle);
        var withinRadius = _isBetween(distance, innerRadius + rAdjust, outerRadius + rAdjust);
        return betweenAngles && withinRadius;
      }
    }, {
      key: "getCenterPoint",
      value: function getCenterPoint(useFinalPosition) {
        var _this$getProps3 = this.getProps(['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius'], useFinalPosition),
          x = _this$getProps3.x,
          y = _this$getProps3.y,
          startAngle = _this$getProps3.startAngle,
          endAngle = _this$getProps3.endAngle,
          innerRadius = _this$getProps3.innerRadius,
          outerRadius = _this$getProps3.outerRadius;
        var _this$options13 = this.options,
          offset = _this$options13.offset,
          spacing = _this$options13.spacing;
        var halfAngle = (startAngle + endAngle) / 2;
        var halfRadius = (innerRadius + outerRadius + spacing + offset) / 2;
        return {
          x: x + Math.cos(halfAngle) * halfRadius,
          y: y + Math.sin(halfAngle) * halfRadius
        };
      }
    }, {
      key: "tooltipPosition",
      value: function tooltipPosition(useFinalPosition) {
        return this.getCenterPoint(useFinalPosition);
      }
    }, {
      key: "draw",
      value: function draw(ctx) {
        var options = this.options,
          circumference = this.circumference;
        var offset = (options.offset || 0) / 4;
        var spacing = (options.spacing || 0) / 2;
        var circular = options.circular;
        this.pixelMargin = options.borderAlign === 'inner' ? 0.33 : 0;
        this.fullCircles = circumference > TAU ? Math.floor(circumference / TAU) : 0;
        if (circumference === 0 || this.innerRadius < 0 || this.outerRadius < 0) {
          return;
        }
        ctx.save();
        var halfAngle = (this.startAngle + this.endAngle) / 2;
        ctx.translate(Math.cos(halfAngle) * offset, Math.sin(halfAngle) * offset);
        var fix = 1 - Math.sin(Math.min(PI, circumference || 0));
        var radiusOffset = offset * fix;
        ctx.fillStyle = options.backgroundColor;
        ctx.strokeStyle = options.borderColor;
        drawArc(ctx, this, radiusOffset, spacing, circular);
        drawBorder(ctx, this, radiusOffset, spacing, circular);
        ctx.restore();
      }
    }]);
  }(Element);
  _defineProperty$1(ArcElement, "id", 'arc');
  _defineProperty$1(ArcElement, "defaults", {
    borderAlign: 'center',
    borderColor: '#fff',
    borderDash: [],
    borderDashOffset: 0,
    borderJoinStyle: undefined,
    borderRadius: 0,
    borderWidth: 2,
    offset: 0,
    spacing: 0,
    angle: undefined,
    circular: true
  });
  _defineProperty$1(ArcElement, "defaultRoutes", {
    backgroundColor: 'backgroundColor'
  });
  _defineProperty$1(ArcElement, "descriptors", {
    _scriptable: true,
    _indexable: function _indexable(name) {
      return name !== 'borderDash';
    }
  });
  function setStyle(ctx, options) {
    var style = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : options;
    ctx.lineCap = valueOrDefault(style.borderCapStyle, options.borderCapStyle);
    ctx.setLineDash(valueOrDefault(style.borderDash, options.borderDash));
    ctx.lineDashOffset = valueOrDefault(style.borderDashOffset, options.borderDashOffset);
    ctx.lineJoin = valueOrDefault(style.borderJoinStyle, options.borderJoinStyle);
    ctx.lineWidth = valueOrDefault(style.borderWidth, options.borderWidth);
    ctx.strokeStyle = valueOrDefault(style.borderColor, options.borderColor);
  }
  function lineTo(ctx, previous, target) {
    ctx.lineTo(target.x, target.y);
  }
  function getLineMethod(options) {
    if (options.stepped) {
      return _steppedLineTo;
    }
    if (options.tension || options.cubicInterpolationMode === 'monotone') {
      return _bezierCurveTo;
    }
    return lineTo;
  }
  function pathVars(points, segment) {
    var params = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
    var count = points.length;
    var _params$start = params.start,
      paramsStart = _params$start === void 0 ? 0 : _params$start,
      _params$end = params.end,
      paramsEnd = _params$end === void 0 ? count - 1 : _params$end;
    var segmentStart = segment.start,
      segmentEnd = segment.end;
    var start = Math.max(paramsStart, segmentStart);
    var end = Math.min(paramsEnd, segmentEnd);
    var outside = paramsStart < segmentStart && paramsEnd < segmentStart || paramsStart > segmentEnd && paramsEnd > segmentEnd;
    return {
      count: count,
      start: start,
      loop: segment.loop,
      ilen: end < start && !outside ? count + end - start : end - start
    };
  }
  function pathSegment(ctx, line, segment, params) {
    var points = line.points,
      options = line.options;
    var _pathVars = pathVars(points, segment, params),
      count = _pathVars.count,
      start = _pathVars.start,
      loop = _pathVars.loop,
      ilen = _pathVars.ilen;
    var lineMethod = getLineMethod(options);
    var _ref5 = params || {},
      _ref5$move = _ref5.move,
      move = _ref5$move === void 0 ? true : _ref5$move,
      reverse = _ref5.reverse;
    var i, point, prev;
    for (i = 0; i <= ilen; ++i) {
      point = points[(start + (reverse ? ilen - i : i)) % count];
      if (point.skip) {
        continue;
      } else if (move) {
        ctx.moveTo(point.x, point.y);
        move = false;
      } else {
        lineMethod(ctx, prev, point, reverse, options.stepped);
      }
      prev = point;
    }
    if (loop) {
      point = points[(start + (reverse ? ilen : 0)) % count];
      lineMethod(ctx, prev, point, reverse, options.stepped);
    }
    return !!loop;
  }
  function fastPathSegment(ctx, line, segment, params) {
    var points = line.points;
    var _pathVars2 = pathVars(points, segment, params),
      count = _pathVars2.count,
      start = _pathVars2.start,
      ilen = _pathVars2.ilen;
    var _ref6 = params || {},
      _ref6$move = _ref6.move,
      move = _ref6$move === void 0 ? true : _ref6$move,
      reverse = _ref6.reverse;
    var avgX = 0;
    var countX = 0;
    var i, point, prevX, minY, maxY, lastY;
    var pointIndex = function pointIndex(index) {
      return (start + (reverse ? ilen - index : index)) % count;
    };
    var drawX = function drawX() {
      if (minY !== maxY) {
        ctx.lineTo(avgX, maxY);
        ctx.lineTo(avgX, minY);
        ctx.lineTo(avgX, lastY);
      }
    };
    if (move) {
      point = points[pointIndex(0)];
      ctx.moveTo(point.x, point.y);
    }
    for (i = 0; i <= ilen; ++i) {
      point = points[pointIndex(i)];
      if (point.skip) {
        continue;
      }
      var x = point.x;
      var y = point.y;
      var truncX = x | 0;
      if (truncX === prevX) {
        if (y < minY) {
          minY = y;
        } else if (y > maxY) {
          maxY = y;
        }
        avgX = (countX * avgX + x) / ++countX;
      } else {
        drawX();
        ctx.lineTo(x, y);
        prevX = truncX;
        countX = 0;
        minY = maxY = y;
      }
      lastY = y;
    }
    drawX();
  }
  function _getSegmentMethod(line) {
    var opts = line.options;
    var borderDash = opts.borderDash && opts.borderDash.length;
    var useFastPath = !line._decimated && !line._loop && !opts.tension && opts.cubicInterpolationMode !== 'monotone' && !opts.stepped && !borderDash;
    return useFastPath ? fastPathSegment : pathSegment;
  }
  function _getInterpolationMethod(options) {
    if (options.stepped) {
      return _steppedInterpolation;
    }
    if (options.tension || options.cubicInterpolationMode === 'monotone') {
      return _bezierInterpolation;
    }
    return _pointInLine;
  }
  function strokePathWithCache(ctx, line, start, count) {
    var path = line._path;
    if (!path) {
      path = line._path = new Path2D();
      if (line.path(path, start, count)) {
        path.closePath();
      }
    }
    setStyle(ctx, line.options);
    ctx.stroke(path);
  }
  function strokePathDirect(ctx, line, start, count) {
    var segments = line.segments,
      options = line.options;
    var segmentMethod = _getSegmentMethod(line);
    var _iterator16 = _createForOfIteratorHelper$1(segments),
      _step16;
    try {
      for (_iterator16.s(); !(_step16 = _iterator16.n()).done;) {
        var segment = _step16.value;
        setStyle(ctx, options, segment.style);
        ctx.beginPath();
        if (segmentMethod(ctx, line, segment, {
          start: start,
          end: start + count - 1
        })) {
          ctx.closePath();
        }
        ctx.stroke();
      }
    } catch (err) {
      _iterator16.e(err);
    } finally {
      _iterator16.f();
    }
  }
  var usePath2D = typeof Path2D === 'function';
  function _draw(ctx, line, start, count) {
    if (usePath2D && !line.options.segment) {
      strokePathWithCache(ctx, line, start, count);
    } else {
      strokePathDirect(ctx, line, start, count);
    }
  }
  var LineElement = /*#__PURE__*/function (_Element4) {
    function LineElement(cfg) {
      var _this24;
      _classCallCheck$1(this, LineElement);
      _this24 = _callSuper(this, LineElement);
      _this24.animated = true;
      _this24.options = undefined;
      _this24._chart = undefined;
      _this24._loop = undefined;
      _this24._fullLoop = undefined;
      _this24._path = undefined;
      _this24._points = undefined;
      _this24._segments = undefined;
      _this24._decimated = false;
      _this24._pointsUpdated = false;
      _this24._datasetIndex = undefined;
      if (cfg) {
        Object.assign(_this24, cfg);
      }
      return _this24;
    }
    _inherits$1(LineElement, _Element4);
    return _createClass$1(LineElement, [{
      key: "updateControlPoints",
      value: function updateControlPoints(chartArea, indexAxis) {
        var options = this.options;
        if ((options.tension || options.cubicInterpolationMode === 'monotone') && !options.stepped && !this._pointsUpdated) {
          var loop = options.spanGaps ? this._loop : this._fullLoop;
          _updateBezierControlPoints(this._points, options, chartArea, loop, indexAxis);
          this._pointsUpdated = true;
        }
      }
    }, {
      key: "points",
      get: function get() {
        return this._points;
      },
      set: function set(points) {
        this._points = points;
        delete this._segments;
        delete this._path;
        this._pointsUpdated = false;
      }
    }, {
      key: "segments",
      get: function get() {
        return this._segments || (this._segments = _computeSegments(this, this.options.segment));
      }
    }, {
      key: "first",
      value: function first() {
        var segments = this.segments;
        var points = this.points;
        return segments.length && points[segments[0].start];
      }
    }, {
      key: "last",
      value: function last() {
        var segments = this.segments;
        var points = this.points;
        var count = segments.length;
        return count && points[segments[count - 1].end];
      }
    }, {
      key: "interpolate",
      value: function interpolate(point, property) {
        var options = this.options;
        var value = point[property];
        var points = this.points;
        var segments = _boundSegments(this, {
          property: property,
          start: value,
          end: value
        });
        if (!segments.length) {
          return;
        }
        var result = [];
        var _interpolate = _getInterpolationMethod(options);
        var i, ilen;
        for (i = 0, ilen = segments.length; i < ilen; ++i) {
          var _segments$i = segments[i],
            start = _segments$i.start,
            end = _segments$i.end;
          var p1 = points[start];
          var p2 = points[end];
          if (p1 === p2) {
            result.push(p1);
            continue;
          }
          var t = Math.abs((value - p1[property]) / (p2[property] - p1[property]));
          var interpolated = _interpolate(p1, p2, t, options.stepped);
          interpolated[property] = point[property];
          result.push(interpolated);
        }
        return result.length === 1 ? result[0] : result;
      }
    }, {
      key: "pathSegment",
      value: function pathSegment(ctx, segment, params) {
        var segmentMethod = _getSegmentMethod(this);
        return segmentMethod(ctx, this, segment, params);
      }
    }, {
      key: "path",
      value: function path(ctx, start, count) {
        var segments = this.segments;
        var segmentMethod = _getSegmentMethod(this);
        var loop = this._loop;
        start = start || 0;
        count = count || this.points.length - start;
        var _iterator17 = _createForOfIteratorHelper$1(segments),
          _step17;
        try {
          for (_iterator17.s(); !(_step17 = _iterator17.n()).done;) {
            var segment = _step17.value;
            loop &= segmentMethod(ctx, this, segment, {
              start: start,
              end: start + count - 1
            });
          }
        } catch (err) {
          _iterator17.e(err);
        } finally {
          _iterator17.f();
        }
        return !!loop;
      }
    }, {
      key: "draw",
      value: function draw(ctx, chartArea, start, count) {
        var options = this.options || {};
        var points = this.points || [];
        if (points.length && options.borderWidth) {
          ctx.save();
          _draw(ctx, this, start, count);
          ctx.restore();
        }
        if (this.animated) {
          this._pointsUpdated = false;
          this._path = undefined;
        }
      }
    }]);
  }(Element);
  _defineProperty$1(LineElement, "id", 'line');
  _defineProperty$1(LineElement, "defaults", {
    borderCapStyle: 'butt',
    borderDash: [],
    borderDashOffset: 0,
    borderJoinStyle: 'miter',
    borderWidth: 3,
    capBezierPoints: true,
    cubicInterpolationMode: 'default',
    fill: false,
    spanGaps: false,
    stepped: false,
    tension: 0
  });
  _defineProperty$1(LineElement, "defaultRoutes", {
    backgroundColor: 'backgroundColor',
    borderColor: 'borderColor'
  });
  _defineProperty$1(LineElement, "descriptors", {
    _scriptable: true,
    _indexable: function _indexable(name) {
      return name !== 'borderDash' && name !== 'fill';
    }
  });
  function inRange$1(el, pos, axis, useFinalPosition) {
    var options = el.options;
    var _el$getProps = el.getProps([axis], useFinalPosition),
      value = _el$getProps[axis];
    return Math.abs(pos - value) < options.radius + options.hitRadius;
  }
  var PointElement = /*#__PURE__*/function (_Element5) {
    function PointElement(cfg) {
      var _this25;
      _classCallCheck$1(this, PointElement);
      _this25 = _callSuper(this, PointElement);
      _defineProperty$1(_this25, "parsed", void 0);
      _defineProperty$1(_this25, "skip", void 0);
      _defineProperty$1(_this25, "stop", void 0);
      _this25.options = undefined;
      _this25.parsed = undefined;
      _this25.skip = undefined;
      _this25.stop = undefined;
      if (cfg) {
        Object.assign(_this25, cfg);
      }
      return _this25;
    }
    _inherits$1(PointElement, _Element5);
    return _createClass$1(PointElement, [{
      key: "inRange",
      value: function inRange(mouseX, mouseY, useFinalPosition) {
        var options = this.options;
        var _this$getProps4 = this.getProps(['x', 'y'], useFinalPosition),
          x = _this$getProps4.x,
          y = _this$getProps4.y;
        return Math.pow(mouseX - x, 2) + Math.pow(mouseY - y, 2) < Math.pow(options.hitRadius + options.radius, 2);
      }
    }, {
      key: "inXRange",
      value: function inXRange(mouseX, useFinalPosition) {
        return inRange$1(this, mouseX, 'x', useFinalPosition);
      }
    }, {
      key: "inYRange",
      value: function inYRange(mouseY, useFinalPosition) {
        return inRange$1(this, mouseY, 'y', useFinalPosition);
      }
    }, {
      key: "getCenterPoint",
      value: function getCenterPoint(useFinalPosition) {
        var _this$getProps5 = this.getProps(['x', 'y'], useFinalPosition),
          x = _this$getProps5.x,
          y = _this$getProps5.y;
        return {
          x: x,
          y: y
        };
      }
    }, {
      key: "size",
      value: function size(options) {
        options = options || this.options || {};
        var radius = options.radius || 0;
        radius = Math.max(radius, radius && options.hoverRadius || 0);
        var borderWidth = radius && options.borderWidth || 0;
        return (radius + borderWidth) * 2;
      }
    }, {
      key: "draw",
      value: function draw(ctx, area) {
        var options = this.options;
        if (this.skip || options.radius < 0.1 || !_isPointInArea(this, area, this.size(options) / 2)) {
          return;
        }
        ctx.strokeStyle = options.borderColor;
        ctx.lineWidth = options.borderWidth;
        ctx.fillStyle = options.backgroundColor;
        drawPoint(ctx, options, this.x, this.y);
      }
    }, {
      key: "getRange",
      value: function getRange() {
        var options = this.options || {};
        // @ts-expect-error Fallbacks should never be hit in practice
        return options.radius + options.hitRadius;
      }
    }]);
  }(Element);
  _defineProperty$1(PointElement, "id", 'point');
  /**
  * @type {any}
  */
  _defineProperty$1(PointElement, "defaults", {
    borderWidth: 1,
    hitRadius: 1,
    hoverBorderWidth: 1,
    hoverRadius: 4,
    pointStyle: 'circle',
    radius: 3,
    rotation: 0
  });
  /**
  * @type {any}
  */
  _defineProperty$1(PointElement, "defaultRoutes", {
    backgroundColor: 'backgroundColor',
    borderColor: 'borderColor'
  });
  function getBarBounds(bar, useFinalPosition) {
    var _bar$getProps = bar.getProps(['x', 'y', 'base', 'width', 'height'], useFinalPosition),
      x = _bar$getProps.x,
      y = _bar$getProps.y,
      base = _bar$getProps.base,
      width = _bar$getProps.width,
      height = _bar$getProps.height;
    var left, right, top, bottom, half;
    if (bar.horizontal) {
      half = height / 2;
      left = Math.min(x, base);
      right = Math.max(x, base);
      top = y - half;
      bottom = y + half;
    } else {
      half = width / 2;
      left = x - half;
      right = x + half;
      top = Math.min(y, base);
      bottom = Math.max(y, base);
    }
    return {
      left: left,
      top: top,
      right: right,
      bottom: bottom
    };
  }
  function skipOrLimit(skip, value, min, max) {
    return skip ? 0 : _limitValue(value, min, max);
  }
  function parseBorderWidth(bar, maxW, maxH) {
    var value = bar.options.borderWidth;
    var skip = bar.borderSkipped;
    var o = toTRBL(value);
    return {
      t: skipOrLimit(skip.top, o.top, 0, maxH),
      r: skipOrLimit(skip.right, o.right, 0, maxW),
      b: skipOrLimit(skip.bottom, o.bottom, 0, maxH),
      l: skipOrLimit(skip.left, o.left, 0, maxW)
    };
  }
  function parseBorderRadius(bar, maxW, maxH) {
    var _bar$getProps2 = bar.getProps(['enableBorderRadius']),
      enableBorderRadius = _bar$getProps2.enableBorderRadius;
    var value = bar.options.borderRadius;
    var o = toTRBLCorners(value);
    var maxR = Math.min(maxW, maxH);
    var skip = bar.borderSkipped;
    var enableBorder = enableBorderRadius || isObject(value);
    return {
      topLeft: skipOrLimit(!enableBorder || skip.top || skip.left, o.topLeft, 0, maxR),
      topRight: skipOrLimit(!enableBorder || skip.top || skip.right, o.topRight, 0, maxR),
      bottomLeft: skipOrLimit(!enableBorder || skip.bottom || skip.left, o.bottomLeft, 0, maxR),
      bottomRight: skipOrLimit(!enableBorder || skip.bottom || skip.right, o.bottomRight, 0, maxR)
    };
  }
  function boundingRects(bar) {
    var bounds = getBarBounds(bar);
    var width = bounds.right - bounds.left;
    var height = bounds.bottom - bounds.top;
    var border = parseBorderWidth(bar, width / 2, height / 2);
    var radius = parseBorderRadius(bar, width / 2, height / 2);
    return {
      outer: {
        x: bounds.left,
        y: bounds.top,
        w: width,
        h: height,
        radius: radius
      },
      inner: {
        x: bounds.left + border.l,
        y: bounds.top + border.t,
        w: width - border.l - border.r,
        h: height - border.t - border.b,
        radius: {
          topLeft: Math.max(0, radius.topLeft - Math.max(border.t, border.l)),
          topRight: Math.max(0, radius.topRight - Math.max(border.t, border.r)),
          bottomLeft: Math.max(0, radius.bottomLeft - Math.max(border.b, border.l)),
          bottomRight: Math.max(0, radius.bottomRight - Math.max(border.b, border.r))
        }
      }
    };
  }
  function _inRange(bar, x, y, useFinalPosition) {
    var skipX = x === null;
    var skipY = y === null;
    var skipBoth = skipX && skipY;
    var bounds = bar && !skipBoth && getBarBounds(bar, useFinalPosition);
    return bounds && (skipX || _isBetween(x, bounds.left, bounds.right)) && (skipY || _isBetween(y, bounds.top, bounds.bottom));
  }
  function hasRadius(radius) {
    return radius.topLeft || radius.topRight || radius.bottomLeft || radius.bottomRight;
  }
  function addNormalRectPath(ctx, rect) {
    ctx.rect(rect.x, rect.y, rect.w, rect.h);
  }
  function inflateRect(rect, amount) {
    var refRect = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
    var x = rect.x !== refRect.x ? -amount : 0;
    var y = rect.y !== refRect.y ? -amount : 0;
    var w = (rect.x + rect.w !== refRect.x + refRect.w ? amount : 0) - x;
    var h = (rect.y + rect.h !== refRect.y + refRect.h ? amount : 0) - y;
    return {
      x: rect.x + x,
      y: rect.y + y,
      w: rect.w + w,
      h: rect.h + h,
      radius: rect.radius
    };
  }
  var BarElement = /*#__PURE__*/function (_Element6) {
    function BarElement(cfg) {
      var _this26;
      _classCallCheck$1(this, BarElement);
      _this26 = _callSuper(this, BarElement);
      _this26.options = undefined;
      _this26.horizontal = undefined;
      _this26.base = undefined;
      _this26.width = undefined;
      _this26.height = undefined;
      _this26.inflateAmount = undefined;
      if (cfg) {
        Object.assign(_this26, cfg);
      }
      return _this26;
    }
    _inherits$1(BarElement, _Element6);
    return _createClass$1(BarElement, [{
      key: "draw",
      value: function draw(ctx) {
        var inflateAmount = this.inflateAmount,
          _this$options14 = this.options,
          borderColor = _this$options14.borderColor,
          backgroundColor = _this$options14.backgroundColor;
        var _boundingRects = boundingRects(this),
          inner = _boundingRects.inner,
          outer = _boundingRects.outer;
        var addRectPath = hasRadius(outer.radius) ? addRoundedRectPath : addNormalRectPath;
        ctx.save();
        if (outer.w !== inner.w || outer.h !== inner.h) {
          ctx.beginPath();
          addRectPath(ctx, inflateRect(outer, inflateAmount, inner));
          ctx.clip();
          addRectPath(ctx, inflateRect(inner, -inflateAmount, outer));
          ctx.fillStyle = borderColor;
          ctx.fill('evenodd');
        }
        ctx.beginPath();
        addRectPath(ctx, inflateRect(inner, inflateAmount));
        ctx.fillStyle = backgroundColor;
        ctx.fill();
        ctx.restore();
      }
    }, {
      key: "inRange",
      value: function inRange(mouseX, mouseY, useFinalPosition) {
        return _inRange(this, mouseX, mouseY, useFinalPosition);
      }
    }, {
      key: "inXRange",
      value: function inXRange(mouseX, useFinalPosition) {
        return _inRange(this, mouseX, null, useFinalPosition);
      }
    }, {
      key: "inYRange",
      value: function inYRange(mouseY, useFinalPosition) {
        return _inRange(this, null, mouseY, useFinalPosition);
      }
    }, {
      key: "getCenterPoint",
      value: function getCenterPoint(useFinalPosition) {
        var _this$getProps6 = this.getProps(['x', 'y', 'base', 'horizontal'], useFinalPosition),
          x = _this$getProps6.x,
          y = _this$getProps6.y,
          base = _this$getProps6.base,
          horizontal = _this$getProps6.horizontal;
        return {
          x: horizontal ? (x + base) / 2 : x,
          y: horizontal ? y : (y + base) / 2
        };
      }
    }, {
      key: "getRange",
      value: function getRange(axis) {
        return axis === 'x' ? this.width / 2 : this.height / 2;
      }
    }]);
  }(Element);
  _defineProperty$1(BarElement, "id", 'bar');
  _defineProperty$1(BarElement, "defaults", {
    borderSkipped: 'start',
    borderWidth: 0,
    borderRadius: 0,
    inflateAmount: 'auto',
    pointStyle: undefined
  });
  _defineProperty$1(BarElement, "defaultRoutes", {
    backgroundColor: 'backgroundColor',
    borderColor: 'borderColor'
  });
  var elements = /*#__PURE__*/Object.freeze({
    __proto__: null,
    ArcElement: ArcElement,
    BarElement: BarElement,
    LineElement: LineElement,
    PointElement: PointElement
  });
  var BORDER_COLORS = ['rgb(54, 162, 235)', 'rgb(255, 99, 132)', 'rgb(255, 159, 64)', 'rgb(255, 205, 86)', 'rgb(75, 192, 192)', 'rgb(153, 102, 255)', 'rgb(201, 203, 207)' // grey
  ];
  // Border colors with 50% transparency
  var BACKGROUND_COLORS = /* #__PURE__ */BORDER_COLORS.map(function (color) {
    return color.replace('rgb(', 'rgba(').replace(')', ', 0.5)');
  });
  function getBorderColor(i) {
    return BORDER_COLORS[i % BORDER_COLORS.length];
  }
  function getBackgroundColor(i) {
    return BACKGROUND_COLORS[i % BACKGROUND_COLORS.length];
  }
  function colorizeDefaultDataset(dataset, i) {
    dataset.borderColor = getBorderColor(i);
    dataset.backgroundColor = getBackgroundColor(i);
    return ++i;
  }
  function colorizeDoughnutDataset(dataset, i) {
    dataset.backgroundColor = dataset.data.map(function () {
      return getBorderColor(i++);
    });
    return i;
  }
  function colorizePolarAreaDataset(dataset, i) {
    dataset.backgroundColor = dataset.data.map(function () {
      return getBackgroundColor(i++);
    });
    return i;
  }
  function getColorizer(chart) {
    var i = 0;
    return function (dataset, datasetIndex) {
      var controller = chart.getDatasetMeta(datasetIndex).controller;
      if (controller instanceof DoughnutController) {
        i = colorizeDoughnutDataset(dataset, i);
      } else if (controller instanceof PolarAreaController) {
        i = colorizePolarAreaDataset(dataset, i);
      } else if (controller) {
        i = colorizeDefaultDataset(dataset, i);
      }
    };
  }
  function containsColorsDefinitions(descriptors) {
    var k;
    for (k in descriptors) {
      if (descriptors[k].borderColor || descriptors[k].backgroundColor) {
        return true;
      }
    }
    return false;
  }
  function containsColorsDefinition(descriptor) {
    return descriptor && (descriptor.borderColor || descriptor.backgroundColor);
  }
  var plugin_colors = {
    id: 'colors',
    defaults: {
      enabled: true,
      forceOverride: false
    },
    beforeLayout: function beforeLayout(chart, _args, options) {
      if (!options.enabled) {
        return;
      }
      var _chart$config = chart.config,
        datasets = _chart$config.data.datasets,
        chartOptions = _chart$config.options;
      var elements = chartOptions.elements;
      if (!options.forceOverride && (containsColorsDefinitions(datasets) || containsColorsDefinition(chartOptions) || elements && containsColorsDefinitions(elements))) {
        return;
      }
      var colorizer = getColorizer(chart);
      datasets.forEach(colorizer);
    }
  };
  function lttbDecimation(data, start, count, availableWidth, options) {
    var samples = options.samples || availableWidth;
    if (samples >= count) {
      return data.slice(start, start + count);
    }
    var decimated = [];
    var bucketWidth = (count - 2) / (samples - 2);
    var sampledIndex = 0;
    var endIndex = start + count - 1;
    var a = start;
    var i, maxAreaPoint, maxArea, area, nextA;
    decimated[sampledIndex++] = data[a];
    for (i = 0; i < samples - 2; i++) {
      var avgX = 0;
      var avgY = 0;
      var j = void 0;
      var avgRangeStart = Math.floor((i + 1) * bucketWidth) + 1 + start;
      var avgRangeEnd = Math.min(Math.floor((i + 2) * bucketWidth) + 1, count) + start;
      var avgRangeLength = avgRangeEnd - avgRangeStart;
      for (j = avgRangeStart; j < avgRangeEnd; j++) {
        avgX += data[j].x;
        avgY += data[j].y;
      }
      avgX /= avgRangeLength;
      avgY /= avgRangeLength;
      var rangeOffs = Math.floor(i * bucketWidth) + 1 + start;
      var rangeTo = Math.min(Math.floor((i + 1) * bucketWidth) + 1, count) + start;
      var _data$a = data[a],
        pointAx = _data$a.x,
        pointAy = _data$a.y;
      maxArea = area = -1;
      for (j = rangeOffs; j < rangeTo; j++) {
        area = 0.5 * Math.abs((pointAx - avgX) * (data[j].y - pointAy) - (pointAx - data[j].x) * (avgY - pointAy));
        if (area > maxArea) {
          maxArea = area;
          maxAreaPoint = data[j];
          nextA = j;
        }
      }
      decimated[sampledIndex++] = maxAreaPoint;
      a = nextA;
    }
    decimated[sampledIndex++] = data[endIndex];
    return decimated;
  }
  function minMaxDecimation(data, start, count, availableWidth) {
    var avgX = 0;
    var countX = 0;
    var i, point, x, y, prevX, minIndex, maxIndex, startIndex, minY, maxY;
    var decimated = [];
    var endIndex = start + count - 1;
    var xMin = data[start].x;
    var xMax = data[endIndex].x;
    var dx = xMax - xMin;
    for (i = start; i < start + count; ++i) {
      point = data[i];
      x = (point.x - xMin) / dx * availableWidth;
      y = point.y;
      var truncX = x | 0;
      if (truncX === prevX) {
        if (y < minY) {
          minY = y;
          minIndex = i;
        } else if (y > maxY) {
          maxY = y;
          maxIndex = i;
        }
        avgX = (countX * avgX + point.x) / ++countX;
      } else {
        var lastIndex = i - 1;
        if (!isNullOrUndef(minIndex) && !isNullOrUndef(maxIndex)) {
          var intermediateIndex1 = Math.min(minIndex, maxIndex);
          var intermediateIndex2 = Math.max(minIndex, maxIndex);
          if (intermediateIndex1 !== startIndex && intermediateIndex1 !== lastIndex) {
            decimated.push(_objectSpread2(_objectSpread2({}, data[intermediateIndex1]), {}, {
              x: avgX
            }));
          }
          if (intermediateIndex2 !== startIndex && intermediateIndex2 !== lastIndex) {
            decimated.push(_objectSpread2(_objectSpread2({}, data[intermediateIndex2]), {}, {
              x: avgX
            }));
          }
        }
        if (i > 0 && lastIndex !== startIndex) {
          decimated.push(data[lastIndex]);
        }
        decimated.push(point);
        prevX = truncX;
        countX = 0;
        minY = maxY = y;
        minIndex = maxIndex = startIndex = i;
      }
    }
    return decimated;
  }
  function cleanDecimatedDataset(dataset) {
    if (dataset._decimated) {
      var data = dataset._data;
      delete dataset._decimated;
      delete dataset._data;
      Object.defineProperty(dataset, 'data', {
        configurable: true,
        enumerable: true,
        writable: true,
        value: data
      });
    }
  }
  function cleanDecimatedData(chart) {
    chart.data.datasets.forEach(function (dataset) {
      cleanDecimatedDataset(dataset);
    });
  }
  function getStartAndCountOfVisiblePointsSimplified(meta, points) {
    var pointCount = points.length;
    var start = 0;
    var count;
    var iScale = meta.iScale;
    var _iScale$getUserBounds = iScale.getUserBounds(),
      min = _iScale$getUserBounds.min,
      max = _iScale$getUserBounds.max,
      minDefined = _iScale$getUserBounds.minDefined,
      maxDefined = _iScale$getUserBounds.maxDefined;
    if (minDefined) {
      start = _limitValue(_lookupByKey(points, iScale.axis, min).lo, 0, pointCount - 1);
    }
    if (maxDefined) {
      count = _limitValue(_lookupByKey(points, iScale.axis, max).hi + 1, start, pointCount) - start;
    } else {
      count = pointCount - start;
    }
    return {
      start: start,
      count: count
    };
  }
  var plugin_decimation = {
    id: 'decimation',
    defaults: {
      algorithm: 'min-max',
      enabled: false
    },
    beforeElementsUpdate: function beforeElementsUpdate(chart, args, options) {
      if (!options.enabled) {
        cleanDecimatedData(chart);
        return;
      }
      var availableWidth = chart.width;
      chart.data.datasets.forEach(function (dataset, datasetIndex) {
        var _data = dataset._data,
          indexAxis = dataset.indexAxis;
        var meta = chart.getDatasetMeta(datasetIndex);
        var data = _data || dataset.data;
        if (resolve([indexAxis, chart.options.indexAxis]) === 'y') {
          return;
        }
        if (!meta.controller.supportsDecimation) {
          return;
        }
        var xAxis = chart.scales[meta.xAxisID];
        if (xAxis.type !== 'linear' && xAxis.type !== 'time') {
          return;
        }
        if (chart.options.parsing) {
          return;
        }
        var _getStartAndCountOfVi3 = getStartAndCountOfVisiblePointsSimplified(meta, data),
          start = _getStartAndCountOfVi3.start,
          count = _getStartAndCountOfVi3.count;
        var threshold = options.threshold || 4 * availableWidth;
        if (count <= threshold) {
          cleanDecimatedDataset(dataset);
          return;
        }
        if (isNullOrUndef(_data)) {
          dataset._data = data;
          delete dataset.data;
          Object.defineProperty(dataset, 'data', {
            configurable: true,
            enumerable: true,
            get: function get() {
              return this._decimated;
            },
            set: function set(d) {
              this._data = d;
            }
          });
        }
        var decimated;
        switch (options.algorithm) {
          case 'lttb':
            decimated = lttbDecimation(data, start, count, availableWidth, options);
            break;
          case 'min-max':
            decimated = minMaxDecimation(data, start, count, availableWidth);
            break;
          default:
            throw new Error("Unsupported decimation algorithm '".concat(options.algorithm, "'"));
        }
        dataset._decimated = decimated;
      });
    },
    destroy: function destroy(chart) {
      cleanDecimatedData(chart);
    }
  };
  function _segments(line, target, property) {
    var segments = line.segments;
    var points = line.points;
    var tpoints = target.points;
    var parts = [];
    var _iterator18 = _createForOfIteratorHelper$1(segments),
      _step18;
    try {
      for (_iterator18.s(); !(_step18 = _iterator18.n()).done;) {
        var segment = _step18.value;
        var start = segment.start,
          end = segment.end;
        end = _findSegmentEnd(start, end, points);
        var bounds = _getBounds(property, points[start], points[end], segment.loop);
        if (!target.segments) {
          parts.push({
            source: segment,
            target: bounds,
            start: points[start],
            end: points[end]
          });
          continue;
        }
        var targetSegments = _boundSegments(target, bounds);
        var _iterator19 = _createForOfIteratorHelper$1(targetSegments),
          _step19;
        try {
          for (_iterator19.s(); !(_step19 = _iterator19.n()).done;) {
            var tgt = _step19.value;
            var subBounds = _getBounds(property, tpoints[tgt.start], tpoints[tgt.end], tgt.loop);
            var fillSources = _boundSegment(segment, points, subBounds);
            var _iterator20 = _createForOfIteratorHelper$1(fillSources),
              _step20;
            try {
              for (_iterator20.s(); !(_step20 = _iterator20.n()).done;) {
                var fillSource = _step20.value;
                parts.push({
                  source: fillSource,
                  target: tgt,
                  start: _defineProperty$1({}, property, _getEdge(bounds, subBounds, 'start', Math.max)),
                  end: _defineProperty$1({}, property, _getEdge(bounds, subBounds, 'end', Math.min))
                });
              }
            } catch (err) {
              _iterator20.e(err);
            } finally {
              _iterator20.f();
            }
          }
        } catch (err) {
          _iterator19.e(err);
        } finally {
          _iterator19.f();
        }
      }
    } catch (err) {
      _iterator18.e(err);
    } finally {
      _iterator18.f();
    }
    return parts;
  }
  function _getBounds(property, first, last, loop) {
    if (loop) {
      return;
    }
    var start = first[property];
    var end = last[property];
    if (property === 'angle') {
      start = _normalizeAngle(start);
      end = _normalizeAngle(end);
    }
    return {
      property: property,
      start: start,
      end: end
    };
  }
  function _pointsFromSegments(boundary, line) {
    var _ref7 = boundary || {},
      _ref7$x = _ref7.x,
      x = _ref7$x === void 0 ? null : _ref7$x,
      _ref7$y = _ref7.y,
      y = _ref7$y === void 0 ? null : _ref7$y;
    var linePoints = line.points;
    var points = [];
    line.segments.forEach(function (_ref8) {
      var start = _ref8.start,
        end = _ref8.end;
      end = _findSegmentEnd(start, end, linePoints);
      var first = linePoints[start];
      var last = linePoints[end];
      if (y !== null) {
        points.push({
          x: first.x,
          y: y
        });
        points.push({
          x: last.x,
          y: y
        });
      } else if (x !== null) {
        points.push({
          x: x,
          y: first.y
        });
        points.push({
          x: x,
          y: last.y
        });
      }
    });
    return points;
  }
  function _findSegmentEnd(start, end, points) {
    for (; end > start; end--) {
      var point = points[end];
      if (!isNaN(point.x) && !isNaN(point.y)) {
        break;
      }
    }
    return end;
  }
  function _getEdge(a, b, prop, fn) {
    if (a && b) {
      return fn(a[prop], b[prop]);
    }
    return a ? a[prop] : b ? b[prop] : 0;
  }
  function _createBoundaryLine(boundary, line) {
    var points = [];
    var _loop = false;
    if (isArray(boundary)) {
      _loop = true;
      points = boundary;
    } else {
      points = _pointsFromSegments(boundary, line);
    }
    return points.length ? new LineElement({
      points: points,
      options: {
        tension: 0
      },
      _loop: _loop,
      _fullLoop: _loop
    }) : null;
  }
  function _shouldApplyFill(source) {
    return source && source.fill !== false;
  }
  function _resolveTarget(sources, index, propagate) {
    var source = sources[index];
    var fill = source.fill;
    var visited = [index];
    var target;
    if (!propagate) {
      return fill;
    }
    while (fill !== false && visited.indexOf(fill) === -1) {
      if (!isNumberFinite(fill)) {
        return fill;
      }
      target = sources[fill];
      if (!target) {
        return false;
      }
      if (target.visible) {
        return fill;
      }
      visited.push(fill);
      fill = target.fill;
    }
    return false;
  }
  function _decodeFill(line, index, count) {
    var fill = parseFillOption(line);
    if (isObject(fill)) {
      return isNaN(fill.value) ? false : fill;
    }
    var target = parseFloat(fill);
    if (isNumberFinite(target) && Math.floor(target) === target) {
      return decodeTargetIndex(fill[0], index, target, count);
    }
    return ['origin', 'start', 'end', 'stack', 'shape'].indexOf(fill) >= 0 && fill;
  }
  function decodeTargetIndex(firstCh, index, target, count) {
    if (firstCh === '-' || firstCh === '+') {
      target = index + target;
    }
    if (target === index || target < 0 || target >= count) {
      return false;
    }
    return target;
  }
  function _getTargetPixel(fill, scale) {
    var pixel = null;
    if (fill === 'start') {
      pixel = scale.bottom;
    } else if (fill === 'end') {
      pixel = scale.top;
    } else if (isObject(fill)) {
      pixel = scale.getPixelForValue(fill.value);
    } else if (scale.getBasePixel) {
      pixel = scale.getBasePixel();
    }
    return pixel;
  }
  function _getTargetValue(fill, scale, startValue) {
    var value;
    if (fill === 'start') {
      value = startValue;
    } else if (fill === 'end') {
      value = scale.options.reverse ? scale.min : scale.max;
    } else if (isObject(fill)) {
      value = fill.value;
    } else {
      value = scale.getBaseValue();
    }
    return value;
  }
  function parseFillOption(line) {
    var options = line.options;
    var fillOption = options.fill;
    var fill = valueOrDefault(fillOption && fillOption.target, fillOption);
    if (fill === undefined) {
      fill = !!options.backgroundColor;
    }
    if (fill === false || fill === null) {
      return false;
    }
    if (fill === true) {
      return 'origin';
    }
    return fill;
  }
  function _buildStackLine(source) {
    var scale = source.scale,
      index = source.index,
      line = source.line;
    var points = [];
    var segments = line.segments;
    var sourcePoints = line.points;
    var linesBelow = getLinesBelow(scale, index);
    linesBelow.push(_createBoundaryLine({
      x: null,
      y: scale.bottom
    }, line));
    for (var i = 0; i < segments.length; i++) {
      var segment = segments[i];
      for (var j = segment.start; j <= segment.end; j++) {
        addPointsBelow(points, sourcePoints[j], linesBelow);
      }
    }
    return new LineElement({
      points: points,
      options: {}
    });
  }
  function getLinesBelow(scale, index) {
    var below = [];
    var metas = scale.getMatchingVisibleMetas('line');
    for (var i = 0; i < metas.length; i++) {
      var meta = metas[i];
      if (meta.index === index) {
        break;
      }
      if (!meta.hidden) {
        below.unshift(meta.dataset);
      }
    }
    return below;
  }
  function addPointsBelow(points, sourcePoint, linesBelow) {
    var postponed = [];
    for (var j = 0; j < linesBelow.length; j++) {
      var line = linesBelow[j];
      var _findPoint = findPoint(line, sourcePoint, 'x'),
        first = _findPoint.first,
        last = _findPoint.last,
        point = _findPoint.point;
      if (!point || first && last) {
        continue;
      }
      if (first) {
        postponed.unshift(point);
      } else {
        points.push(point);
        if (!last) {
          break;
        }
      }
    }
    points.push.apply(points, postponed);
  }
  function findPoint(line, sourcePoint, property) {
    var point = line.interpolate(sourcePoint, property);
    if (!point) {
      return {};
    }
    var pointValue = point[property];
    var segments = line.segments;
    var linePoints = line.points;
    var first = false;
    var last = false;
    for (var i = 0; i < segments.length; i++) {
      var segment = segments[i];
      var firstValue = linePoints[segment.start][property];
      var lastValue = linePoints[segment.end][property];
      if (_isBetween(pointValue, firstValue, lastValue)) {
        first = pointValue === firstValue;
        last = pointValue === lastValue;
        break;
      }
    }
    return {
      first: first,
      last: last,
      point: point
    };
  }
  var simpleArc = /*#__PURE__*/function () {
    function simpleArc(opts) {
      _classCallCheck$1(this, simpleArc);
      this.x = opts.x;
      this.y = opts.y;
      this.radius = opts.radius;
    }
    return _createClass$1(simpleArc, [{
      key: "pathSegment",
      value: function pathSegment(ctx, bounds, opts) {
        var x = this.x,
          y = this.y,
          radius = this.radius;
        bounds = bounds || {
          start: 0,
          end: TAU
        };
        ctx.arc(x, y, radius, bounds.end, bounds.start, true);
        return !opts.bounds;
      }
    }, {
      key: "interpolate",
      value: function interpolate(point) {
        var x = this.x,
          y = this.y,
          radius = this.radius;
        var angle = point.angle;
        return {
          x: x + Math.cos(angle) * radius,
          y: y + Math.sin(angle) * radius,
          angle: angle
        };
      }
    }]);
  }();
  function _getTarget(source) {
    var chart = source.chart,
      fill = source.fill,
      line = source.line;
    if (isNumberFinite(fill)) {
      return getLineByIndex(chart, fill);
    }
    if (fill === 'stack') {
      return _buildStackLine(source);
    }
    if (fill === 'shape') {
      return true;
    }
    var boundary = computeBoundary(source);
    if (boundary instanceof simpleArc) {
      return boundary;
    }
    return _createBoundaryLine(boundary, line);
  }
  function getLineByIndex(chart, index) {
    var meta = chart.getDatasetMeta(index);
    var visible = meta && chart.isDatasetVisible(index);
    return visible ? meta.dataset : null;
  }
  function computeBoundary(source) {
    var scale = source.scale || {};
    if (scale.getPointPositionForValue) {
      return computeCircularBoundary(source);
    }
    return computeLinearBoundary(source);
  }
  function computeLinearBoundary(source) {
    var _source$scale = source.scale,
      scale = _source$scale === void 0 ? {} : _source$scale,
      fill = source.fill;
    var pixel = _getTargetPixel(fill, scale);
    if (isNumberFinite(pixel)) {
      var horizontal = scale.isHorizontal();
      return {
        x: horizontal ? pixel : null,
        y: horizontal ? null : pixel
      };
    }
    return null;
  }
  function computeCircularBoundary(source) {
    var scale = source.scale,
      fill = source.fill;
    var options = scale.options;
    var length = scale.getLabels().length;
    var start = options.reverse ? scale.max : scale.min;
    var value = _getTargetValue(fill, scale, start);
    var target = [];
    if (options.grid.circular) {
      var center = scale.getPointPositionForValue(0, start);
      return new simpleArc({
        x: center.x,
        y: center.y,
        radius: scale.getDistanceFromCenterForValue(value)
      });
    }
    for (var i = 0; i < length; ++i) {
      target.push(scale.getPointPositionForValue(i, value));
    }
    return target;
  }
  function _drawfill(ctx, source, area) {
    var target = _getTarget(source);
    var line = source.line,
      scale = source.scale,
      axis = source.axis;
    var lineOpts = line.options;
    var fillOption = lineOpts.fill;
    var color = lineOpts.backgroundColor;
    var _ref9 = fillOption || {},
      _ref9$above = _ref9.above,
      above = _ref9$above === void 0 ? color : _ref9$above,
      _ref9$below = _ref9.below,
      below = _ref9$below === void 0 ? color : _ref9$below;
    if (target && line.points.length) {
      clipArea(ctx, area);
      doFill(ctx, {
        line: line,
        target: target,
        above: above,
        below: below,
        area: area,
        scale: scale,
        axis: axis
      });
      unclipArea(ctx);
    }
  }
  function doFill(ctx, cfg) {
    var line = cfg.line,
      target = cfg.target,
      above = cfg.above,
      below = cfg.below,
      area = cfg.area,
      scale = cfg.scale;
    var property = line._loop ? 'angle' : cfg.axis;
    ctx.save();
    if (property === 'x' && below !== above) {
      clipVertical(ctx, target, area.top);
      fill(ctx, {
        line: line,
        target: target,
        color: above,
        scale: scale,
        property: property
      });
      ctx.restore();
      ctx.save();
      clipVertical(ctx, target, area.bottom);
    }
    fill(ctx, {
      line: line,
      target: target,
      color: below,
      scale: scale,
      property: property
    });
    ctx.restore();
  }
  function clipVertical(ctx, target, clipY) {
    var segments = target.segments,
      points = target.points;
    var first = true;
    var lineLoop = false;
    ctx.beginPath();
    var _iterator21 = _createForOfIteratorHelper$1(segments),
      _step21;
    try {
      for (_iterator21.s(); !(_step21 = _iterator21.n()).done;) {
        var segment = _step21.value;
        var start = segment.start,
          end = segment.end;
        var firstPoint = points[start];
        var lastPoint = points[_findSegmentEnd(start, end, points)];
        if (first) {
          ctx.moveTo(firstPoint.x, firstPoint.y);
          first = false;
        } else {
          ctx.lineTo(firstPoint.x, clipY);
          ctx.lineTo(firstPoint.x, firstPoint.y);
        }
        lineLoop = !!target.pathSegment(ctx, segment, {
          move: lineLoop
        });
        if (lineLoop) {
          ctx.closePath();
        } else {
          ctx.lineTo(lastPoint.x, clipY);
        }
      }
    } catch (err) {
      _iterator21.e(err);
    } finally {
      _iterator21.f();
    }
    ctx.lineTo(target.first().x, clipY);
    ctx.closePath();
    ctx.clip();
  }
  function fill(ctx, cfg) {
    var line = cfg.line,
      target = cfg.target,
      property = cfg.property,
      color = cfg.color,
      scale = cfg.scale;
    var segments = _segments(line, target, property);
    var _iterator22 = _createForOfIteratorHelper$1(segments),
      _step22;
    try {
      for (_iterator22.s(); !(_step22 = _iterator22.n()).done;) {
        var _step22$value = _step22.value,
          src = _step22$value.source,
          tgt = _step22$value.target,
          start = _step22$value.start,
          end = _step22$value.end;
        var _src$style = src.style,
          _src$style2 = _src$style === void 0 ? {} : _src$style,
          _src$style2$backgroun = _src$style2.backgroundColor,
          backgroundColor = _src$style2$backgroun === void 0 ? color : _src$style2$backgroun;
        var notShape = target !== true;
        ctx.save();
        ctx.fillStyle = backgroundColor;
        clipBounds(ctx, scale, notShape && _getBounds(property, start, end));
        ctx.beginPath();
        var lineLoop = !!line.pathSegment(ctx, src);
        var loop = void 0;
        if (notShape) {
          if (lineLoop) {
            ctx.closePath();
          } else {
            interpolatedLineTo(ctx, target, end, property);
          }
          var targetLoop = !!target.pathSegment(ctx, tgt, {
            move: lineLoop,
            reverse: true
          });
          loop = lineLoop && targetLoop;
          if (!loop) {
            interpolatedLineTo(ctx, target, start, property);
          }
        }
        ctx.closePath();
        ctx.fill(loop ? 'evenodd' : 'nonzero');
        ctx.restore();
      }
    } catch (err) {
      _iterator22.e(err);
    } finally {
      _iterator22.f();
    }
  }
  function clipBounds(ctx, scale, bounds) {
    var _scale$chart$chartAre = scale.chart.chartArea,
      top = _scale$chart$chartAre.top,
      bottom = _scale$chart$chartAre.bottom;
    var _ref10 = bounds || {},
      property = _ref10.property,
      start = _ref10.start,
      end = _ref10.end;
    if (property === 'x') {
      ctx.beginPath();
      ctx.rect(start, top, end - start, bottom - top);
      ctx.clip();
    }
  }
  function interpolatedLineTo(ctx, target, point, property) {
    var interpolatedPoint = target.interpolate(point, property);
    if (interpolatedPoint) {
      ctx.lineTo(interpolatedPoint.x, interpolatedPoint.y);
    }
  }
  var index = {
    id: 'filler',
    afterDatasetsUpdate: function afterDatasetsUpdate(chart, _args, options) {
      var count = (chart.data.datasets || []).length;
      var sources = [];
      var meta, i, line, source;
      for (i = 0; i < count; ++i) {
        meta = chart.getDatasetMeta(i);
        line = meta.dataset;
        source = null;
        if (line && line.options && line instanceof LineElement) {
          source = {
            visible: chart.isDatasetVisible(i),
            index: i,
            fill: _decodeFill(line, i, count),
            chart: chart,
            axis: meta.controller.options.indexAxis,
            scale: meta.vScale,
            line: line
          };
        }
        meta.$filler = source;
        sources.push(source);
      }
      for (i = 0; i < count; ++i) {
        source = sources[i];
        if (!source || source.fill === false) {
          continue;
        }
        source.fill = _resolveTarget(sources, i, options.propagate);
      }
    },
    beforeDraw: function beforeDraw(chart, _args, options) {
      var draw = options.drawTime === 'beforeDraw';
      var metasets = chart.getSortedVisibleDatasetMetas();
      var area = chart.chartArea;
      for (var i = metasets.length - 1; i >= 0; --i) {
        var source = metasets[i].$filler;
        if (!source) {
          continue;
        }
        source.line.updateControlPoints(area, source.axis);
        if (draw && source.fill) {
          _drawfill(chart.ctx, source, area);
        }
      }
    },
    beforeDatasetsDraw: function beforeDatasetsDraw(chart, _args, options) {
      if (options.drawTime !== 'beforeDatasetsDraw') {
        return;
      }
      var metasets = chart.getSortedVisibleDatasetMetas();
      for (var i = metasets.length - 1; i >= 0; --i) {
        var source = metasets[i].$filler;
        if (_shouldApplyFill(source)) {
          _drawfill(chart.ctx, source, chart.chartArea);
        }
      }
    },
    beforeDatasetDraw: function beforeDatasetDraw(chart, args, options) {
      var source = args.meta.$filler;
      if (!_shouldApplyFill(source) || options.drawTime !== 'beforeDatasetDraw') {
        return;
      }
      _drawfill(chart.ctx, source, chart.chartArea);
    },
    defaults: {
      propagate: true,
      drawTime: 'beforeDatasetDraw'
    }
  };
  var getBoxSize = function getBoxSize(labelOpts, fontSize) {
    var _labelOpts$boxHeight = labelOpts.boxHeight,
      boxHeight = _labelOpts$boxHeight === void 0 ? fontSize : _labelOpts$boxHeight,
      _labelOpts$boxWidth = labelOpts.boxWidth,
      boxWidth = _labelOpts$boxWidth === void 0 ? fontSize : _labelOpts$boxWidth;
    if (labelOpts.usePointStyle) {
      boxHeight = Math.min(boxHeight, fontSize);
      boxWidth = labelOpts.pointStyleWidth || Math.min(boxWidth, fontSize);
    }
    return {
      boxWidth: boxWidth,
      boxHeight: boxHeight,
      itemHeight: Math.max(fontSize, boxHeight)
    };
  };
  var itemsEqual = function itemsEqual(a, b) {
    return a !== null && b !== null && a.datasetIndex === b.datasetIndex && a.index === b.index;
  };
  var Legend = /*#__PURE__*/function (_Element7) {
    function Legend(config) {
      var _this27;
      _classCallCheck$1(this, Legend);
      _this27 = _callSuper(this, Legend);
      _this27._added = false;
      _this27.legendHitBoxes = [];
      _this27._hoveredItem = null;
      _this27.doughnutMode = false;
      _this27.chart = config.chart;
      _this27.options = config.options;
      _this27.ctx = config.ctx;
      _this27.legendItems = undefined;
      _this27.columnSizes = undefined;
      _this27.lineWidths = undefined;
      _this27.maxHeight = undefined;
      _this27.maxWidth = undefined;
      _this27.top = undefined;
      _this27.bottom = undefined;
      _this27.left = undefined;
      _this27.right = undefined;
      _this27.height = undefined;
      _this27.width = undefined;
      _this27._margins = undefined;
      _this27.position = undefined;
      _this27.weight = undefined;
      _this27.fullSize = undefined;
      return _this27;
    }
    _inherits$1(Legend, _Element7);
    return _createClass$1(Legend, [{
      key: "update",
      value: function update(maxWidth, maxHeight, margins) {
        this.maxWidth = maxWidth;
        this.maxHeight = maxHeight;
        this._margins = margins;
        this.setDimensions();
        this.buildLabels();
        this.fit();
      }
    }, {
      key: "setDimensions",
      value: function setDimensions() {
        if (this.isHorizontal()) {
          this.width = this.maxWidth;
          this.left = this._margins.left;
          this.right = this.width;
        } else {
          this.height = this.maxHeight;
          this.top = this._margins.top;
          this.bottom = this.height;
        }
      }
    }, {
      key: "buildLabels",
      value: function buildLabels() {
        var _this28 = this;
        var labelOpts = this.options.labels || {};
        var legendItems = callback(labelOpts.generateLabels, [this.chart], this) || [];
        if (labelOpts.filter) {
          legendItems = legendItems.filter(function (item) {
            return labelOpts.filter(item, _this28.chart.data);
          });
        }
        if (labelOpts.sort) {
          legendItems = legendItems.sort(function (a, b) {
            return labelOpts.sort(a, b, _this28.chart.data);
          });
        }
        if (this.options.reverse) {
          legendItems.reverse();
        }
        this.legendItems = legendItems;
      }
    }, {
      key: "fit",
      value: function fit() {
        var options = this.options,
          ctx = this.ctx;
        if (!options.display) {
          this.width = this.height = 0;
          return;
        }
        var labelOpts = options.labels;
        var labelFont = toFont(labelOpts.font);
        var fontSize = labelFont.size;
        var titleHeight = this._computeTitleHeight();
        var _getBoxSize = getBoxSize(labelOpts, fontSize),
          boxWidth = _getBoxSize.boxWidth,
          itemHeight = _getBoxSize.itemHeight;
        var width, height;
        ctx.font = labelFont.string;
        if (this.isHorizontal()) {
          width = this.maxWidth;
          height = this._fitRows(titleHeight, fontSize, boxWidth, itemHeight) + 10;
        } else {
          height = this.maxHeight;
          width = this._fitCols(titleHeight, labelFont, boxWidth, itemHeight) + 10;
        }
        this.width = Math.min(width, options.maxWidth || this.maxWidth);
        this.height = Math.min(height, options.maxHeight || this.maxHeight);
      }
    }, {
      key: "_fitRows",
      value: function _fitRows(titleHeight, fontSize, boxWidth, itemHeight) {
        var ctx = this.ctx,
          maxWidth = this.maxWidth,
          padding = this.options.labels.padding;
        var hitboxes = this.legendHitBoxes = [];
        var lineWidths = this.lineWidths = [0];
        var lineHeight = itemHeight + padding;
        var totalHeight = titleHeight;
        ctx.textAlign = 'left';
        ctx.textBaseline = 'middle';
        var row = -1;
        var top = -lineHeight;
        this.legendItems.forEach(function (legendItem, i) {
          var itemWidth = boxWidth + fontSize / 2 + ctx.measureText(legendItem.text).width;
          if (i === 0 || lineWidths[lineWidths.length - 1] + itemWidth + 2 * padding > maxWidth) {
            totalHeight += lineHeight;
            lineWidths[lineWidths.length - (i > 0 ? 0 : 1)] = 0;
            top += lineHeight;
            row++;
          }
          hitboxes[i] = {
            left: 0,
            top: top,
            row: row,
            width: itemWidth,
            height: itemHeight
          };
          lineWidths[lineWidths.length - 1] += itemWidth + padding;
        });
        return totalHeight;
      }
    }, {
      key: "_fitCols",
      value: function _fitCols(titleHeight, labelFont, boxWidth, _itemHeight) {
        var ctx = this.ctx,
          maxHeight = this.maxHeight,
          padding = this.options.labels.padding;
        var hitboxes = this.legendHitBoxes = [];
        var columnSizes = this.columnSizes = [];
        var heightLimit = maxHeight - titleHeight;
        var totalWidth = padding;
        var currentColWidth = 0;
        var currentColHeight = 0;
        var left = 0;
        var col = 0;
        this.legendItems.forEach(function (legendItem, i) {
          var _calculateItemSize = calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight),
            itemWidth = _calculateItemSize.itemWidth,
            itemHeight = _calculateItemSize.itemHeight;
          if (i > 0 && currentColHeight + itemHeight + 2 * padding > heightLimit) {
            totalWidth += currentColWidth + padding;
            columnSizes.push({
              width: currentColWidth,
              height: currentColHeight
            });
            left += currentColWidth + padding;
            col++;
            currentColWidth = currentColHeight = 0;
          }
          hitboxes[i] = {
            left: left,
            top: currentColHeight,
            col: col,
            width: itemWidth,
            height: itemHeight
          };
          currentColWidth = Math.max(currentColWidth, itemWidth);
          currentColHeight += itemHeight + padding;
        });
        totalWidth += currentColWidth;
        columnSizes.push({
          width: currentColWidth,
          height: currentColHeight
        });
        return totalWidth;
      }
    }, {
      key: "adjustHitBoxes",
      value: function adjustHitBoxes() {
        if (!this.options.display) {
          return;
        }
        var titleHeight = this._computeTitleHeight();
        var hitboxes = this.legendHitBoxes,
          _this$options15 = this.options,
          align = _this$options15.align,
          padding = _this$options15.labels.padding,
          rtl = _this$options15.rtl;
        var rtlHelper = getRtlAdapter(rtl, this.left, this.width);
        if (this.isHorizontal()) {
          var row = 0;
          var left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
          var _iterator23 = _createForOfIteratorHelper$1(hitboxes),
            _step23;
          try {
            for (_iterator23.s(); !(_step23 = _iterator23.n()).done;) {
              var hitbox = _step23.value;
              if (row !== hitbox.row) {
                row = hitbox.row;
                left = _alignStartEnd(align, this.left + padding, this.right - this.lineWidths[row]);
              }
              hitbox.top += this.top + titleHeight + padding;
              hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(left), hitbox.width);
              left += hitbox.width + padding;
            }
          } catch (err) {
            _iterator23.e(err);
          } finally {
            _iterator23.f();
          }
        } else {
          var col = 0;
          var top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
          var _iterator24 = _createForOfIteratorHelper$1(hitboxes),
            _step24;
          try {
            for (_iterator24.s(); !(_step24 = _iterator24.n()).done;) {
              var _hitbox = _step24.value;
              if (_hitbox.col !== col) {
                col = _hitbox.col;
                top = _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - this.columnSizes[col].height);
              }
              _hitbox.top = top;
              _hitbox.left += this.left + padding;
              _hitbox.left = rtlHelper.leftForLtr(rtlHelper.x(_hitbox.left), _hitbox.width);
              top += _hitbox.height + padding;
            }
          } catch (err) {
            _iterator24.e(err);
          } finally {
            _iterator24.f();
          }
        }
      }
    }, {
      key: "isHorizontal",
      value: function isHorizontal() {
        return this.options.position === 'top' || this.options.position === 'bottom';
      }
    }, {
      key: "draw",
      value: function draw() {
        if (this.options.display) {
          var ctx = this.ctx;
          clipArea(ctx, this);
          this._draw();
          unclipArea(ctx);
        }
      }
    }, {
      key: "_draw",
      value: function _draw() {
        var _this29 = this;
        var opts = this.options,
          columnSizes = this.columnSizes,
          lineWidths = this.lineWidths,
          ctx = this.ctx;
        var align = opts.align,
          labelOpts = opts.labels;
        var defaultColor = defaults.color;
        var rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
        var labelFont = toFont(labelOpts.font);
        var padding = labelOpts.padding;
        var fontSize = labelFont.size;
        var halfFontSize = fontSize / 2;
        var cursor;
        this.drawTitle();
        ctx.textAlign = rtlHelper.textAlign('left');
        ctx.textBaseline = 'middle';
        ctx.lineWidth = 0.5;
        ctx.font = labelFont.string;
        var _getBoxSize2 = getBoxSize(labelOpts, fontSize),
          boxWidth = _getBoxSize2.boxWidth,
          boxHeight = _getBoxSize2.boxHeight,
          itemHeight = _getBoxSize2.itemHeight;
        var drawLegendBox = function drawLegendBox(x, y, legendItem) {
          if (isNaN(boxWidth) || boxWidth <= 0 || isNaN(boxHeight) || boxHeight < 0) {
            return;
          }
          ctx.save();
          var lineWidth = valueOrDefault(legendItem.lineWidth, 1);
          ctx.fillStyle = valueOrDefault(legendItem.fillStyle, defaultColor);
          ctx.lineCap = valueOrDefault(legendItem.lineCap, 'butt');
          ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, 0);
          ctx.lineJoin = valueOrDefault(legendItem.lineJoin, 'miter');
          ctx.lineWidth = lineWidth;
          ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, defaultColor);
          ctx.setLineDash(valueOrDefault(legendItem.lineDash, []));
          if (labelOpts.usePointStyle) {
            var drawOptions = {
              radius: boxHeight * Math.SQRT2 / 2,
              pointStyle: legendItem.pointStyle,
              rotation: legendItem.rotation,
              borderWidth: lineWidth
            };
            var centerX = rtlHelper.xPlus(x, boxWidth / 2);
            var centerY = y + halfFontSize;
            drawPointLegend(ctx, drawOptions, centerX, centerY, labelOpts.pointStyleWidth && boxWidth);
          } else {
            var yBoxTop = y + Math.max((fontSize - boxHeight) / 2, 0);
            var xBoxLeft = rtlHelper.leftForLtr(x, boxWidth);
            var borderRadius = toTRBLCorners(legendItem.borderRadius);
            ctx.beginPath();
            if (Object.values(borderRadius).some(function (v) {
              return v !== 0;
            })) {
              addRoundedRectPath(ctx, {
                x: xBoxLeft,
                y: yBoxTop,
                w: boxWidth,
                h: boxHeight,
                radius: borderRadius
              });
            } else {
              ctx.rect(xBoxLeft, yBoxTop, boxWidth, boxHeight);
            }
            ctx.fill();
            if (lineWidth !== 0) {
              ctx.stroke();
            }
          }
          ctx.restore();
        };
        var fillText = function fillText(x, y, legendItem) {
          renderText(ctx, legendItem.text, x, y + itemHeight / 2, labelFont, {
            strikethrough: legendItem.hidden,
            textAlign: rtlHelper.textAlign(legendItem.textAlign)
          });
        };
        var isHorizontal = this.isHorizontal();
        var titleHeight = this._computeTitleHeight();
        if (isHorizontal) {
          cursor = {
            x: _alignStartEnd(align, this.left + padding, this.right - lineWidths[0]),
            y: this.top + padding + titleHeight,
            line: 0
          };
        } else {
          cursor = {
            x: this.left + padding,
            y: _alignStartEnd(align, this.top + titleHeight + padding, this.bottom - columnSizes[0].height),
            line: 0
          };
        }
        overrideTextDirection(this.ctx, opts.textDirection);
        var lineHeight = itemHeight + padding;
        this.legendItems.forEach(function (legendItem, i) {
          ctx.strokeStyle = legendItem.fontColor;
          ctx.fillStyle = legendItem.fontColor;
          var textWidth = ctx.measureText(legendItem.text).width;
          var textAlign = rtlHelper.textAlign(legendItem.textAlign || (legendItem.textAlign = labelOpts.textAlign));
          var width = boxWidth + halfFontSize + textWidth;
          var x = cursor.x;
          var y = cursor.y;
          rtlHelper.setWidth(_this29.width);
          if (isHorizontal) {
            if (i > 0 && x + width + padding > _this29.right) {
              y = cursor.y += lineHeight;
              cursor.line++;
              x = cursor.x = _alignStartEnd(align, _this29.left + padding, _this29.right - lineWidths[cursor.line]);
            }
          } else if (i > 0 && y + lineHeight > _this29.bottom) {
            x = cursor.x = x + columnSizes[cursor.line].width + padding;
            cursor.line++;
            y = cursor.y = _alignStartEnd(align, _this29.top + titleHeight + padding, _this29.bottom - columnSizes[cursor.line].height);
          }
          var realX = rtlHelper.x(x);
          drawLegendBox(realX, y, legendItem);
          x = _textX(textAlign, x + boxWidth + halfFontSize, isHorizontal ? x + width : _this29.right, opts.rtl);
          fillText(rtlHelper.x(x), y, legendItem);
          if (isHorizontal) {
            cursor.x += width + padding;
          } else if (typeof legendItem.text !== 'string') {
            var fontLineHeight = labelFont.lineHeight;
            cursor.y += calculateLegendItemHeight(legendItem, fontLineHeight) + padding;
          } else {
            cursor.y += lineHeight;
          }
        });
        restoreTextDirection(this.ctx, opts.textDirection);
      }
    }, {
      key: "drawTitle",
      value: function drawTitle() {
        var opts = this.options;
        var titleOpts = opts.title;
        var titleFont = toFont(titleOpts.font);
        var titlePadding = toPadding(titleOpts.padding);
        if (!titleOpts.display) {
          return;
        }
        var rtlHelper = getRtlAdapter(opts.rtl, this.left, this.width);
        var ctx = this.ctx;
        var position = titleOpts.position;
        var halfFontSize = titleFont.size / 2;
        var topPaddingPlusHalfFontSize = titlePadding.top + halfFontSize;
        var y;
        var left = this.left;
        var maxWidth = this.width;
        if (this.isHorizontal()) {
          maxWidth = Math.max.apply(Math, _toConsumableArray(this.lineWidths));
          y = this.top + topPaddingPlusHalfFontSize;
          left = _alignStartEnd(opts.align, left, this.right - maxWidth);
        } else {
          var maxHeight = this.columnSizes.reduce(function (acc, size) {
            return Math.max(acc, size.height);
          }, 0);
          y = topPaddingPlusHalfFontSize + _alignStartEnd(opts.align, this.top, this.bottom - maxHeight - opts.labels.padding - this._computeTitleHeight());
        }
        var x = _alignStartEnd(position, left, left + maxWidth);
        ctx.textAlign = rtlHelper.textAlign(_toLeftRightCenter(position));
        ctx.textBaseline = 'middle';
        ctx.strokeStyle = titleOpts.color;
        ctx.fillStyle = titleOpts.color;
        ctx.font = titleFont.string;
        renderText(ctx, titleOpts.text, x, y, titleFont);
      }
    }, {
      key: "_computeTitleHeight",
      value: function _computeTitleHeight() {
        var titleOpts = this.options.title;
        var titleFont = toFont(titleOpts.font);
        var titlePadding = toPadding(titleOpts.padding);
        return titleOpts.display ? titleFont.lineHeight + titlePadding.height : 0;
      }
    }, {
      key: "_getLegendItemAt",
      value: function _getLegendItemAt(x, y) {
        var i, hitBox, lh;
        if (_isBetween(x, this.left, this.right) && _isBetween(y, this.top, this.bottom)) {
          lh = this.legendHitBoxes;
          for (i = 0; i < lh.length; ++i) {
            hitBox = lh[i];
            if (_isBetween(x, hitBox.left, hitBox.left + hitBox.width) && _isBetween(y, hitBox.top, hitBox.top + hitBox.height)) {
              return this.legendItems[i];
            }
          }
        }
        return null;
      }
    }, {
      key: "handleEvent",
      value: function handleEvent(e) {
        var opts = this.options;
        if (!isListened(e.type, opts)) {
          return;
        }
        var hoveredItem = this._getLegendItemAt(e.x, e.y);
        if (e.type === 'mousemove' || e.type === 'mouseout') {
          var previous = this._hoveredItem;
          var sameItem = itemsEqual(previous, hoveredItem);
          if (previous && !sameItem) {
            callback(opts.onLeave, [e, previous, this], this);
          }
          this._hoveredItem = hoveredItem;
          if (hoveredItem && !sameItem) {
            callback(opts.onHover, [e, hoveredItem, this], this);
          }
        } else if (hoveredItem) {
          callback(opts.onClick, [e, hoveredItem, this], this);
        }
      }
    }]);
  }(Element);
  function calculateItemSize(boxWidth, labelFont, ctx, legendItem, _itemHeight) {
    var itemWidth = calculateItemWidth(legendItem, boxWidth, labelFont, ctx);
    var itemHeight = calculateItemHeight(_itemHeight, legendItem, labelFont.lineHeight);
    return {
      itemWidth: itemWidth,
      itemHeight: itemHeight
    };
  }
  function calculateItemWidth(legendItem, boxWidth, labelFont, ctx) {
    var legendItemText = legendItem.text;
    if (legendItemText && typeof legendItemText !== 'string') {
      legendItemText = legendItemText.reduce(function (a, b) {
        return a.length > b.length ? a : b;
      });
    }
    return boxWidth + labelFont.size / 2 + ctx.measureText(legendItemText).width;
  }
  function calculateItemHeight(_itemHeight, legendItem, fontLineHeight) {
    var itemHeight = _itemHeight;
    if (typeof legendItem.text !== 'string') {
      itemHeight = calculateLegendItemHeight(legendItem, fontLineHeight);
    }
    return itemHeight;
  }
  function calculateLegendItemHeight(legendItem, fontLineHeight) {
    var labelHeight = legendItem.text ? legendItem.text.length : 0;
    return fontLineHeight * labelHeight;
  }
  function isListened(type, opts) {
    if ((type === 'mousemove' || type === 'mouseout') && (opts.onHover || opts.onLeave)) {
      return true;
    }
    if (opts.onClick && (type === 'click' || type === 'mouseup')) {
      return true;
    }
    return false;
  }
  var plugin_legend = {
    id: 'legend',
    _element: Legend,
    start: function start(chart, _args, options) {
      var legend = chart.legend = new Legend({
        ctx: chart.ctx,
        options: options,
        chart: chart
      });
      layouts.configure(chart, legend, options);
      layouts.addBox(chart, legend);
    },
    stop: function stop(chart) {
      layouts.removeBox(chart, chart.legend);
      delete chart.legend;
    },
    beforeUpdate: function beforeUpdate(chart, _args, options) {
      var legend = chart.legend;
      layouts.configure(chart, legend, options);
      legend.options = options;
    },
    afterUpdate: function afterUpdate(chart) {
      var legend = chart.legend;
      legend.buildLabels();
      legend.adjustHitBoxes();
    },
    afterEvent: function afterEvent(chart, args) {
      if (!args.replay) {
        chart.legend.handleEvent(args.event);
      }
    },
    defaults: {
      display: true,
      position: 'top',
      align: 'center',
      fullSize: true,
      reverse: false,
      weight: 1000,
      onClick: function onClick(e, legendItem, legend) {
        var index = legendItem.datasetIndex;
        var ci = legend.chart;
        if (ci.isDatasetVisible(index)) {
          ci.hide(index);
          legendItem.hidden = true;
        } else {
          ci.show(index);
          legendItem.hidden = false;
        }
      },
      onHover: null,
      onLeave: null,
      labels: {
        color: function color(ctx) {
          return ctx.chart.options.color;
        },
        boxWidth: 40,
        padding: 10,
        generateLabels: function generateLabels(chart) {
          var datasets = chart.data.datasets;
          var _chart$legend$options = chart.legend.options.labels,
            usePointStyle = _chart$legend$options.usePointStyle,
            pointStyle = _chart$legend$options.pointStyle,
            textAlign = _chart$legend$options.textAlign,
            color = _chart$legend$options.color,
            useBorderRadius = _chart$legend$options.useBorderRadius,
            borderRadius = _chart$legend$options.borderRadius;
          return chart._getSortedDatasetMetas().map(function (meta) {
            var style = meta.controller.getStyle(usePointStyle ? 0 : undefined);
            var borderWidth = toPadding(style.borderWidth);
            return {
              text: datasets[meta.index].label,
              fillStyle: style.backgroundColor,
              fontColor: color,
              hidden: !meta.visible,
              lineCap: style.borderCapStyle,
              lineDash: style.borderDash,
              lineDashOffset: style.borderDashOffset,
              lineJoin: style.borderJoinStyle,
              lineWidth: (borderWidth.width + borderWidth.height) / 4,
              strokeStyle: style.borderColor,
              pointStyle: pointStyle || style.pointStyle,
              rotation: style.rotation,
              textAlign: textAlign || style.textAlign,
              borderRadius: useBorderRadius && (borderRadius || style.borderRadius),
              datasetIndex: meta.index
            };
          }, this);
        }
      },
      title: {
        color: function color(ctx) {
          return ctx.chart.options.color;
        },
        display: false,
        position: 'center',
        text: ''
      }
    },
    descriptors: {
      _scriptable: function _scriptable(name) {
        return !name.startsWith('on');
      },
      labels: {
        _scriptable: function _scriptable(name) {
          return !['generateLabels', 'filter', 'sort'].includes(name);
        }
      }
    }
  };
  var Title = /*#__PURE__*/function (_Element8) {
    function Title(config) {
      var _this30;
      _classCallCheck$1(this, Title);
      _this30 = _callSuper(this, Title);
      _this30.chart = config.chart;
      _this30.options = config.options;
      _this30.ctx = config.ctx;
      _this30._padding = undefined;
      _this30.top = undefined;
      _this30.bottom = undefined;
      _this30.left = undefined;
      _this30.right = undefined;
      _this30.width = undefined;
      _this30.height = undefined;
      _this30.position = undefined;
      _this30.weight = undefined;
      _this30.fullSize = undefined;
      return _this30;
    }
    _inherits$1(Title, _Element8);
    return _createClass$1(Title, [{
      key: "update",
      value: function update(maxWidth, maxHeight) {
        var opts = this.options;
        this.left = 0;
        this.top = 0;
        if (!opts.display) {
          this.width = this.height = this.right = this.bottom = 0;
          return;
        }
        this.width = this.right = maxWidth;
        this.height = this.bottom = maxHeight;
        var lineCount = isArray(opts.text) ? opts.text.length : 1;
        this._padding = toPadding(opts.padding);
        var textSize = lineCount * toFont(opts.font).lineHeight + this._padding.height;
        if (this.isHorizontal()) {
          this.height = textSize;
        } else {
          this.width = textSize;
        }
      }
    }, {
      key: "isHorizontal",
      value: function isHorizontal() {
        var pos = this.options.position;
        return pos === 'top' || pos === 'bottom';
      }
    }, {
      key: "_drawArgs",
      value: function _drawArgs(offset) {
        var top = this.top,
          left = this.left,
          bottom = this.bottom,
          right = this.right,
          options = this.options;
        var align = options.align;
        var rotation = 0;
        var maxWidth, titleX, titleY;
        if (this.isHorizontal()) {
          titleX = _alignStartEnd(align, left, right);
          titleY = top + offset;
          maxWidth = right - left;
        } else {
          if (options.position === 'left') {
            titleX = left + offset;
            titleY = _alignStartEnd(align, bottom, top);
            rotation = PI * -0.5;
          } else {
            titleX = right - offset;
            titleY = _alignStartEnd(align, top, bottom);
            rotation = PI * 0.5;
          }
          maxWidth = bottom - top;
        }
        return {
          titleX: titleX,
          titleY: titleY,
          maxWidth: maxWidth,
          rotation: rotation
        };
      }
    }, {
      key: "draw",
      value: function draw() {
        var ctx = this.ctx;
        var opts = this.options;
        if (!opts.display) {
          return;
        }
        var fontOpts = toFont(opts.font);
        var lineHeight = fontOpts.lineHeight;
        var offset = lineHeight / 2 + this._padding.top;
        var _this$_drawArgs = this._drawArgs(offset),
          titleX = _this$_drawArgs.titleX,
          titleY = _this$_drawArgs.titleY,
          maxWidth = _this$_drawArgs.maxWidth,
          rotation = _this$_drawArgs.rotation;
        renderText(ctx, opts.text, 0, 0, fontOpts, {
          color: opts.color,
          maxWidth: maxWidth,
          rotation: rotation,
          textAlign: _toLeftRightCenter(opts.align),
          textBaseline: 'middle',
          translation: [titleX, titleY]
        });
      }
    }]);
  }(Element);
  function createTitle(chart, titleOpts) {
    var title = new Title({
      ctx: chart.ctx,
      options: titleOpts,
      chart: chart
    });
    layouts.configure(chart, title, titleOpts);
    layouts.addBox(chart, title);
    chart.titleBlock = title;
  }
  var plugin_title = {
    id: 'title',
    _element: Title,
    start: function start(chart, _args, options) {
      createTitle(chart, options);
    },
    stop: function stop(chart) {
      var titleBlock = chart.titleBlock;
      layouts.removeBox(chart, titleBlock);
      delete chart.titleBlock;
    },
    beforeUpdate: function beforeUpdate(chart, _args, options) {
      var title = chart.titleBlock;
      layouts.configure(chart, title, options);
      title.options = options;
    },
    defaults: {
      align: 'center',
      display: false,
      font: {
        weight: 'bold'
      },
      fullSize: true,
      padding: 10,
      position: 'top',
      text: '',
      weight: 2000
    },
    defaultRoutes: {
      color: 'color'
    },
    descriptors: {
      _scriptable: true,
      _indexable: false
    }
  };
  var map = new WeakMap();
  var plugin_subtitle = {
    id: 'subtitle',
    start: function start(chart, _args, options) {
      var title = new Title({
        ctx: chart.ctx,
        options: options,
        chart: chart
      });
      layouts.configure(chart, title, options);
      layouts.addBox(chart, title);
      map.set(chart, title);
    },
    stop: function stop(chart) {
      layouts.removeBox(chart, map.get(chart));
      map["delete"](chart);
    },
    beforeUpdate: function beforeUpdate(chart, _args, options) {
      var title = map.get(chart);
      layouts.configure(chart, title, options);
      title.options = options;
    },
    defaults: {
      align: 'center',
      display: false,
      font: {
        weight: 'normal'
      },
      fullSize: true,
      padding: 0,
      position: 'top',
      text: '',
      weight: 1500
    },
    defaultRoutes: {
      color: 'color'
    },
    descriptors: {
      _scriptable: true,
      _indexable: false
    }
  };
  var positioners = {
    average: function average(items) {
      if (!items.length) {
        return false;
      }
      var i, len;
      var xSet = new Set();
      var y = 0;
      var count = 0;
      for (i = 0, len = items.length; i < len; ++i) {
        var el = items[i].element;
        if (el && el.hasValue()) {
          var pos = el.tooltipPosition();
          xSet.add(pos.x);
          y += pos.y;
          ++count;
        }
      }
      var xAverage = _toConsumableArray(xSet).reduce(function (a, b) {
        return a + b;
      }) / xSet.size;
      return {
        x: xAverage,
        y: y / count
      };
    },
    nearest: function nearest(items, eventPosition) {
      if (!items.length) {
        return false;
      }
      var x = eventPosition.x;
      var y = eventPosition.y;
      var minDistance = Number.POSITIVE_INFINITY;
      var i, len, nearestElement;
      for (i = 0, len = items.length; i < len; ++i) {
        var el = items[i].element;
        if (el && el.hasValue()) {
          var center = el.getCenterPoint();
          var d = distanceBetweenPoints(eventPosition, center);
          if (d < minDistance) {
            minDistance = d;
            nearestElement = el;
          }
        }
      }
      if (nearestElement) {
        var tp = nearestElement.tooltipPosition();
        x = tp.x;
        y = tp.y;
      }
      return {
        x: x,
        y: y
      };
    }
  };
  function pushOrConcat(base, toPush) {
    if (toPush) {
      if (isArray(toPush)) {
        Array.prototype.push.apply(base, toPush);
      } else {
        base.push(toPush);
      }
    }
    return base;
  }
  function splitNewlines(str) {
    if ((typeof str === 'string' || str instanceof String) && str.indexOf('\n') > -1) {
      return str.split('\n');
    }
    return str;
  }
  function createTooltipItem(chart, item) {
    var element = item.element,
      datasetIndex = item.datasetIndex,
      index = item.index;
    var controller = chart.getDatasetMeta(datasetIndex).controller;
    var _controller$getLabelA = controller.getLabelAndValue(index),
      label = _controller$getLabelA.label,
      value = _controller$getLabelA.value;
    return {
      chart: chart,
      label: label,
      parsed: controller.getParsed(index),
      raw: chart.data.datasets[datasetIndex].data[index],
      formattedValue: value,
      dataset: controller.getDataset(),
      dataIndex: index,
      datasetIndex: datasetIndex,
      element: element
    };
  }
  function getTooltipSize(tooltip, options) {
    var ctx = tooltip.chart.ctx;
    var body = tooltip.body,
      footer = tooltip.footer,
      title = tooltip.title;
    var boxWidth = options.boxWidth,
      boxHeight = options.boxHeight;
    var bodyFont = toFont(options.bodyFont);
    var titleFont = toFont(options.titleFont);
    var footerFont = toFont(options.footerFont);
    var titleLineCount = title.length;
    var footerLineCount = footer.length;
    var bodyLineItemCount = body.length;
    var padding = toPadding(options.padding);
    var height = padding.height;
    var width = 0;
    var combinedBodyLength = body.reduce(function (count, bodyItem) {
      return count + bodyItem.before.length + bodyItem.lines.length + bodyItem.after.length;
    }, 0);
    combinedBodyLength += tooltip.beforeBody.length + tooltip.afterBody.length;
    if (titleLineCount) {
      height += titleLineCount * titleFont.lineHeight + (titleLineCount - 1) * options.titleSpacing + options.titleMarginBottom;
    }
    if (combinedBodyLength) {
      var bodyLineHeight = options.displayColors ? Math.max(boxHeight, bodyFont.lineHeight) : bodyFont.lineHeight;
      height += bodyLineItemCount * bodyLineHeight + (combinedBodyLength - bodyLineItemCount) * bodyFont.lineHeight + (combinedBodyLength - 1) * options.bodySpacing;
    }
    if (footerLineCount) {
      height += options.footerMarginTop + footerLineCount * footerFont.lineHeight + (footerLineCount - 1) * options.footerSpacing;
    }
    var widthPadding = 0;
    var maxLineWidth = function maxLineWidth(line) {
      width = Math.max(width, ctx.measureText(line).width + widthPadding);
    };
    ctx.save();
    ctx.font = titleFont.string;
    each(tooltip.title, maxLineWidth);
    ctx.font = bodyFont.string;
    each(tooltip.beforeBody.concat(tooltip.afterBody), maxLineWidth);
    widthPadding = options.displayColors ? boxWidth + 2 + options.boxPadding : 0;
    each(body, function (bodyItem) {
      each(bodyItem.before, maxLineWidth);
      each(bodyItem.lines, maxLineWidth);
      each(bodyItem.after, maxLineWidth);
    });
    widthPadding = 0;
    ctx.font = footerFont.string;
    each(tooltip.footer, maxLineWidth);
    ctx.restore();
    width += padding.width;
    return {
      width: width,
      height: height
    };
  }
  function determineYAlign(chart, size) {
    var y = size.y,
      height = size.height;
    if (y < height / 2) {
      return 'top';
    } else if (y > chart.height - height / 2) {
      return 'bottom';
    }
    return 'center';
  }
  function doesNotFitWithAlign(xAlign, chart, options, size) {
    var x = size.x,
      width = size.width;
    var caret = options.caretSize + options.caretPadding;
    if (xAlign === 'left' && x + width + caret > chart.width) {
      return true;
    }
    if (xAlign === 'right' && x - width - caret < 0) {
      return true;
    }
  }
  function determineXAlign(chart, options, size, yAlign) {
    var x = size.x,
      width = size.width;
    var chartWidth = chart.width,
      _chart$chartArea = chart.chartArea,
      left = _chart$chartArea.left,
      right = _chart$chartArea.right;
    var xAlign = 'center';
    if (yAlign === 'center') {
      xAlign = x <= (left + right) / 2 ? 'left' : 'right';
    } else if (x <= width / 2) {
      xAlign = 'left';
    } else if (x >= chartWidth - width / 2) {
      xAlign = 'right';
    }
    if (doesNotFitWithAlign(xAlign, chart, options, size)) {
      xAlign = 'center';
    }
    return xAlign;
  }
  function determineAlignment(chart, options, size) {
    var yAlign = size.yAlign || options.yAlign || determineYAlign(chart, size);
    return {
      xAlign: size.xAlign || options.xAlign || determineXAlign(chart, options, size, yAlign),
      yAlign: yAlign
    };
  }
  function alignX(size, xAlign) {
    var x = size.x,
      width = size.width;
    if (xAlign === 'right') {
      x -= width;
    } else if (xAlign === 'center') {
      x -= width / 2;
    }
    return x;
  }
  function alignY(size, yAlign, paddingAndSize) {
    var y = size.y,
      height = size.height;
    if (yAlign === 'top') {
      y += paddingAndSize;
    } else if (yAlign === 'bottom') {
      y -= height + paddingAndSize;
    } else {
      y -= height / 2;
    }
    return y;
  }
  function getBackgroundPoint(options, size, alignment, chart) {
    var caretSize = options.caretSize,
      caretPadding = options.caretPadding,
      cornerRadius = options.cornerRadius;
    var xAlign = alignment.xAlign,
      yAlign = alignment.yAlign;
    var paddingAndSize = caretSize + caretPadding;
    var _toTRBLCorners = toTRBLCorners(cornerRadius),
      topLeft = _toTRBLCorners.topLeft,
      topRight = _toTRBLCorners.topRight,
      bottomLeft = _toTRBLCorners.bottomLeft,
      bottomRight = _toTRBLCorners.bottomRight;
    var x = alignX(size, xAlign);
    var y = alignY(size, yAlign, paddingAndSize);
    if (yAlign === 'center') {
      if (xAlign === 'left') {
        x += paddingAndSize;
      } else if (xAlign === 'right') {
        x -= paddingAndSize;
      }
    } else if (xAlign === 'left') {
      x -= Math.max(topLeft, bottomLeft) + caretSize;
    } else if (xAlign === 'right') {
      x += Math.max(topRight, bottomRight) + caretSize;
    }
    return {
      x: _limitValue(x, 0, chart.width - size.width),
      y: _limitValue(y, 0, chart.height - size.height)
    };
  }
  function getAlignedX(tooltip, align, options) {
    var padding = toPadding(options.padding);
    return align === 'center' ? tooltip.x + tooltip.width / 2 : align === 'right' ? tooltip.x + tooltip.width - padding.right : tooltip.x + padding.left;
  }
  function getBeforeAfterBodyLines(callback) {
    return pushOrConcat([], splitNewlines(callback));
  }
  function createTooltipContext(parent, tooltip, tooltipItems) {
    return createContext(parent, {
      tooltip: tooltip,
      tooltipItems: tooltipItems,
      type: 'tooltip'
    });
  }
  function overrideCallbacks(callbacks, context) {
    var override = context && context.dataset && context.dataset.tooltip && context.dataset.tooltip.callbacks;
    return override ? callbacks.override(override) : callbacks;
  }
  var defaultCallbacks = {
    beforeTitle: noop,
    title: function title(tooltipItems) {
      if (tooltipItems.length > 0) {
        var item = tooltipItems[0];
        var labels = item.chart.data.labels;
        var labelCount = labels ? labels.length : 0;
        if (this && this.options && this.options.mode === 'dataset') {
          return item.dataset.label || '';
        } else if (item.label) {
          return item.label;
        } else if (labelCount > 0 && item.dataIndex < labelCount) {
          return labels[item.dataIndex];
        }
      }
      return '';
    },
    afterTitle: noop,
    beforeBody: noop,
    beforeLabel: noop,
    label: function label(tooltipItem) {
      if (this && this.options && this.options.mode === 'dataset') {
        return tooltipItem.label + ': ' + tooltipItem.formattedValue || tooltipItem.formattedValue;
      }
      var label = tooltipItem.dataset.label || '';
      if (label) {
        label += ': ';
      }
      var value = tooltipItem.formattedValue;
      if (!isNullOrUndef(value)) {
        label += value;
      }
      return label;
    },
    labelColor: function labelColor(tooltipItem) {
      var meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
      var options = meta.controller.getStyle(tooltipItem.dataIndex);
      return {
        borderColor: options.borderColor,
        backgroundColor: options.backgroundColor,
        borderWidth: options.borderWidth,
        borderDash: options.borderDash,
        borderDashOffset: options.borderDashOffset,
        borderRadius: 0
      };
    },
    labelTextColor: function labelTextColor() {
      return this.options.bodyColor;
    },
    labelPointStyle: function labelPointStyle(tooltipItem) {
      var meta = tooltipItem.chart.getDatasetMeta(tooltipItem.datasetIndex);
      var options = meta.controller.getStyle(tooltipItem.dataIndex);
      return {
        pointStyle: options.pointStyle,
        rotation: options.rotation
      };
    },
    afterLabel: noop,
    afterBody: noop,
    beforeFooter: noop,
    footer: noop,
    afterFooter: noop
  };
  function invokeCallbackWithFallback(callbacks, name, ctx, arg) {
    var result = callbacks[name].call(ctx, arg);
    if (typeof result === 'undefined') {
      return defaultCallbacks[name].call(ctx, arg);
    }
    return result;
  }
  var Tooltip = /*#__PURE__*/function (_Element9) {
    function Tooltip(config) {
      var _this31;
      _classCallCheck$1(this, Tooltip);
      _this31 = _callSuper(this, Tooltip);
      _this31.opacity = 0;
      _this31._active = [];
      _this31._eventPosition = undefined;
      _this31._size = undefined;
      _this31._cachedAnimations = undefined;
      _this31._tooltipItems = [];
      _this31.$animations = undefined;
      _this31.$context = undefined;
      _this31.chart = config.chart;
      _this31.options = config.options;
      _this31.dataPoints = undefined;
      _this31.title = undefined;
      _this31.beforeBody = undefined;
      _this31.body = undefined;
      _this31.afterBody = undefined;
      _this31.footer = undefined;
      _this31.xAlign = undefined;
      _this31.yAlign = undefined;
      _this31.x = undefined;
      _this31.y = undefined;
      _this31.height = undefined;
      _this31.width = undefined;
      _this31.caretX = undefined;
      _this31.caretY = undefined;
      _this31.labelColors = undefined;
      _this31.labelPointStyles = undefined;
      _this31.labelTextColors = undefined;
      return _this31;
    }
    _inherits$1(Tooltip, _Element9);
    return _createClass$1(Tooltip, [{
      key: "initialize",
      value: function initialize(options) {
        this.options = options;
        this._cachedAnimations = undefined;
        this.$context = undefined;
      }
    }, {
      key: "_resolveAnimations",
      value: function _resolveAnimations() {
        var cached = this._cachedAnimations;
        if (cached) {
          return cached;
        }
        var chart = this.chart;
        var options = this.options.setContext(this.getContext());
        var opts = options.enabled && chart.options.animation && options.animations;
        var animations = new Animations(this.chart, opts);
        if (opts._cacheable) {
          this._cachedAnimations = Object.freeze(animations);
        }
        return animations;
      }
    }, {
      key: "getContext",
      value: function getContext() {
        return this.$context || (this.$context = createTooltipContext(this.chart.getContext(), this, this._tooltipItems));
      }
    }, {
      key: "getTitle",
      value: function getTitle(context, options) {
        var callbacks = options.callbacks;
        var beforeTitle = invokeCallbackWithFallback(callbacks, 'beforeTitle', this, context);
        var title = invokeCallbackWithFallback(callbacks, 'title', this, context);
        var afterTitle = invokeCallbackWithFallback(callbacks, 'afterTitle', this, context);
        var lines = [];
        lines = pushOrConcat(lines, splitNewlines(beforeTitle));
        lines = pushOrConcat(lines, splitNewlines(title));
        lines = pushOrConcat(lines, splitNewlines(afterTitle));
        return lines;
      }
    }, {
      key: "getBeforeBody",
      value: function getBeforeBody(tooltipItems, options) {
        return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'beforeBody', this, tooltipItems));
      }
    }, {
      key: "getBody",
      value: function getBody(tooltipItems, options) {
        var _this32 = this;
        var callbacks = options.callbacks;
        var bodyItems = [];
        each(tooltipItems, function (context) {
          var bodyItem = {
            before: [],
            lines: [],
            after: []
          };
          var scoped = overrideCallbacks(callbacks, context);
          pushOrConcat(bodyItem.before, splitNewlines(invokeCallbackWithFallback(scoped, 'beforeLabel', _this32, context)));
          pushOrConcat(bodyItem.lines, invokeCallbackWithFallback(scoped, 'label', _this32, context));
          pushOrConcat(bodyItem.after, splitNewlines(invokeCallbackWithFallback(scoped, 'afterLabel', _this32, context)));
          bodyItems.push(bodyItem);
        });
        return bodyItems;
      }
    }, {
      key: "getAfterBody",
      value: function getAfterBody(tooltipItems, options) {
        return getBeforeAfterBodyLines(invokeCallbackWithFallback(options.callbacks, 'afterBody', this, tooltipItems));
      }
    }, {
      key: "getFooter",
      value: function getFooter(tooltipItems, options) {
        var callbacks = options.callbacks;
        var beforeFooter = invokeCallbackWithFallback(callbacks, 'beforeFooter', this, tooltipItems);
        var footer = invokeCallbackWithFallback(callbacks, 'footer', this, tooltipItems);
        var afterFooter = invokeCallbackWithFallback(callbacks, 'afterFooter', this, tooltipItems);
        var lines = [];
        lines = pushOrConcat(lines, splitNewlines(beforeFooter));
        lines = pushOrConcat(lines, splitNewlines(footer));
        lines = pushOrConcat(lines, splitNewlines(afterFooter));
        return lines;
      }
    }, {
      key: "_createItems",
      value: function _createItems(options) {
        var _this33 = this;
        var active = this._active;
        var data = this.chart.data;
        var labelColors = [];
        var labelPointStyles = [];
        var labelTextColors = [];
        var tooltipItems = [];
        var i, len;
        for (i = 0, len = active.length; i < len; ++i) {
          tooltipItems.push(createTooltipItem(this.chart, active[i]));
        }
        if (options.filter) {
          tooltipItems = tooltipItems.filter(function (element, index, array) {
            return options.filter(element, index, array, data);
          });
        }
        if (options.itemSort) {
          tooltipItems = tooltipItems.sort(function (a, b) {
            return options.itemSort(a, b, data);
          });
        }
        each(tooltipItems, function (context) {
          var scoped = overrideCallbacks(options.callbacks, context);
          labelColors.push(invokeCallbackWithFallback(scoped, 'labelColor', _this33, context));
          labelPointStyles.push(invokeCallbackWithFallback(scoped, 'labelPointStyle', _this33, context));
          labelTextColors.push(invokeCallbackWithFallback(scoped, 'labelTextColor', _this33, context));
        });
        this.labelColors = labelColors;
        this.labelPointStyles = labelPointStyles;
        this.labelTextColors = labelTextColors;
        this.dataPoints = tooltipItems;
        return tooltipItems;
      }
    }, {
      key: "update",
      value: function update(changed, replay) {
        var options = this.options.setContext(this.getContext());
        var active = this._active;
        var properties;
        var tooltipItems = [];
        if (!active.length) {
          if (this.opacity !== 0) {
            properties = {
              opacity: 0
            };
          }
        } else {
          var position = positioners[options.position].call(this, active, this._eventPosition);
          tooltipItems = this._createItems(options);
          this.title = this.getTitle(tooltipItems, options);
          this.beforeBody = this.getBeforeBody(tooltipItems, options);
          this.body = this.getBody(tooltipItems, options);
          this.afterBody = this.getAfterBody(tooltipItems, options);
          this.footer = this.getFooter(tooltipItems, options);
          var size = this._size = getTooltipSize(this, options);
          var positionAndSize = Object.assign({}, position, size);
          var alignment = determineAlignment(this.chart, options, positionAndSize);
          var backgroundPoint = getBackgroundPoint(options, positionAndSize, alignment, this.chart);
          this.xAlign = alignment.xAlign;
          this.yAlign = alignment.yAlign;
          properties = {
            opacity: 1,
            x: backgroundPoint.x,
            y: backgroundPoint.y,
            width: size.width,
            height: size.height,
            caretX: position.x,
            caretY: position.y
          };
        }
        this._tooltipItems = tooltipItems;
        this.$context = undefined;
        if (properties) {
          this._resolveAnimations().update(this, properties);
        }
        if (changed && options.external) {
          options.external.call(this, {
            chart: this.chart,
            tooltip: this,
            replay: replay
          });
        }
      }
    }, {
      key: "drawCaret",
      value: function drawCaret(tooltipPoint, ctx, size, options) {
        var caretPosition = this.getCaretPosition(tooltipPoint, size, options);
        ctx.lineTo(caretPosition.x1, caretPosition.y1);
        ctx.lineTo(caretPosition.x2, caretPosition.y2);
        ctx.lineTo(caretPosition.x3, caretPosition.y3);
      }
    }, {
      key: "getCaretPosition",
      value: function getCaretPosition(tooltipPoint, size, options) {
        var xAlign = this.xAlign,
          yAlign = this.yAlign;
        var caretSize = options.caretSize,
          cornerRadius = options.cornerRadius;
        var _toTRBLCorners2 = toTRBLCorners(cornerRadius),
          topLeft = _toTRBLCorners2.topLeft,
          topRight = _toTRBLCorners2.topRight,
          bottomLeft = _toTRBLCorners2.bottomLeft,
          bottomRight = _toTRBLCorners2.bottomRight;
        var ptX = tooltipPoint.x,
          ptY = tooltipPoint.y;
        var width = size.width,
          height = size.height;
        var x1, x2, x3, y1, y2, y3;
        if (yAlign === 'center') {
          y2 = ptY + height / 2;
          if (xAlign === 'left') {
            x1 = ptX;
            x2 = x1 - caretSize;
            y1 = y2 + caretSize;
            y3 = y2 - caretSize;
          } else {
            x1 = ptX + width;
            x2 = x1 + caretSize;
            y1 = y2 - caretSize;
            y3 = y2 + caretSize;
          }
          x3 = x1;
        } else {
          if (xAlign === 'left') {
            x2 = ptX + Math.max(topLeft, bottomLeft) + caretSize;
          } else if (xAlign === 'right') {
            x2 = ptX + width - Math.max(topRight, bottomRight) - caretSize;
          } else {
            x2 = this.caretX;
          }
          if (yAlign === 'top') {
            y1 = ptY;
            y2 = y1 - caretSize;
            x1 = x2 - caretSize;
            x3 = x2 + caretSize;
          } else {
            y1 = ptY + height;
            y2 = y1 + caretSize;
            x1 = x2 + caretSize;
            x3 = x2 - caretSize;
          }
          y3 = y1;
        }
        return {
          x1: x1,
          x2: x2,
          x3: x3,
          y1: y1,
          y2: y2,
          y3: y3
        };
      }
    }, {
      key: "drawTitle",
      value: function drawTitle(pt, ctx, options) {
        var title = this.title;
        var length = title.length;
        var titleFont, titleSpacing, i;
        if (length) {
          var rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
          pt.x = getAlignedX(this, options.titleAlign, options);
          ctx.textAlign = rtlHelper.textAlign(options.titleAlign);
          ctx.textBaseline = 'middle';
          titleFont = toFont(options.titleFont);
          titleSpacing = options.titleSpacing;
          ctx.fillStyle = options.titleColor;
          ctx.font = titleFont.string;
          for (i = 0; i < length; ++i) {
            ctx.fillText(title[i], rtlHelper.x(pt.x), pt.y + titleFont.lineHeight / 2);
            pt.y += titleFont.lineHeight + titleSpacing;
            if (i + 1 === length) {
              pt.y += options.titleMarginBottom - titleSpacing;
            }
          }
        }
      }
    }, {
      key: "_drawColorBox",
      value: function _drawColorBox(ctx, pt, i, rtlHelper, options) {
        var labelColor = this.labelColors[i];
        var labelPointStyle = this.labelPointStyles[i];
        var boxHeight = options.boxHeight,
          boxWidth = options.boxWidth;
        var bodyFont = toFont(options.bodyFont);
        var colorX = getAlignedX(this, 'left', options);
        var rtlColorX = rtlHelper.x(colorX);
        var yOffSet = boxHeight < bodyFont.lineHeight ? (bodyFont.lineHeight - boxHeight) / 2 : 0;
        var colorY = pt.y + yOffSet;
        if (options.usePointStyle) {
          var drawOptions = {
            radius: Math.min(boxWidth, boxHeight) / 2,
            pointStyle: labelPointStyle.pointStyle,
            rotation: labelPointStyle.rotation,
            borderWidth: 1
          };
          var centerX = rtlHelper.leftForLtr(rtlColorX, boxWidth) + boxWidth / 2;
          var centerY = colorY + boxHeight / 2;
          ctx.strokeStyle = options.multiKeyBackground;
          ctx.fillStyle = options.multiKeyBackground;
          drawPoint(ctx, drawOptions, centerX, centerY);
          ctx.strokeStyle = labelColor.borderColor;
          ctx.fillStyle = labelColor.backgroundColor;
          drawPoint(ctx, drawOptions, centerX, centerY);
        } else {
          ctx.lineWidth = isObject(labelColor.borderWidth) ? Math.max.apply(Math, _toConsumableArray(Object.values(labelColor.borderWidth))) : labelColor.borderWidth || 1;
          ctx.strokeStyle = labelColor.borderColor;
          ctx.setLineDash(labelColor.borderDash || []);
          ctx.lineDashOffset = labelColor.borderDashOffset || 0;
          var outerX = rtlHelper.leftForLtr(rtlColorX, boxWidth);
          var innerX = rtlHelper.leftForLtr(rtlHelper.xPlus(rtlColorX, 1), boxWidth - 2);
          var borderRadius = toTRBLCorners(labelColor.borderRadius);
          if (Object.values(borderRadius).some(function (v) {
            return v !== 0;
          })) {
            ctx.beginPath();
            ctx.fillStyle = options.multiKeyBackground;
            addRoundedRectPath(ctx, {
              x: outerX,
              y: colorY,
              w: boxWidth,
              h: boxHeight,
              radius: borderRadius
            });
            ctx.fill();
            ctx.stroke();
            ctx.fillStyle = labelColor.backgroundColor;
            ctx.beginPath();
            addRoundedRectPath(ctx, {
              x: innerX,
              y: colorY + 1,
              w: boxWidth - 2,
              h: boxHeight - 2,
              radius: borderRadius
            });
            ctx.fill();
          } else {
            ctx.fillStyle = options.multiKeyBackground;
            ctx.fillRect(outerX, colorY, boxWidth, boxHeight);
            ctx.strokeRect(outerX, colorY, boxWidth, boxHeight);
            ctx.fillStyle = labelColor.backgroundColor;
            ctx.fillRect(innerX, colorY + 1, boxWidth - 2, boxHeight - 2);
          }
        }
        ctx.fillStyle = this.labelTextColors[i];
      }
    }, {
      key: "drawBody",
      value: function drawBody(pt, ctx, options) {
        var body = this.body;
        var bodySpacing = options.bodySpacing,
          bodyAlign = options.bodyAlign,
          displayColors = options.displayColors,
          boxHeight = options.boxHeight,
          boxWidth = options.boxWidth,
          boxPadding = options.boxPadding;
        var bodyFont = toFont(options.bodyFont);
        var bodyLineHeight = bodyFont.lineHeight;
        var xLinePadding = 0;
        var rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
        var fillLineOfText = function fillLineOfText(line) {
          ctx.fillText(line, rtlHelper.x(pt.x + xLinePadding), pt.y + bodyLineHeight / 2);
          pt.y += bodyLineHeight + bodySpacing;
        };
        var bodyAlignForCalculation = rtlHelper.textAlign(bodyAlign);
        var bodyItem, textColor, lines, i, j, ilen, jlen;
        ctx.textAlign = bodyAlign;
        ctx.textBaseline = 'middle';
        ctx.font = bodyFont.string;
        pt.x = getAlignedX(this, bodyAlignForCalculation, options);
        ctx.fillStyle = options.bodyColor;
        each(this.beforeBody, fillLineOfText);
        xLinePadding = displayColors && bodyAlignForCalculation !== 'right' ? bodyAlign === 'center' ? boxWidth / 2 + boxPadding : boxWidth + 2 + boxPadding : 0;
        for (i = 0, ilen = body.length; i < ilen; ++i) {
          bodyItem = body[i];
          textColor = this.labelTextColors[i];
          ctx.fillStyle = textColor;
          each(bodyItem.before, fillLineOfText);
          lines = bodyItem.lines;
          if (displayColors && lines.length) {
            this._drawColorBox(ctx, pt, i, rtlHelper, options);
            bodyLineHeight = Math.max(bodyFont.lineHeight, boxHeight);
          }
          for (j = 0, jlen = lines.length; j < jlen; ++j) {
            fillLineOfText(lines[j]);
            bodyLineHeight = bodyFont.lineHeight;
          }
          each(bodyItem.after, fillLineOfText);
        }
        xLinePadding = 0;
        bodyLineHeight = bodyFont.lineHeight;
        each(this.afterBody, fillLineOfText);
        pt.y -= bodySpacing;
      }
    }, {
      key: "drawFooter",
      value: function drawFooter(pt, ctx, options) {
        var footer = this.footer;
        var length = footer.length;
        var footerFont, i;
        if (length) {
          var rtlHelper = getRtlAdapter(options.rtl, this.x, this.width);
          pt.x = getAlignedX(this, options.footerAlign, options);
          pt.y += options.footerMarginTop;
          ctx.textAlign = rtlHelper.textAlign(options.footerAlign);
          ctx.textBaseline = 'middle';
          footerFont = toFont(options.footerFont);
          ctx.fillStyle = options.footerColor;
          ctx.font = footerFont.string;
          for (i = 0; i < length; ++i) {
            ctx.fillText(footer[i], rtlHelper.x(pt.x), pt.y + footerFont.lineHeight / 2);
            pt.y += footerFont.lineHeight + options.footerSpacing;
          }
        }
      }
    }, {
      key: "drawBackground",
      value: function drawBackground(pt, ctx, tooltipSize, options) {
        var xAlign = this.xAlign,
          yAlign = this.yAlign;
        var x = pt.x,
          y = pt.y;
        var width = tooltipSize.width,
          height = tooltipSize.height;
        var _toTRBLCorners3 = toTRBLCorners(options.cornerRadius),
          topLeft = _toTRBLCorners3.topLeft,
          topRight = _toTRBLCorners3.topRight,
          bottomLeft = _toTRBLCorners3.bottomLeft,
          bottomRight = _toTRBLCorners3.bottomRight;
        ctx.fillStyle = options.backgroundColor;
        ctx.strokeStyle = options.borderColor;
        ctx.lineWidth = options.borderWidth;
        ctx.beginPath();
        ctx.moveTo(x + topLeft, y);
        if (yAlign === 'top') {
          this.drawCaret(pt, ctx, tooltipSize, options);
        }
        ctx.lineTo(x + width - topRight, y);
        ctx.quadraticCurveTo(x + width, y, x + width, y + topRight);
        if (yAlign === 'center' && xAlign === 'right') {
          this.drawCaret(pt, ctx, tooltipSize, options);
        }
        ctx.lineTo(x + width, y + height - bottomRight);
        ctx.quadraticCurveTo(x + width, y + height, x + width - bottomRight, y + height);
        if (yAlign === 'bottom') {
          this.drawCaret(pt, ctx, tooltipSize, options);
        }
        ctx.lineTo(x + bottomLeft, y + height);
        ctx.quadraticCurveTo(x, y + height, x, y + height - bottomLeft);
        if (yAlign === 'center' && xAlign === 'left') {
          this.drawCaret(pt, ctx, tooltipSize, options);
        }
        ctx.lineTo(x, y + topLeft);
        ctx.quadraticCurveTo(x, y, x + topLeft, y);
        ctx.closePath();
        ctx.fill();
        if (options.borderWidth > 0) {
          ctx.stroke();
        }
      }
    }, {
      key: "_updateAnimationTarget",
      value: function _updateAnimationTarget(options) {
        var chart = this.chart;
        var anims = this.$animations;
        var animX = anims && anims.x;
        var animY = anims && anims.y;
        if (animX || animY) {
          var position = positioners[options.position].call(this, this._active, this._eventPosition);
          if (!position) {
            return;
          }
          var size = this._size = getTooltipSize(this, options);
          var positionAndSize = Object.assign({}, position, this._size);
          var alignment = determineAlignment(chart, options, positionAndSize);
          var point = getBackgroundPoint(options, positionAndSize, alignment, chart);
          if (animX._to !== point.x || animY._to !== point.y) {
            this.xAlign = alignment.xAlign;
            this.yAlign = alignment.yAlign;
            this.width = size.width;
            this.height = size.height;
            this.caretX = position.x;
            this.caretY = position.y;
            this._resolveAnimations().update(this, point);
          }
        }
      }
    }, {
      key: "_willRender",
      value: function _willRender() {
        return !!this.opacity;
      }
    }, {
      key: "draw",
      value: function draw(ctx) {
        var options = this.options.setContext(this.getContext());
        var opacity = this.opacity;
        if (!opacity) {
          return;
        }
        this._updateAnimationTarget(options);
        var tooltipSize = {
          width: this.width,
          height: this.height
        };
        var pt = {
          x: this.x,
          y: this.y
        };
        opacity = Math.abs(opacity) < 1e-3 ? 0 : opacity;
        var padding = toPadding(options.padding);
        var hasTooltipContent = this.title.length || this.beforeBody.length || this.body.length || this.afterBody.length || this.footer.length;
        if (options.enabled && hasTooltipContent) {
          ctx.save();
          ctx.globalAlpha = opacity;
          this.drawBackground(pt, ctx, tooltipSize, options);
          overrideTextDirection(ctx, options.textDirection);
          pt.y += padding.top;
          this.drawTitle(pt, ctx, options);
          this.drawBody(pt, ctx, options);
          this.drawFooter(pt, ctx, options);
          restoreTextDirection(ctx, options.textDirection);
          ctx.restore();
        }
      }
    }, {
      key: "getActiveElements",
      value: function getActiveElements() {
        return this._active || [];
      }
    }, {
      key: "setActiveElements",
      value: function setActiveElements(activeElements, eventPosition) {
        var _this34 = this;
        var lastActive = this._active;
        var active = activeElements.map(function (_ref11) {
          var datasetIndex = _ref11.datasetIndex,
            index = _ref11.index;
          var meta = _this34.chart.getDatasetMeta(datasetIndex);
          if (!meta) {
            throw new Error('Cannot find a dataset at index ' + datasetIndex);
          }
          return {
            datasetIndex: datasetIndex,
            element: meta.data[index],
            index: index
          };
        });
        var changed = !_elementsEqual(lastActive, active);
        var positionChanged = this._positionChanged(active, eventPosition);
        if (changed || positionChanged) {
          this._active = active;
          this._eventPosition = eventPosition;
          this._ignoreReplayEvents = true;
          this.update(true);
        }
      }
    }, {
      key: "handleEvent",
      value: function handleEvent(e, replay) {
        var inChartArea = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
        if (replay && this._ignoreReplayEvents) {
          return false;
        }
        this._ignoreReplayEvents = false;
        var options = this.options;
        var lastActive = this._active || [];
        var active = this._getActiveElements(e, lastActive, replay, inChartArea);
        var positionChanged = this._positionChanged(active, e);
        var changed = replay || !_elementsEqual(active, lastActive) || positionChanged;
        if (changed) {
          this._active = active;
          if (options.enabled || options.external) {
            this._eventPosition = {
              x: e.x,
              y: e.y
            };
            this.update(true, replay);
          }
        }
        return changed;
      }
    }, {
      key: "_getActiveElements",
      value: function _getActiveElements(e, lastActive, replay, inChartArea) {
        var _this35 = this;
        var options = this.options;
        if (e.type === 'mouseout') {
          return [];
        }
        if (!inChartArea) {
          return lastActive.filter(function (i) {
            return _this35.chart.data.datasets[i.datasetIndex] && _this35.chart.getDatasetMeta(i.datasetIndex).controller.getParsed(i.index) !== undefined;
          });
        }
        var active = this.chart.getElementsAtEventForMode(e, options.mode, options, replay);
        if (options.reverse) {
          active.reverse();
        }
        return active;
      }
    }, {
      key: "_positionChanged",
      value: function _positionChanged(active, e) {
        var caretX = this.caretX,
          caretY = this.caretY,
          options = this.options;
        var position = positioners[options.position].call(this, active, e);
        return position !== false && (caretX !== position.x || caretY !== position.y);
      }
    }]);
  }(Element);
  _defineProperty$1(Tooltip, "positioners", positioners);
  var plugin_tooltip = {
    id: 'tooltip',
    _element: Tooltip,
    positioners: positioners,
    afterInit: function afterInit(chart, _args, options) {
      if (options) {
        chart.tooltip = new Tooltip({
          chart: chart,
          options: options
        });
      }
    },
    beforeUpdate: function beforeUpdate(chart, _args, options) {
      if (chart.tooltip) {
        chart.tooltip.initialize(options);
      }
    },
    reset: function reset(chart, _args, options) {
      if (chart.tooltip) {
        chart.tooltip.initialize(options);
      }
    },
    afterDraw: function afterDraw(chart) {
      var tooltip = chart.tooltip;
      if (tooltip && tooltip._willRender()) {
        var args = {
          tooltip: tooltip
        };
        if (chart.notifyPlugins('beforeTooltipDraw', _objectSpread2(_objectSpread2({}, args), {}, {
          cancelable: true
        })) === false) {
          return;
        }
        tooltip.draw(chart.ctx);
        chart.notifyPlugins('afterTooltipDraw', args);
      }
    },
    afterEvent: function afterEvent(chart, args) {
      if (chart.tooltip) {
        var useFinalPosition = args.replay;
        if (chart.tooltip.handleEvent(args.event, useFinalPosition, args.inChartArea)) {
          args.changed = true;
        }
      }
    },
    defaults: {
      enabled: true,
      external: null,
      position: 'average',
      backgroundColor: 'rgba(0,0,0,0.8)',
      titleColor: '#fff',
      titleFont: {
        weight: 'bold'
      },
      titleSpacing: 2,
      titleMarginBottom: 6,
      titleAlign: 'left',
      bodyColor: '#fff',
      bodySpacing: 2,
      bodyFont: {},
      bodyAlign: 'left',
      footerColor: '#fff',
      footerSpacing: 2,
      footerMarginTop: 6,
      footerFont: {
        weight: 'bold'
      },
      footerAlign: 'left',
      padding: 6,
      caretPadding: 2,
      caretSize: 5,
      cornerRadius: 6,
      boxHeight: function boxHeight(ctx, opts) {
        return opts.bodyFont.size;
      },
      boxWidth: function boxWidth(ctx, opts) {
        return opts.bodyFont.size;
      },
      multiKeyBackground: '#fff',
      displayColors: true,
      boxPadding: 0,
      borderColor: 'rgba(0,0,0,0)',
      borderWidth: 0,
      animation: {
        duration: 400,
        easing: 'easeOutQuart'
      },
      animations: {
        numbers: {
          type: 'number',
          properties: ['x', 'y', 'width', 'height', 'caretX', 'caretY']
        },
        opacity: {
          easing: 'linear',
          duration: 200
        }
      },
      callbacks: defaultCallbacks
    },
    defaultRoutes: {
      bodyFont: 'font',
      footerFont: 'font',
      titleFont: 'font'
    },
    descriptors: {
      _scriptable: function _scriptable(name) {
        return name !== 'filter' && name !== 'itemSort' && name !== 'external';
      },
      _indexable: false,
      callbacks: {
        _scriptable: false,
        _indexable: false
      },
      animation: {
        _fallback: false
      },
      animations: {
        _fallback: 'animation'
      }
    },
    additionalOptionScopes: ['interaction']
  };
  var plugins = /*#__PURE__*/Object.freeze({
    __proto__: null,
    Colors: plugin_colors,
    Decimation: plugin_decimation,
    Filler: index,
    Legend: plugin_legend,
    SubTitle: plugin_subtitle,
    Title: plugin_title,
    Tooltip: plugin_tooltip
  });
  var addIfString = function addIfString(labels, raw, index, addedLabels) {
    if (typeof raw === 'string') {
      index = labels.push(raw) - 1;
      addedLabels.unshift({
        index: index,
        label: raw
      });
    } else if (isNaN(raw)) {
      index = null;
    }
    return index;
  };
  function findOrAddLabel(labels, raw, index, addedLabels) {
    var first = labels.indexOf(raw);
    if (first === -1) {
      return addIfString(labels, raw, index, addedLabels);
    }
    var last = labels.lastIndexOf(raw);
    return first !== last ? index : first;
  }
  var validIndex = function validIndex(index, max) {
    return index === null ? null : _limitValue(Math.round(index), 0, max);
  };
  function _getLabelForValue(value) {
    var labels = this.getLabels();
    if (value >= 0 && value < labels.length) {
      return labels[value];
    }
    return value;
  }
  var CategoryScale = /*#__PURE__*/function (_Scale) {
    function CategoryScale(cfg) {
      var _this36;
      _classCallCheck$1(this, CategoryScale);
      _this36 = _callSuper(this, CategoryScale, [cfg]);
      _this36._startValue = undefined;
      _this36._valueRange = 0;
      _this36._addedLabels = [];
      return _this36;
    }
    _inherits$1(CategoryScale, _Scale);
    return _createClass$1(CategoryScale, [{
      key: "init",
      value: function init(scaleOptions) {
        var added = this._addedLabels;
        if (added.length) {
          var labels = this.getLabels();
          var _iterator25 = _createForOfIteratorHelper$1(added),
            _step25;
          try {
            for (_iterator25.s(); !(_step25 = _iterator25.n()).done;) {
              var _step25$value = _step25.value,
                _index3 = _step25$value.index,
                label = _step25$value.label;
              if (labels[_index3] === label) {
                labels.splice(_index3, 1);
              }
            }
          } catch (err) {
            _iterator25.e(err);
          } finally {
            _iterator25.f();
          }
          this._addedLabels = [];
        }
        _get(_getPrototypeOf$1(CategoryScale.prototype), "init", this).call(this, scaleOptions);
      }
    }, {
      key: "parse",
      value: function parse(raw, index) {
        if (isNullOrUndef(raw)) {
          return null;
        }
        var labels = this.getLabels();
        index = isFinite(index) && labels[index] === raw ? index : findOrAddLabel(labels, raw, valueOrDefault(index, raw), this._addedLabels);
        return validIndex(index, labels.length - 1);
      }
    }, {
      key: "determineDataLimits",
      value: function determineDataLimits() {
        var _this$getUserBounds2 = this.getUserBounds(),
          minDefined = _this$getUserBounds2.minDefined,
          maxDefined = _this$getUserBounds2.maxDefined;
        var _this$getMinMax = this.getMinMax(true),
          min = _this$getMinMax.min,
          max = _this$getMinMax.max;
        if (this.options.bounds === 'ticks') {
          if (!minDefined) {
            min = 0;
          }
          if (!maxDefined) {
            max = this.getLabels().length - 1;
          }
        }
        this.min = min;
        this.max = max;
      }
    }, {
      key: "buildTicks",
      value: function buildTicks() {
        var min = this.min;
        var max = this.max;
        var offset = this.options.offset;
        var ticks = [];
        var labels = this.getLabels();
        labels = min === 0 && max === labels.length - 1 ? labels : labels.slice(min, max + 1);
        this._valueRange = Math.max(labels.length - (offset ? 0 : 1), 1);
        this._startValue = this.min - (offset ? 0.5 : 0);
        for (var value = min; value <= max; value++) {
          ticks.push({
            value: value
          });
        }
        return ticks;
      }
    }, {
      key: "getLabelForValue",
      value: function getLabelForValue(value) {
        return _getLabelForValue.call(this, value);
      }
    }, {
      key: "configure",
      value: function configure() {
        _get(_getPrototypeOf$1(CategoryScale.prototype), "configure", this).call(this);
        if (!this.isHorizontal()) {
          this._reversePixels = !this._reversePixels;
        }
      }
    }, {
      key: "getPixelForValue",
      value: function getPixelForValue(value) {
        if (typeof value !== 'number') {
          value = this.parse(value);
        }
        return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
      }
    }, {
      key: "getPixelForTick",
      value: function getPixelForTick(index) {
        var ticks = this.ticks;
        if (index < 0 || index > ticks.length - 1) {
          return null;
        }
        return this.getPixelForValue(ticks[index].value);
      }
    }, {
      key: "getValueForPixel",
      value: function getValueForPixel(pixel) {
        return Math.round(this._startValue + this.getDecimalForPixel(pixel) * this._valueRange);
      }
    }, {
      key: "getBasePixel",
      value: function getBasePixel() {
        return this.bottom;
      }
    }]);
  }(Scale);
  _defineProperty$1(CategoryScale, "id", 'category');
  _defineProperty$1(CategoryScale, "defaults", {
    ticks: {
      callback: _getLabelForValue
    }
  });
  function generateTicks$1(generationOptions, dataRange) {
    var ticks = [];
    var MIN_SPACING = 1e-14;
    var bounds = generationOptions.bounds,
      step = generationOptions.step,
      min = generationOptions.min,
      max = generationOptions.max,
      precision = generationOptions.precision,
      count = generationOptions.count,
      maxTicks = generationOptions.maxTicks,
      maxDigits = generationOptions.maxDigits,
      includeBounds = generationOptions.includeBounds;
    var unit = step || 1;
    var maxSpaces = maxTicks - 1;
    var rmin = dataRange.min,
      rmax = dataRange.max;
    var minDefined = !isNullOrUndef(min);
    var maxDefined = !isNullOrUndef(max);
    var countDefined = !isNullOrUndef(count);
    var minSpacing = (rmax - rmin) / (maxDigits + 1);
    var spacing = niceNum((rmax - rmin) / maxSpaces / unit) * unit;
    var factor, niceMin, niceMax, numSpaces;
    if (spacing < MIN_SPACING && !minDefined && !maxDefined) {
      return [{
        value: rmin
      }, {
        value: rmax
      }];
    }
    numSpaces = Math.ceil(rmax / spacing) - Math.floor(rmin / spacing);
    if (numSpaces > maxSpaces) {
      spacing = niceNum(numSpaces * spacing / maxSpaces / unit) * unit;
    }
    if (!isNullOrUndef(precision)) {
      factor = Math.pow(10, precision);
      spacing = Math.ceil(spacing * factor) / factor;
    }
    if (bounds === 'ticks') {
      niceMin = Math.floor(rmin / spacing) * spacing;
      niceMax = Math.ceil(rmax / spacing) * spacing;
    } else {
      niceMin = rmin;
      niceMax = rmax;
    }
    if (minDefined && maxDefined && step && almostWhole((max - min) / step, spacing / 1000)) {
      numSpaces = Math.round(Math.min((max - min) / spacing, maxTicks));
      spacing = (max - min) / numSpaces;
      niceMin = min;
      niceMax = max;
    } else if (countDefined) {
      niceMin = minDefined ? min : niceMin;
      niceMax = maxDefined ? max : niceMax;
      numSpaces = count - 1;
      spacing = (niceMax - niceMin) / numSpaces;
    } else {
      numSpaces = (niceMax - niceMin) / spacing;
      if (almostEquals(numSpaces, Math.round(numSpaces), spacing / 1000)) {
        numSpaces = Math.round(numSpaces);
      } else {
        numSpaces = Math.ceil(numSpaces);
      }
    }
    var decimalPlaces = Math.max(_decimalPlaces(spacing), _decimalPlaces(niceMin));
    factor = Math.pow(10, isNullOrUndef(precision) ? decimalPlaces : precision);
    niceMin = Math.round(niceMin * factor) / factor;
    niceMax = Math.round(niceMax * factor) / factor;
    var j = 0;
    if (minDefined) {
      if (includeBounds && niceMin !== min) {
        ticks.push({
          value: min
        });
        if (niceMin < min) {
          j++;
        }
        if (almostEquals(Math.round((niceMin + j * spacing) * factor) / factor, min, relativeLabelSize(min, minSpacing, generationOptions))) {
          j++;
        }
      } else if (niceMin < min) {
        j++;
      }
    }
    for (; j < numSpaces; ++j) {
      var tickValue = Math.round((niceMin + j * spacing) * factor) / factor;
      if (maxDefined && tickValue > max) {
        break;
      }
      ticks.push({
        value: tickValue
      });
    }
    if (maxDefined && includeBounds && niceMax !== max) {
      if (ticks.length && almostEquals(ticks[ticks.length - 1].value, max, relativeLabelSize(max, minSpacing, generationOptions))) {
        ticks[ticks.length - 1].value = max;
      } else {
        ticks.push({
          value: max
        });
      }
    } else if (!maxDefined || niceMax === max) {
      ticks.push({
        value: niceMax
      });
    }
    return ticks;
  }
  function relativeLabelSize(value, minSpacing, _ref12) {
    var horizontal = _ref12.horizontal,
      minRotation = _ref12.minRotation;
    var rad = toRadians(minRotation);
    var ratio = (horizontal ? Math.sin(rad) : Math.cos(rad)) || 0.001;
    var length = 0.75 * minSpacing * ('' + value).length;
    return Math.min(minSpacing / ratio, length);
  }
  var LinearScaleBase = /*#__PURE__*/function (_Scale2) {
    function LinearScaleBase(cfg) {
      var _this37;
      _classCallCheck$1(this, LinearScaleBase);
      _this37 = _callSuper(this, LinearScaleBase, [cfg]);
      _this37.start = undefined;
      _this37.end = undefined;
      _this37._startValue = undefined;
      _this37._endValue = undefined;
      _this37._valueRange = 0;
      return _this37;
    }
    _inherits$1(LinearScaleBase, _Scale2);
    return _createClass$1(LinearScaleBase, [{
      key: "parse",
      value: function parse(raw, index) {
        if (isNullOrUndef(raw)) {
          return null;
        }
        if ((typeof raw === 'number' || raw instanceof Number) && !isFinite(+raw)) {
          return null;
        }
        return +raw;
      }
    }, {
      key: "handleTickRangeOptions",
      value: function handleTickRangeOptions() {
        var beginAtZero = this.options.beginAtZero;
        var _this$getUserBounds3 = this.getUserBounds(),
          minDefined = _this$getUserBounds3.minDefined,
          maxDefined = _this$getUserBounds3.maxDefined;
        var min = this.min,
          max = this.max;
        var setMin = function setMin(v) {
          return min = minDefined ? min : v;
        };
        var setMax = function setMax(v) {
          return max = maxDefined ? max : v;
        };
        if (beginAtZero) {
          var minSign = sign(min);
          var maxSign = sign(max);
          if (minSign < 0 && maxSign < 0) {
            setMax(0);
          } else if (minSign > 0 && maxSign > 0) {
            setMin(0);
          }
        }
        if (min === max) {
          var offset = max === 0 ? 1 : Math.abs(max * 0.05);
          setMax(max + offset);
          if (!beginAtZero) {
            setMin(min - offset);
          }
        }
        this.min = min;
        this.max = max;
      }
    }, {
      key: "getTickLimit",
      value: function getTickLimit() {
        var tickOpts = this.options.ticks;
        var maxTicksLimit = tickOpts.maxTicksLimit,
          stepSize = tickOpts.stepSize;
        var maxTicks;
        if (stepSize) {
          maxTicks = Math.ceil(this.max / stepSize) - Math.floor(this.min / stepSize) + 1;
          if (maxTicks > 1000) {
            console.warn("scales.".concat(this.id, ".ticks.stepSize: ").concat(stepSize, " would result generating up to ").concat(maxTicks, " ticks. Limiting to 1000."));
            maxTicks = 1000;
          }
        } else {
          maxTicks = this.computeTickLimit();
          maxTicksLimit = maxTicksLimit || 11;
        }
        if (maxTicksLimit) {
          maxTicks = Math.min(maxTicksLimit, maxTicks);
        }
        return maxTicks;
      }
    }, {
      key: "computeTickLimit",
      value: function computeTickLimit() {
        return Number.POSITIVE_INFINITY;
      }
    }, {
      key: "buildTicks",
      value: function buildTicks() {
        var opts = this.options;
        var tickOpts = opts.ticks;
        var maxTicks = this.getTickLimit();
        maxTicks = Math.max(2, maxTicks);
        var numericGeneratorOptions = {
          maxTicks: maxTicks,
          bounds: opts.bounds,
          min: opts.min,
          max: opts.max,
          precision: tickOpts.precision,
          step: tickOpts.stepSize,
          count: tickOpts.count,
          maxDigits: this._maxDigits(),
          horizontal: this.isHorizontal(),
          minRotation: tickOpts.minRotation || 0,
          includeBounds: tickOpts.includeBounds !== false
        };
        var dataRange = this._range || this;
        var ticks = generateTicks$1(numericGeneratorOptions, dataRange);
        if (opts.bounds === 'ticks') {
          _setMinAndMaxByKey(ticks, this, 'value');
        }
        if (opts.reverse) {
          ticks.reverse();
          this.start = this.max;
          this.end = this.min;
        } else {
          this.start = this.min;
          this.end = this.max;
        }
        return ticks;
      }
    }, {
      key: "configure",
      value: function configure() {
        var ticks = this.ticks;
        var start = this.min;
        var end = this.max;
        _get(_getPrototypeOf$1(LinearScaleBase.prototype), "configure", this).call(this);
        if (this.options.offset && ticks.length) {
          var offset = (end - start) / Math.max(ticks.length - 1, 1) / 2;
          start -= offset;
          end += offset;
        }
        this._startValue = start;
        this._endValue = end;
        this._valueRange = end - start;
      }
    }, {
      key: "getLabelForValue",
      value: function getLabelForValue(value) {
        return formatNumber(value, this.chart.options.locale, this.options.ticks.format);
      }
    }]);
  }(Scale);
  var LinearScale = /*#__PURE__*/function (_LinearScaleBase) {
    function LinearScale() {
      _classCallCheck$1(this, LinearScale);
      return _callSuper(this, LinearScale, arguments);
    }
    _inherits$1(LinearScale, _LinearScaleBase);
    return _createClass$1(LinearScale, [{
      key: "determineDataLimits",
      value: function determineDataLimits() {
        var _this$getMinMax2 = this.getMinMax(true),
          min = _this$getMinMax2.min,
          max = _this$getMinMax2.max;
        this.min = isNumberFinite(min) ? min : 0;
        this.max = isNumberFinite(max) ? max : 1;
        this.handleTickRangeOptions();
      }
    }, {
      key: "computeTickLimit",
      value: function computeTickLimit() {
        var horizontal = this.isHorizontal();
        var length = horizontal ? this.width : this.height;
        var minRotation = toRadians(this.options.ticks.minRotation);
        var ratio = (horizontal ? Math.sin(minRotation) : Math.cos(minRotation)) || 0.001;
        var tickFont = this._resolveTickFontOptions(0);
        return Math.ceil(length / Math.min(40, tickFont.lineHeight / ratio));
      }
    }, {
      key: "getPixelForValue",
      value: function getPixelForValue(value) {
        return value === null ? NaN : this.getPixelForDecimal((value - this._startValue) / this._valueRange);
      }
    }, {
      key: "getValueForPixel",
      value: function getValueForPixel(pixel) {
        return this._startValue + this.getDecimalForPixel(pixel) * this._valueRange;
      }
    }]);
  }(LinearScaleBase);
  _defineProperty$1(LinearScale, "id", 'linear');
  _defineProperty$1(LinearScale, "defaults", {
    ticks: {
      callback: Ticks.formatters.numeric
    }
  });
  var log10Floor = function log10Floor(v) {
    return Math.floor(log10(v));
  };
  var changeExponent = function changeExponent(v, m) {
    return Math.pow(10, log10Floor(v) + m);
  };
  function isMajor(tickVal) {
    var remain = tickVal / Math.pow(10, log10Floor(tickVal));
    return remain === 1;
  }
  function steps(min, max, rangeExp) {
    var rangeStep = Math.pow(10, rangeExp);
    var start = Math.floor(min / rangeStep);
    var end = Math.ceil(max / rangeStep);
    return end - start;
  }
  function startExp(min, max) {
    var range = max - min;
    var rangeExp = log10Floor(range);
    while (steps(min, max, rangeExp) > 10) {
      rangeExp++;
    }
    while (steps(min, max, rangeExp) < 10) {
      rangeExp--;
    }
    return Math.min(rangeExp, log10Floor(min));
  }
  function generateTicks(generationOptions, _ref13) {
    var min = _ref13.min,
      max = _ref13.max;
    min = finiteOrDefault(generationOptions.min, min);
    var ticks = [];
    var minExp = log10Floor(min);
    var exp = startExp(min, max);
    var precision = exp < 0 ? Math.pow(10, Math.abs(exp)) : 1;
    var stepSize = Math.pow(10, exp);
    var base = minExp > exp ? Math.pow(10, minExp) : 0;
    var start = Math.round((min - base) * precision) / precision;
    var offset = Math.floor((min - base) / stepSize / 10) * stepSize * 10;
    var significand = Math.floor((start - offset) / Math.pow(10, exp));
    var value = finiteOrDefault(generationOptions.min, Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision);
    while (value < max) {
      ticks.push({
        value: value,
        major: isMajor(value),
        significand: significand
      });
      if (significand >= 10) {
        significand = significand < 15 ? 15 : 20;
      } else {
        significand++;
      }
      if (significand >= 20) {
        exp++;
        significand = 2;
        precision = exp >= 0 ? 1 : precision;
      }
      value = Math.round((base + offset + significand * Math.pow(10, exp)) * precision) / precision;
    }
    var lastTick = finiteOrDefault(generationOptions.max, value);
    ticks.push({
      value: lastTick,
      major: isMajor(lastTick),
      significand: significand
    });
    return ticks;
  }
  var LogarithmicScale = /*#__PURE__*/function (_Scale3) {
    function LogarithmicScale(cfg) {
      var _this38;
      _classCallCheck$1(this, LogarithmicScale);
      _this38 = _callSuper(this, LogarithmicScale, [cfg]);
      _this38.start = undefined;
      _this38.end = undefined;
      _this38._startValue = undefined;
      _this38._valueRange = 0;
      return _this38;
    }
    _inherits$1(LogarithmicScale, _Scale3);
    return _createClass$1(LogarithmicScale, [{
      key: "parse",
      value: function parse(raw, index) {
        var value = LinearScaleBase.prototype.parse.apply(this, [raw, index]);
        if (value === 0) {
          this._zero = true;
          return undefined;
        }
        return isNumberFinite(value) && value > 0 ? value : null;
      }
    }, {
      key: "determineDataLimits",
      value: function determineDataLimits() {
        var _this$getMinMax3 = this.getMinMax(true),
          min = _this$getMinMax3.min,
          max = _this$getMinMax3.max;
        this.min = isNumberFinite(min) ? Math.max(0, min) : null;
        this.max = isNumberFinite(max) ? Math.max(0, max) : null;
        if (this.options.beginAtZero) {
          this._zero = true;
        }
        if (this._zero && this.min !== this._suggestedMin && !isNumberFinite(this._userMin)) {
          this.min = min === changeExponent(this.min, 0) ? changeExponent(this.min, -1) : changeExponent(this.min, 0);
        }
        this.handleTickRangeOptions();
      }
    }, {
      key: "handleTickRangeOptions",
      value: function handleTickRangeOptions() {
        var _this$getUserBounds4 = this.getUserBounds(),
          minDefined = _this$getUserBounds4.minDefined,
          maxDefined = _this$getUserBounds4.maxDefined;
        var min = this.min;
        var max = this.max;
        var setMin = function setMin(v) {
          return min = minDefined ? min : v;
        };
        var setMax = function setMax(v) {
          return max = maxDefined ? max : v;
        };
        if (min === max) {
          if (min <= 0) {
            setMin(1);
            setMax(10);
          } else {
            setMin(changeExponent(min, -1));
            setMax(changeExponent(max, +1));
          }
        }
        if (min <= 0) {
          setMin(changeExponent(max, -1));
        }
        if (max <= 0) {
          setMax(changeExponent(min, +1));
        }
        this.min = min;
        this.max = max;
      }
    }, {
      key: "buildTicks",
      value: function buildTicks() {
        var opts = this.options;
        var generationOptions = {
          min: this._userMin,
          max: this._userMax
        };
        var ticks = generateTicks(generationOptions, this);
        if (opts.bounds === 'ticks') {
          _setMinAndMaxByKey(ticks, this, 'value');
        }
        if (opts.reverse) {
          ticks.reverse();
          this.start = this.max;
          this.end = this.min;
        } else {
          this.start = this.min;
          this.end = this.max;
        }
        return ticks;
      }
    }, {
      key: "getLabelForValue",
      value: function getLabelForValue(value) {
        return value === undefined ? '0' : formatNumber(value, this.chart.options.locale, this.options.ticks.format);
      }
    }, {
      key: "configure",
      value: function configure() {
        var start = this.min;
        _get(_getPrototypeOf$1(LogarithmicScale.prototype), "configure", this).call(this);
        this._startValue = log10(start);
        this._valueRange = log10(this.max) - log10(start);
      }
    }, {
      key: "getPixelForValue",
      value: function getPixelForValue(value) {
        if (value === undefined || value === 0) {
          value = this.min;
        }
        if (value === null || isNaN(value)) {
          return NaN;
        }
        return this.getPixelForDecimal(value === this.min ? 0 : (log10(value) - this._startValue) / this._valueRange);
      }
    }, {
      key: "getValueForPixel",
      value: function getValueForPixel(pixel) {
        var decimal = this.getDecimalForPixel(pixel);
        return Math.pow(10, this._startValue + decimal * this._valueRange);
      }
    }]);
  }(Scale);
  _defineProperty$1(LogarithmicScale, "id", 'logarithmic');
  _defineProperty$1(LogarithmicScale, "defaults", {
    ticks: {
      callback: Ticks.formatters.logarithmic,
      major: {
        enabled: true
      }
    }
  });
  function getTickBackdropHeight(opts) {
    var tickOpts = opts.ticks;
    if (tickOpts.display && opts.display) {
      var padding = toPadding(tickOpts.backdropPadding);
      return valueOrDefault(tickOpts.font && tickOpts.font.size, defaults.font.size) + padding.height;
    }
    return 0;
  }
  function measureLabelSize(ctx, font, label) {
    label = isArray(label) ? label : [label];
    return {
      w: _longestText(ctx, font.string, label),
      h: label.length * font.lineHeight
    };
  }
  function determineLimits(angle, pos, size, min, max) {
    if (angle === min || angle === max) {
      return {
        start: pos - size / 2,
        end: pos + size / 2
      };
    } else if (angle < min || angle > max) {
      return {
        start: pos - size,
        end: pos
      };
    }
    return {
      start: pos,
      end: pos + size
    };
  }
  function fitWithPointLabels(scale) {
    var orig = {
      l: scale.left + scale._padding.left,
      r: scale.right - scale._padding.right,
      t: scale.top + scale._padding.top,
      b: scale.bottom - scale._padding.bottom
    };
    var limits = Object.assign({}, orig);
    var labelSizes = [];
    var padding = [];
    var valueCount = scale._pointLabels.length;
    var pointLabelOpts = scale.options.pointLabels;
    var additionalAngle = pointLabelOpts.centerPointLabels ? PI / valueCount : 0;
    for (var i = 0; i < valueCount; i++) {
      var opts = pointLabelOpts.setContext(scale.getPointLabelContext(i));
      padding[i] = opts.padding;
      var pointPosition = scale.getPointPosition(i, scale.drawingArea + padding[i], additionalAngle);
      var plFont = toFont(opts.font);
      var textSize = measureLabelSize(scale.ctx, plFont, scale._pointLabels[i]);
      labelSizes[i] = textSize;
      var angleRadians = _normalizeAngle(scale.getIndexAngle(i) + additionalAngle);
      var angle = Math.round(toDegrees(angleRadians));
      var hLimits = determineLimits(angle, pointPosition.x, textSize.w, 0, 180);
      var vLimits = determineLimits(angle, pointPosition.y, textSize.h, 90, 270);
      updateLimits(limits, orig, angleRadians, hLimits, vLimits);
    }
    scale.setCenterPoint(orig.l - limits.l, limits.r - orig.r, orig.t - limits.t, limits.b - orig.b);
    scale._pointLabelItems = buildPointLabelItems(scale, labelSizes, padding);
  }
  function updateLimits(limits, orig, angle, hLimits, vLimits) {
    var sin = Math.abs(Math.sin(angle));
    var cos = Math.abs(Math.cos(angle));
    var x = 0;
    var y = 0;
    if (hLimits.start < orig.l) {
      x = (orig.l - hLimits.start) / sin;
      limits.l = Math.min(limits.l, orig.l - x);
    } else if (hLimits.end > orig.r) {
      x = (hLimits.end - orig.r) / sin;
      limits.r = Math.max(limits.r, orig.r + x);
    }
    if (vLimits.start < orig.t) {
      y = (orig.t - vLimits.start) / cos;
      limits.t = Math.min(limits.t, orig.t - y);
    } else if (vLimits.end > orig.b) {
      y = (vLimits.end - orig.b) / cos;
      limits.b = Math.max(limits.b, orig.b + y);
    }
  }
  function createPointLabelItem(scale, index, itemOpts) {
    var outerDistance = scale.drawingArea;
    var extra = itemOpts.extra,
      additionalAngle = itemOpts.additionalAngle,
      padding = itemOpts.padding,
      size = itemOpts.size;
    var pointLabelPosition = scale.getPointPosition(index, outerDistance + extra + padding, additionalAngle);
    var angle = Math.round(toDegrees(_normalizeAngle(pointLabelPosition.angle + HALF_PI)));
    var y = yForAngle(pointLabelPosition.y, size.h, angle);
    var textAlign = getTextAlignForAngle(angle);
    var left = leftForTextAlign(pointLabelPosition.x, size.w, textAlign);
    return {
      visible: true,
      x: pointLabelPosition.x,
      y: y,
      textAlign: textAlign,
      left: left,
      top: y,
      right: left + size.w,
      bottom: y + size.h
    };
  }
  function isNotOverlapped(item, area) {
    if (!area) {
      return true;
    }
    var left = item.left,
      top = item.top,
      right = item.right,
      bottom = item.bottom;
    var apexesInArea = _isPointInArea({
      x: left,
      y: top
    }, area) || _isPointInArea({
      x: left,
      y: bottom
    }, area) || _isPointInArea({
      x: right,
      y: top
    }, area) || _isPointInArea({
      x: right,
      y: bottom
    }, area);
    return !apexesInArea;
  }
  function buildPointLabelItems(scale, labelSizes, padding) {
    var items = [];
    var valueCount = scale._pointLabels.length;
    var opts = scale.options;
    var _opts$pointLabels = opts.pointLabels,
      centerPointLabels = _opts$pointLabels.centerPointLabels,
      display = _opts$pointLabels.display;
    var itemOpts = {
      extra: getTickBackdropHeight(opts) / 2,
      additionalAngle: centerPointLabels ? PI / valueCount : 0
    };
    var area;
    for (var i = 0; i < valueCount; i++) {
      itemOpts.padding = padding[i];
      itemOpts.size = labelSizes[i];
      var item = createPointLabelItem(scale, i, itemOpts);
      items.push(item);
      if (display === 'auto') {
        item.visible = isNotOverlapped(item, area);
        if (item.visible) {
          area = item;
        }
      }
    }
    return items;
  }
  function getTextAlignForAngle(angle) {
    if (angle === 0 || angle === 180) {
      return 'center';
    } else if (angle < 180) {
      return 'left';
    }
    return 'right';
  }
  function leftForTextAlign(x, w, align) {
    if (align === 'right') {
      x -= w;
    } else if (align === 'center') {
      x -= w / 2;
    }
    return x;
  }
  function yForAngle(y, h, angle) {
    if (angle === 90 || angle === 270) {
      y -= h / 2;
    } else if (angle > 270 || angle < 90) {
      y -= h;
    }
    return y;
  }
  function drawPointLabelBox(ctx, opts, item) {
    var left = item.left,
      top = item.top,
      right = item.right,
      bottom = item.bottom;
    var backdropColor = opts.backdropColor;
    if (!isNullOrUndef(backdropColor)) {
      var borderRadius = toTRBLCorners(opts.borderRadius);
      var padding = toPadding(opts.backdropPadding);
      ctx.fillStyle = backdropColor;
      var backdropLeft = left - padding.left;
      var backdropTop = top - padding.top;
      var backdropWidth = right - left + padding.width;
      var backdropHeight = bottom - top + padding.height;
      if (Object.values(borderRadius).some(function (v) {
        return v !== 0;
      })) {
        ctx.beginPath();
        addRoundedRectPath(ctx, {
          x: backdropLeft,
          y: backdropTop,
          w: backdropWidth,
          h: backdropHeight,
          radius: borderRadius
        });
        ctx.fill();
      } else {
        ctx.fillRect(backdropLeft, backdropTop, backdropWidth, backdropHeight);
      }
    }
  }
  function drawPointLabels(scale, labelCount) {
    var ctx = scale.ctx,
      pointLabels = scale.options.pointLabels;
    for (var i = labelCount - 1; i >= 0; i--) {
      var item = scale._pointLabelItems[i];
      if (!item.visible) {
        continue;
      }
      var optsAtIndex = pointLabels.setContext(scale.getPointLabelContext(i));
      drawPointLabelBox(ctx, optsAtIndex, item);
      var plFont = toFont(optsAtIndex.font);
      var x = item.x,
        y = item.y,
        textAlign = item.textAlign;
      renderText(ctx, scale._pointLabels[i], x, y + plFont.lineHeight / 2, plFont, {
        color: optsAtIndex.color,
        textAlign: textAlign,
        textBaseline: 'middle'
      });
    }
  }
  function pathRadiusLine(scale, radius, circular, labelCount) {
    var ctx = scale.ctx;
    if (circular) {
      ctx.arc(scale.xCenter, scale.yCenter, radius, 0, TAU);
    } else {
      var pointPosition = scale.getPointPosition(0, radius);
      ctx.moveTo(pointPosition.x, pointPosition.y);
      for (var i = 1; i < labelCount; i++) {
        pointPosition = scale.getPointPosition(i, radius);
        ctx.lineTo(pointPosition.x, pointPosition.y);
      }
    }
  }
  function drawRadiusLine(scale, gridLineOpts, radius, labelCount, borderOpts) {
    var ctx = scale.ctx;
    var circular = gridLineOpts.circular;
    var color = gridLineOpts.color,
      lineWidth = gridLineOpts.lineWidth;
    if (!circular && !labelCount || !color || !lineWidth || radius < 0) {
      return;
    }
    ctx.save();
    ctx.strokeStyle = color;
    ctx.lineWidth = lineWidth;
    ctx.setLineDash(borderOpts.dash);
    ctx.lineDashOffset = borderOpts.dashOffset;
    ctx.beginPath();
    pathRadiusLine(scale, radius, circular, labelCount);
    ctx.closePath();
    ctx.stroke();
    ctx.restore();
  }
  function createPointLabelContext(parent, index, label) {
    return createContext(parent, {
      label: label,
      index: index,
      type: 'pointLabel'
    });
  }
  var RadialLinearScale = /*#__PURE__*/function (_LinearScaleBase2) {
    function RadialLinearScale(cfg) {
      var _this39;
      _classCallCheck$1(this, RadialLinearScale);
      _this39 = _callSuper(this, RadialLinearScale, [cfg]);
      _this39.xCenter = undefined;
      _this39.yCenter = undefined;
      _this39.drawingArea = undefined;
      _this39._pointLabels = [];
      _this39._pointLabelItems = [];
      return _this39;
    }
    _inherits$1(RadialLinearScale, _LinearScaleBase2);
    return _createClass$1(RadialLinearScale, [{
      key: "setDimensions",
      value: function setDimensions() {
        var padding = this._padding = toPadding(getTickBackdropHeight(this.options) / 2);
        var w = this.width = this.maxWidth - padding.width;
        var h = this.height = this.maxHeight - padding.height;
        this.xCenter = Math.floor(this.left + w / 2 + padding.left);
        this.yCenter = Math.floor(this.top + h / 2 + padding.top);
        this.drawingArea = Math.floor(Math.min(w, h) / 2);
      }
    }, {
      key: "determineDataLimits",
      value: function determineDataLimits() {
        var _this$getMinMax4 = this.getMinMax(false),
          min = _this$getMinMax4.min,
          max = _this$getMinMax4.max;
        this.min = isNumberFinite(min) && !isNaN(min) ? min : 0;
        this.max = isNumberFinite(max) && !isNaN(max) ? max : 0;
        this.handleTickRangeOptions();
      }
    }, {
      key: "computeTickLimit",
      value: function computeTickLimit() {
        return Math.ceil(this.drawingArea / getTickBackdropHeight(this.options));
      }
    }, {
      key: "generateTickLabels",
      value: function generateTickLabels(ticks) {
        var _this40 = this;
        LinearScaleBase.prototype.generateTickLabels.call(this, ticks);
        this._pointLabels = this.getLabels().map(function (value, index) {
          var label = callback(_this40.options.pointLabels.callback, [value, index], _this40);
          return label || label === 0 ? label : '';
        }).filter(function (v, i) {
          return _this40.chart.getDataVisibility(i);
        });
      }
    }, {
      key: "fit",
      value: function fit() {
        var opts = this.options;
        if (opts.display && opts.pointLabels.display) {
          fitWithPointLabels(this);
        } else {
          this.setCenterPoint(0, 0, 0, 0);
        }
      }
    }, {
      key: "setCenterPoint",
      value: function setCenterPoint(leftMovement, rightMovement, topMovement, bottomMovement) {
        this.xCenter += Math.floor((leftMovement - rightMovement) / 2);
        this.yCenter += Math.floor((topMovement - bottomMovement) / 2);
        this.drawingArea -= Math.min(this.drawingArea / 2, Math.max(leftMovement, rightMovement, topMovement, bottomMovement));
      }
    }, {
      key: "getIndexAngle",
      value: function getIndexAngle(index) {
        var angleMultiplier = TAU / (this._pointLabels.length || 1);
        var startAngle = this.options.startAngle || 0;
        return _normalizeAngle(index * angleMultiplier + toRadians(startAngle));
      }
    }, {
      key: "getDistanceFromCenterForValue",
      value: function getDistanceFromCenterForValue(value) {
        if (isNullOrUndef(value)) {
          return NaN;
        }
        var scalingFactor = this.drawingArea / (this.max - this.min);
        if (this.options.reverse) {
          return (this.max - value) * scalingFactor;
        }
        return (value - this.min) * scalingFactor;
      }
    }, {
      key: "getValueForDistanceFromCenter",
      value: function getValueForDistanceFromCenter(distance) {
        if (isNullOrUndef(distance)) {
          return NaN;
        }
        var scaledDistance = distance / (this.drawingArea / (this.max - this.min));
        return this.options.reverse ? this.max - scaledDistance : this.min + scaledDistance;
      }
    }, {
      key: "getPointLabelContext",
      value: function getPointLabelContext(index) {
        var pointLabels = this._pointLabels || [];
        if (index >= 0 && index < pointLabels.length) {
          var pointLabel = pointLabels[index];
          return createPointLabelContext(this.getContext(), index, pointLabel);
        }
      }
    }, {
      key: "getPointPosition",
      value: function getPointPosition(index, distanceFromCenter) {
        var additionalAngle = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
        var angle = this.getIndexAngle(index) - HALF_PI + additionalAngle;
        return {
          x: Math.cos(angle) * distanceFromCenter + this.xCenter,
          y: Math.sin(angle) * distanceFromCenter + this.yCenter,
          angle: angle
        };
      }
    }, {
      key: "getPointPositionForValue",
      value: function getPointPositionForValue(index, value) {
        return this.getPointPosition(index, this.getDistanceFromCenterForValue(value));
      }
    }, {
      key: "getBasePosition",
      value: function getBasePosition(index) {
        return this.getPointPositionForValue(index || 0, this.getBaseValue());
      }
    }, {
      key: "getPointLabelPosition",
      value: function getPointLabelPosition(index) {
        var _this$_pointLabelItem = this._pointLabelItems[index],
          left = _this$_pointLabelItem.left,
          top = _this$_pointLabelItem.top,
          right = _this$_pointLabelItem.right,
          bottom = _this$_pointLabelItem.bottom;
        return {
          left: left,
          top: top,
          right: right,
          bottom: bottom
        };
      }
    }, {
      key: "drawBackground",
      value: function drawBackground() {
        var _this$options16 = this.options,
          backgroundColor = _this$options16.backgroundColor,
          circular = _this$options16.grid.circular;
        if (backgroundColor) {
          var ctx = this.ctx;
          ctx.save();
          ctx.beginPath();
          pathRadiusLine(this, this.getDistanceFromCenterForValue(this._endValue), circular, this._pointLabels.length);
          ctx.closePath();
          ctx.fillStyle = backgroundColor;
          ctx.fill();
          ctx.restore();
        }
      }
    }, {
      key: "drawGrid",
      value: function drawGrid() {
        var _this41 = this;
        var ctx = this.ctx;
        var opts = this.options;
        var angleLines = opts.angleLines,
          grid = opts.grid,
          border = opts.border;
        var labelCount = this._pointLabels.length;
        var i, offset, position;
        if (opts.pointLabels.display) {
          drawPointLabels(this, labelCount);
        }
        if (grid.display) {
          this.ticks.forEach(function (tick, index) {
            if (index !== 0 || index === 0 && _this41.min < 0) {
              offset = _this41.getDistanceFromCenterForValue(tick.value);
              var context = _this41.getContext(index);
              var optsAtIndex = grid.setContext(context);
              var optsAtIndexBorder = border.setContext(context);
              drawRadiusLine(_this41, optsAtIndex, offset, labelCount, optsAtIndexBorder);
            }
          });
        }
        if (angleLines.display) {
          ctx.save();
          for (i = labelCount - 1; i >= 0; i--) {
            var optsAtIndex = angleLines.setContext(this.getPointLabelContext(i));
            var color = optsAtIndex.color,
              lineWidth = optsAtIndex.lineWidth;
            if (!lineWidth || !color) {
              continue;
            }
            ctx.lineWidth = lineWidth;
            ctx.strokeStyle = color;
            ctx.setLineDash(optsAtIndex.borderDash);
            ctx.lineDashOffset = optsAtIndex.borderDashOffset;
            offset = this.getDistanceFromCenterForValue(opts.ticks.reverse ? this.min : this.max);
            position = this.getPointPosition(i, offset);
            ctx.beginPath();
            ctx.moveTo(this.xCenter, this.yCenter);
            ctx.lineTo(position.x, position.y);
            ctx.stroke();
          }
          ctx.restore();
        }
      }
    }, {
      key: "drawBorder",
      value: function drawBorder() {}
    }, {
      key: "drawLabels",
      value: function drawLabels() {
        var _this42 = this;
        var ctx = this.ctx;
        var opts = this.options;
        var tickOpts = opts.ticks;
        if (!tickOpts.display) {
          return;
        }
        var startAngle = this.getIndexAngle(0);
        var offset, width;
        ctx.save();
        ctx.translate(this.xCenter, this.yCenter);
        ctx.rotate(startAngle);
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        this.ticks.forEach(function (tick, index) {
          if (index === 0 && _this42.min >= 0 && !opts.reverse) {
            return;
          }
          var optsAtIndex = tickOpts.setContext(_this42.getContext(index));
          var tickFont = toFont(optsAtIndex.font);
          offset = _this42.getDistanceFromCenterForValue(_this42.ticks[index].value);
          if (optsAtIndex.showLabelBackdrop) {
            ctx.font = tickFont.string;
            width = ctx.measureText(tick.label).width;
            ctx.fillStyle = optsAtIndex.backdropColor;
            var padding = toPadding(optsAtIndex.backdropPadding);
            ctx.fillRect(-width / 2 - padding.left, -offset - tickFont.size / 2 - padding.top, width + padding.width, tickFont.size + padding.height);
          }
          renderText(ctx, tick.label, 0, -offset, tickFont, {
            color: optsAtIndex.color,
            strokeColor: optsAtIndex.textStrokeColor,
            strokeWidth: optsAtIndex.textStrokeWidth
          });
        });
        ctx.restore();
      }
    }, {
      key: "drawTitle",
      value: function drawTitle() {}
    }]);
  }(LinearScaleBase);
  _defineProperty$1(RadialLinearScale, "id", 'radialLinear');
  _defineProperty$1(RadialLinearScale, "defaults", {
    display: true,
    animate: true,
    position: 'chartArea',
    angleLines: {
      display: true,
      lineWidth: 1,
      borderDash: [],
      borderDashOffset: 0.0
    },
    grid: {
      circular: false
    },
    startAngle: 0,
    ticks: {
      showLabelBackdrop: true,
      callback: Ticks.formatters.numeric
    },
    pointLabels: {
      backdropColor: undefined,
      backdropPadding: 2,
      display: true,
      font: {
        size: 10
      },
      callback: function callback(label) {
        return label;
      },
      padding: 5,
      centerPointLabels: false
    }
  });
  _defineProperty$1(RadialLinearScale, "defaultRoutes", {
    'angleLines.color': 'borderColor',
    'pointLabels.color': 'color',
    'ticks.color': 'color'
  });
  _defineProperty$1(RadialLinearScale, "descriptors", {
    angleLines: {
      _fallback: 'grid'
    }
  });
  var INTERVALS = {
    millisecond: {
      common: true,
      size: 1,
      steps: 1000
    },
    second: {
      common: true,
      size: 1000,
      steps: 60
    },
    minute: {
      common: true,
      size: 60000,
      steps: 60
    },
    hour: {
      common: true,
      size: 3600000,
      steps: 24
    },
    day: {
      common: true,
      size: 86400000,
      steps: 30
    },
    week: {
      common: false,
      size: 604800000,
      steps: 4
    },
    month: {
      common: true,
      size: 2.628e9,
      steps: 12
    },
    quarter: {
      common: false,
      size: 7.884e9,
      steps: 4
    },
    year: {
      common: true,
      size: 3.154e10
    }
  };
  var UNITS = /* #__PURE__ */Object.keys(INTERVALS);
  function sorter(a, b) {
    return a - b;
  }
  function _parse(scale, input) {
    if (isNullOrUndef(input)) {
      return null;
    }
    var adapter = scale._adapter;
    var _scale$_parseOpts = scale._parseOpts,
      parser = _scale$_parseOpts.parser,
      round = _scale$_parseOpts.round,
      isoWeekday = _scale$_parseOpts.isoWeekday;
    var value = input;
    if (typeof parser === 'function') {
      value = parser(value);
    }
    if (!isNumberFinite(value)) {
      value = typeof parser === 'string' ? adapter.parse(value, parser) : adapter.parse(value);
    }
    if (value === null) {
      return null;
    }
    if (round) {
      value = round === 'week' && (isNumber(isoWeekday) || isoWeekday === true) ? adapter.startOf(value, 'isoWeek', isoWeekday) : adapter.startOf(value, round);
    }
    return +value;
  }
  function determineUnitForAutoTicks(minUnit, min, max, capacity) {
    var ilen = UNITS.length;
    for (var i = UNITS.indexOf(minUnit); i < ilen - 1; ++i) {
      var interval = INTERVALS[UNITS[i]];
      var factor = interval.steps ? interval.steps : Number.MAX_SAFE_INTEGER;
      if (interval.common && Math.ceil((max - min) / (factor * interval.size)) <= capacity) {
        return UNITS[i];
      }
    }
    return UNITS[ilen - 1];
  }
  function determineUnitForFormatting(scale, numTicks, minUnit, min, max) {
    for (var i = UNITS.length - 1; i >= UNITS.indexOf(minUnit); i--) {
      var unit = UNITS[i];
      if (INTERVALS[unit].common && scale._adapter.diff(max, min, unit) >= numTicks - 1) {
        return unit;
      }
    }
    return UNITS[minUnit ? UNITS.indexOf(minUnit) : 0];
  }
  function determineMajorUnit(unit) {
    for (var i = UNITS.indexOf(unit) + 1, ilen = UNITS.length; i < ilen; ++i) {
      if (INTERVALS[UNITS[i]].common) {
        return UNITS[i];
      }
    }
  }
  function addTick(ticks, time, timestamps) {
    if (!timestamps) {
      ticks[time] = true;
    } else if (timestamps.length) {
      var _lookup2 = _lookup(timestamps, time),
        lo = _lookup2.lo,
        hi = _lookup2.hi;
      var timestamp = timestamps[lo] >= time ? timestamps[lo] : timestamps[hi];
      ticks[timestamp] = true;
    }
  }
  function setMajorTicks(scale, ticks, map, majorUnit) {
    var adapter = scale._adapter;
    var first = +adapter.startOf(ticks[0].value, majorUnit);
    var last = ticks[ticks.length - 1].value;
    var major, index;
    for (major = first; major <= last; major = +adapter.add(major, 1, majorUnit)) {
      index = map[major];
      if (index >= 0) {
        ticks[index].major = true;
      }
    }
    return ticks;
  }
  function ticksFromTimestamps(scale, values, majorUnit) {
    var ticks = [];
    var map = {};
    var ilen = values.length;
    var i, value;
    for (i = 0; i < ilen; ++i) {
      value = values[i];
      map[value] = i;
      ticks.push({
        value: value,
        major: false
      });
    }
    return ilen === 0 || !majorUnit ? ticks : setMajorTicks(scale, ticks, map, majorUnit);
  }
  var TimeScale = /*#__PURE__*/function (_Scale4) {
    function TimeScale(props) {
      var _this43;
      _classCallCheck$1(this, TimeScale);
      _this43 = _callSuper(this, TimeScale, [props]);
      _this43._cache = {
        data: [],
        labels: [],
        all: []
      };
      _this43._unit = 'day';
      _this43._majorUnit = undefined;
      _this43._offsets = {};
      _this43._normalized = false;
      _this43._parseOpts = undefined;
      return _this43;
    }
    _inherits$1(TimeScale, _Scale4);
    return _createClass$1(TimeScale, [{
      key: "init",
      value: function init(scaleOpts) {
        var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        var time = scaleOpts.time || (scaleOpts.time = {});
        var adapter = this._adapter = new adapters._date(scaleOpts.adapters.date);
        adapter.init(opts);
        mergeIf(time.displayFormats, adapter.formats());
        this._parseOpts = {
          parser: time.parser,
          round: time.round,
          isoWeekday: time.isoWeekday
        };
        _get(_getPrototypeOf$1(TimeScale.prototype), "init", this).call(this, scaleOpts);
        this._normalized = opts.normalized;
      }
    }, {
      key: "parse",
      value: function parse(raw, index) {
        if (raw === undefined) {
          return null;
        }
        return _parse(this, raw);
      }
    }, {
      key: "beforeLayout",
      value: function beforeLayout() {
        _get(_getPrototypeOf$1(TimeScale.prototype), "beforeLayout", this).call(this);
        this._cache = {
          data: [],
          labels: [],
          all: []
        };
      }
    }, {
      key: "determineDataLimits",
      value: function determineDataLimits() {
        var options = this.options;
        var adapter = this._adapter;
        var unit = options.time.unit || 'day';
        var _this$getUserBounds5 = this.getUserBounds(),
          min = _this$getUserBounds5.min,
          max = _this$getUserBounds5.max,
          minDefined = _this$getUserBounds5.minDefined,
          maxDefined = _this$getUserBounds5.maxDefined;
        function _applyBounds(bounds) {
          if (!minDefined && !isNaN(bounds.min)) {
            min = Math.min(min, bounds.min);
          }
          if (!maxDefined && !isNaN(bounds.max)) {
            max = Math.max(max, bounds.max);
          }
        }
        if (!minDefined || !maxDefined) {
          _applyBounds(this._getLabelBounds());
          if (options.bounds !== 'ticks' || options.ticks.source !== 'labels') {
            _applyBounds(this.getMinMax(false));
          }
        }
        min = isNumberFinite(min) && !isNaN(min) ? min : +adapter.startOf(Date.now(), unit);
        max = isNumberFinite(max) && !isNaN(max) ? max : +adapter.endOf(Date.now(), unit) + 1;
        this.min = Math.min(min, max - 1);
        this.max = Math.max(min + 1, max);
      }
    }, {
      key: "_getLabelBounds",
      value: function _getLabelBounds() {
        var arr = this.getLabelTimestamps();
        var min = Number.POSITIVE_INFINITY;
        var max = Number.NEGATIVE_INFINITY;
        if (arr.length) {
          min = arr[0];
          max = arr[arr.length - 1];
        }
        return {
          min: min,
          max: max
        };
      }
    }, {
      key: "buildTicks",
      value: function buildTicks() {
        var options = this.options;
        var timeOpts = options.time;
        var tickOpts = options.ticks;
        var timestamps = tickOpts.source === 'labels' ? this.getLabelTimestamps() : this._generate();
        if (options.bounds === 'ticks' && timestamps.length) {
          this.min = this._userMin || timestamps[0];
          this.max = this._userMax || timestamps[timestamps.length - 1];
        }
        var min = this.min;
        var max = this.max;
        var ticks = _filterBetween(timestamps, min, max);
        this._unit = timeOpts.unit || (tickOpts.autoSkip ? determineUnitForAutoTicks(timeOpts.minUnit, this.min, this.max, this._getLabelCapacity(min)) : determineUnitForFormatting(this, ticks.length, timeOpts.minUnit, this.min, this.max));
        this._majorUnit = !tickOpts.major.enabled || this._unit === 'year' ? undefined : determineMajorUnit(this._unit);
        this.initOffsets(timestamps);
        if (options.reverse) {
          ticks.reverse();
        }
        return ticksFromTimestamps(this, ticks, this._majorUnit);
      }
    }, {
      key: "afterAutoSkip",
      value: function afterAutoSkip() {
        if (this.options.offsetAfterAutoskip) {
          this.initOffsets(this.ticks.map(function (tick) {
            return +tick.value;
          }));
        }
      }
    }, {
      key: "initOffsets",
      value: function initOffsets() {
        var timestamps = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
        var start = 0;
        var end = 0;
        var first, last;
        if (this.options.offset && timestamps.length) {
          first = this.getDecimalForValue(timestamps[0]);
          if (timestamps.length === 1) {
            start = 1 - first;
          } else {
            start = (this.getDecimalForValue(timestamps[1]) - first) / 2;
          }
          last = this.getDecimalForValue(timestamps[timestamps.length - 1]);
          if (timestamps.length === 1) {
            end = last;
          } else {
            end = (last - this.getDecimalForValue(timestamps[timestamps.length - 2])) / 2;
          }
        }
        var limit = timestamps.length < 3 ? 0.5 : 0.25;
        start = _limitValue(start, 0, limit);
        end = _limitValue(end, 0, limit);
        this._offsets = {
          start: start,
          end: end,
          factor: 1 / (start + 1 + end)
        };
      }
    }, {
      key: "_generate",
      value: function _generate() {
        var adapter = this._adapter;
        var min = this.min;
        var max = this.max;
        var options = this.options;
        var timeOpts = options.time;
        var minor = timeOpts.unit || determineUnitForAutoTicks(timeOpts.minUnit, min, max, this._getLabelCapacity(min));
        var stepSize = valueOrDefault(options.ticks.stepSize, 1);
        var weekday = minor === 'week' ? timeOpts.isoWeekday : false;
        var hasWeekday = isNumber(weekday) || weekday === true;
        var ticks = {};
        var first = min;
        var time, count;
        if (hasWeekday) {
          first = +adapter.startOf(first, 'isoWeek', weekday);
        }
        first = +adapter.startOf(first, hasWeekday ? 'day' : minor);
        if (adapter.diff(max, min, minor) > 100000 * stepSize) {
          throw new Error(min + ' and ' + max + ' are too far apart with stepSize of ' + stepSize + ' ' + minor);
        }
        var timestamps = options.ticks.source === 'data' && this.getDataTimestamps();
        for (time = first, count = 0; time < max; time = +adapter.add(time, stepSize, minor), count++) {
          addTick(ticks, time, timestamps);
        }
        if (time === max || options.bounds === 'ticks' || count === 1) {
          addTick(ticks, time, timestamps);
        }
        return Object.keys(ticks).sort(sorter).map(function (x) {
          return +x;
        });
      }
    }, {
      key: "getLabelForValue",
      value: function getLabelForValue(value) {
        var adapter = this._adapter;
        var timeOpts = this.options.time;
        if (timeOpts.tooltipFormat) {
          return adapter.format(value, timeOpts.tooltipFormat);
        }
        return adapter.format(value, timeOpts.displayFormats.datetime);
      }
    }, {
      key: "format",
      value: function format(value, _format) {
        var options = this.options;
        var formats = options.time.displayFormats;
        var unit = this._unit;
        var fmt = _format || formats[unit];
        return this._adapter.format(value, fmt);
      }
    }, {
      key: "_tickFormatFunction",
      value: function _tickFormatFunction(time, index, ticks, format) {
        var options = this.options;
        var formatter = options.ticks.callback;
        if (formatter) {
          return callback(formatter, [time, index, ticks], this);
        }
        var formats = options.time.displayFormats;
        var unit = this._unit;
        var majorUnit = this._majorUnit;
        var minorFormat = unit && formats[unit];
        var majorFormat = majorUnit && formats[majorUnit];
        var tick = ticks[index];
        var major = majorUnit && majorFormat && tick && tick.major;
        return this._adapter.format(time, format || (major ? majorFormat : minorFormat));
      }
    }, {
      key: "generateTickLabels",
      value: function generateTickLabels(ticks) {
        var i, ilen, tick;
        for (i = 0, ilen = ticks.length; i < ilen; ++i) {
          tick = ticks[i];
          tick.label = this._tickFormatFunction(tick.value, i, ticks);
        }
      }
    }, {
      key: "getDecimalForValue",
      value: function getDecimalForValue(value) {
        return value === null ? NaN : (value - this.min) / (this.max - this.min);
      }
    }, {
      key: "getPixelForValue",
      value: function getPixelForValue(value) {
        var offsets = this._offsets;
        var pos = this.getDecimalForValue(value);
        return this.getPixelForDecimal((offsets.start + pos) * offsets.factor);
      }
    }, {
      key: "getValueForPixel",
      value: function getValueForPixel(pixel) {
        var offsets = this._offsets;
        var pos = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
        return this.min + pos * (this.max - this.min);
      }
    }, {
      key: "_getLabelSize",
      value: function _getLabelSize(label) {
        var ticksOpts = this.options.ticks;
        var tickLabelWidth = this.ctx.measureText(label).width;
        var angle = toRadians(this.isHorizontal() ? ticksOpts.maxRotation : ticksOpts.minRotation);
        var cosRotation = Math.cos(angle);
        var sinRotation = Math.sin(angle);
        var tickFontSize = this._resolveTickFontOptions(0).size;
        return {
          w: tickLabelWidth * cosRotation + tickFontSize * sinRotation,
          h: tickLabelWidth * sinRotation + tickFontSize * cosRotation
        };
      }
    }, {
      key: "_getLabelCapacity",
      value: function _getLabelCapacity(exampleTime) {
        var timeOpts = this.options.time;
        var displayFormats = timeOpts.displayFormats;
        var format = displayFormats[timeOpts.unit] || displayFormats.millisecond;
        var exampleLabel = this._tickFormatFunction(exampleTime, 0, ticksFromTimestamps(this, [exampleTime], this._majorUnit), format);
        var size = this._getLabelSize(exampleLabel);
        var capacity = Math.floor(this.isHorizontal() ? this.width / size.w : this.height / size.h) - 1;
        return capacity > 0 ? capacity : 1;
      }
    }, {
      key: "getDataTimestamps",
      value: function getDataTimestamps() {
        var timestamps = this._cache.data || [];
        var i, ilen;
        if (timestamps.length) {
          return timestamps;
        }
        var metas = this.getMatchingVisibleMetas();
        if (this._normalized && metas.length) {
          return this._cache.data = metas[0].controller.getAllParsedValues(this);
        }
        for (i = 0, ilen = metas.length; i < ilen; ++i) {
          timestamps = timestamps.concat(metas[i].controller.getAllParsedValues(this));
        }
        return this._cache.data = this.normalize(timestamps);
      }
    }, {
      key: "getLabelTimestamps",
      value: function getLabelTimestamps() {
        var timestamps = this._cache.labels || [];
        var i, ilen;
        if (timestamps.length) {
          return timestamps;
        }
        var labels = this.getLabels();
        for (i = 0, ilen = labels.length; i < ilen; ++i) {
          timestamps.push(_parse(this, labels[i]));
        }
        return this._cache.labels = this._normalized ? timestamps : this.normalize(timestamps);
      }
    }, {
      key: "normalize",
      value: function normalize(values) {
        return _arrayUnique(values.sort(sorter));
      }
    }]);
  }(Scale);
  _defineProperty$1(TimeScale, "id", 'time');
  _defineProperty$1(TimeScale, "defaults", {
    bounds: 'data',
    adapters: {},
    time: {
      parser: false,
      unit: false,
      round: false,
      isoWeekday: false,
      minUnit: 'millisecond',
      displayFormats: {}
    },
    ticks: {
      source: 'auto',
      callback: false,
      major: {
        enabled: false
      }
    }
  });
  function interpolate(table, val, reverse) {
    var lo = 0;
    var hi = table.length - 1;
    var prevSource, nextSource, prevTarget, nextTarget;
    if (reverse) {
      if (val >= table[lo].pos && val <= table[hi].pos) {
        var _lookupByKey2 = _lookupByKey(table, 'pos', val);
        lo = _lookupByKey2.lo;
        hi = _lookupByKey2.hi;
      }
      var _table$lo = table[lo];
      prevSource = _table$lo.pos;
      prevTarget = _table$lo.time;
      var _table$hi = table[hi];
      nextSource = _table$hi.pos;
      nextTarget = _table$hi.time;
    } else {
      if (val >= table[lo].time && val <= table[hi].time) {
        var _lookupByKey3 = _lookupByKey(table, 'time', val);
        lo = _lookupByKey3.lo;
        hi = _lookupByKey3.hi;
      }
      var _table$lo2 = table[lo];
      prevSource = _table$lo2.time;
      prevTarget = _table$lo2.pos;
      var _table$hi2 = table[hi];
      nextSource = _table$hi2.time;
      nextTarget = _table$hi2.pos;
    }
    var span = nextSource - prevSource;
    return span ? prevTarget + (nextTarget - prevTarget) * (val - prevSource) / span : prevTarget;
  }
  var TimeSeriesScale = /*#__PURE__*/function (_TimeScale2) {
    function TimeSeriesScale(props) {
      var _this44;
      _classCallCheck$1(this, TimeSeriesScale);
      _this44 = _callSuper(this, TimeSeriesScale, [props]);
      _this44._table = [];
      _this44._minPos = undefined;
      _this44._tableRange = undefined;
      return _this44;
    }
    _inherits$1(TimeSeriesScale, _TimeScale2);
    return _createClass$1(TimeSeriesScale, [{
      key: "initOffsets",
      value: function initOffsets() {
        var timestamps = this._getTimestampsForTable();
        var table = this._table = this.buildLookupTable(timestamps);
        this._minPos = interpolate(table, this.min);
        this._tableRange = interpolate(table, this.max) - this._minPos;
        _get(_getPrototypeOf$1(TimeSeriesScale.prototype), "initOffsets", this).call(this, timestamps);
      }
    }, {
      key: "buildLookupTable",
      value: function buildLookupTable(timestamps) {
        var min = this.min,
          max = this.max;
        var items = [];
        var table = [];
        var i, ilen, prev, curr, next;
        for (i = 0, ilen = timestamps.length; i < ilen; ++i) {
          curr = timestamps[i];
          if (curr >= min && curr <= max) {
            items.push(curr);
          }
        }
        if (items.length < 2) {
          return [{
            time: min,
            pos: 0
          }, {
            time: max,
            pos: 1
          }];
        }
        for (i = 0, ilen = items.length; i < ilen; ++i) {
          next = items[i + 1];
          prev = items[i - 1];
          curr = items[i];
          if (Math.round((next + prev) / 2) !== curr) {
            table.push({
              time: curr,
              pos: i / (ilen - 1)
            });
          }
        }
        return table;
      }
    }, {
      key: "_generate",
      value: function _generate() {
        var min = this.min;
        var max = this.max;
        var timestamps = _get(_getPrototypeOf$1(TimeSeriesScale.prototype), "getDataTimestamps", this).call(this);
        if (!timestamps.includes(min) || !timestamps.length) {
          timestamps.splice(0, 0, min);
        }
        if (!timestamps.includes(max) || timestamps.length === 1) {
          timestamps.push(max);
        }
        return timestamps.sort(function (a, b) {
          return a - b;
        });
      }
    }, {
      key: "_getTimestampsForTable",
      value: function _getTimestampsForTable() {
        var timestamps = this._cache.all || [];
        if (timestamps.length) {
          return timestamps;
        }
        var data = this.getDataTimestamps();
        var label = this.getLabelTimestamps();
        if (data.length && label.length) {
          timestamps = this.normalize(data.concat(label));
        } else {
          timestamps = data.length ? data : label;
        }
        timestamps = this._cache.all = timestamps;
        return timestamps;
      }
    }, {
      key: "getDecimalForValue",
      value: function getDecimalForValue(value) {
        return (interpolate(this._table, value) - this._minPos) / this._tableRange;
      }
    }, {
      key: "getValueForPixel",
      value: function getValueForPixel(pixel) {
        var offsets = this._offsets;
        var decimal = this.getDecimalForPixel(pixel) / offsets.factor - offsets.end;
        return interpolate(this._table, decimal * this._tableRange + this._minPos, true);
      }
    }]);
  }(TimeScale);
  _defineProperty$1(TimeSeriesScale, "id", 'timeseries');
  _defineProperty$1(TimeSeriesScale, "defaults", TimeScale.defaults);
  var scales = /*#__PURE__*/Object.freeze({
    __proto__: null,
    CategoryScale: CategoryScale,
    LinearScale: LinearScale,
    LogarithmicScale: LogarithmicScale,
    RadialLinearScale: RadialLinearScale,
    TimeScale: TimeScale,
    TimeSeriesScale: TimeSeriesScale
  });
  var registerables = [controllers, elements, plugins, scales];

  Chart.register.apply(Chart, _toConsumableArray(registerables));

  var helpers = /*#__PURE__*/Object.freeze({
    __proto__: null,
    HALF_PI: HALF_PI,
    INFINITY: INFINITY,
    PI: PI,
    PITAU: PITAU,
    QUARTER_PI: QUARTER_PI,
    RAD_PER_DEG: RAD_PER_DEG,
    TAU: TAU,
    TWO_THIRDS_PI: TWO_THIRDS_PI,
    _addGrace: _addGrace,
    _alignPixel: _alignPixel,
    _alignStartEnd: _alignStartEnd,
    _angleBetween: _angleBetween,
    _angleDiff: _angleDiff,
    _arrayUnique: _arrayUnique,
    _attachContext: _attachContext,
    _bezierCurveTo: _bezierCurveTo,
    _bezierInterpolation: _bezierInterpolation,
    _boundSegment: _boundSegment,
    _boundSegments: _boundSegments,
    _capitalize: _capitalize,
    _computeSegments: _computeSegments,
    _createResolver: _createResolver,
    _decimalPlaces: _decimalPlaces,
    _deprecated: _deprecated,
    _descriptors: _descriptors,
    _elementsEqual: _elementsEqual,
    _factorize: _factorize,
    _filterBetween: _filterBetween,
    _getParentNode: _getParentNode,
    _getStartAndCountOfVisiblePoints: _getStartAndCountOfVisiblePoints,
    _int16Range: _int16Range,
    _isBetween: _isBetween,
    _isClickEvent: _isClickEvent,
    _isDomSupported: _isDomSupported,
    _isPointInArea: _isPointInArea,
    _limitValue: _limitValue,
    _longestText: _longestText,
    _lookup: _lookup,
    _lookupByKey: _lookupByKey,
    _measureText: _measureText,
    _merger: _merger,
    _mergerIf: _mergerIf,
    _normalizeAngle: _normalizeAngle,
    _parseObjectDataRadialScale: _parseObjectDataRadialScale,
    _pointInLine: _pointInLine,
    _readValueToProps: _readValueToProps,
    _rlookupByKey: _rlookupByKey,
    _scaleRangesChanged: _scaleRangesChanged,
    _setMinAndMaxByKey: _setMinAndMaxByKey,
    _splitKey: _splitKey,
    _steppedInterpolation: _steppedInterpolation,
    _steppedLineTo: _steppedLineTo,
    _textX: _textX,
    _toLeftRightCenter: _toLeftRightCenter,
    _updateBezierControlPoints: _updateBezierControlPoints,
    addRoundedRectPath: addRoundedRectPath,
    almostEquals: almostEquals,
    almostWhole: almostWhole,
    callback: callback,
    clearCanvas: clearCanvas,
    clipArea: clipArea,
    clone: clone,
    color: color,
    createContext: createContext,
    debounce: debounce,
    defined: defined,
    distanceBetweenPoints: distanceBetweenPoints,
    drawPoint: drawPoint,
    drawPointLegend: drawPointLegend,
    each: each,
    easingEffects: effects,
    finiteOrDefault: finiteOrDefault,
    fontString: fontString,
    formatNumber: formatNumber,
    getAngleFromPoint: getAngleFromPoint,
    getHoverColor: getHoverColor,
    getMaximumSize: getMaximumSize,
    getRelativePosition: getRelativePosition,
    getRtlAdapter: getRtlAdapter,
    getStyle: getStyle,
    isArray: isArray,
    isFinite: isNumberFinite,
    isFunction: isFunction,
    isNullOrUndef: isNullOrUndef,
    isNumber: isNumber,
    isObject: isObject,
    isPatternOrGradient: isPatternOrGradient,
    listenArrayEvents: listenArrayEvents,
    log10: log10,
    merge: merge,
    mergeIf: mergeIf,
    niceNum: niceNum,
    noop: noop,
    overrideTextDirection: overrideTextDirection,
    readUsedSize: readUsedSize,
    renderText: renderText,
    requestAnimFrame: requestAnimFrame,
    resolve: resolve,
    resolveObjectKey: resolveObjectKey,
    restoreTextDirection: restoreTextDirection,
    retinaScale: retinaScale,
    setsEqual: setsEqual,
    sign: sign,
    splineCurve: splineCurve,
    splineCurveMonotone: splineCurveMonotone,
    supportsEventListenerOptions: supportsEventListenerOptions,
    throttled: throttled,
    toDegrees: toDegrees,
    toDimension: toDimension,
    toFont: toFont,
    toFontString: toFontString,
    toLineHeight: toLineHeight,
    toPadding: toPadding,
    toPercentage: toPercentage,
    toRadians: toRadians,
    toTRBL: toTRBL,
    toTRBLCorners: toTRBLCorners,
    uid: uid,
    unclipArea: unclipArea,
    unlistenArrayEvents: unlistenArrayEvents,
    valueOrDefault: valueOrDefault
  });

  function _typeof(o) {
    "@babel/helpers - typeof";

    return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) {
      return typeof o;
    } : function (o) {
      return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
    }, _typeof(o);
  }

  function toInteger(dirtyNumber) {
    if (dirtyNumber === null || dirtyNumber === true || dirtyNumber === false) {
      return NaN;
    }
    var number = Number(dirtyNumber);
    if (isNaN(number)) {
      return number;
    }
    return number < 0 ? Math.ceil(number) : Math.floor(number);
  }

  function requiredArgs(required, args) {
    if (args.length < required) {
      throw new TypeError(required + ' argument' + (required > 1 ? 's' : '') + ' required, but only ' + args.length + ' present');
    }
  }

  /**
   * @name toDate
   * @category Common Helpers
   * @summary Convert the given argument to an instance of Date.
   *
   * @description
   * Convert the given argument to an instance of Date.
   *
   * If the argument is an instance of Date, the function returns its clone.
   *
   * If the argument is a number, it is treated as a timestamp.
   *
   * If the argument is none of the above, the function returns Invalid Date.
   *
   * **Note**: *all* Date arguments passed to any *date-fns* function is processed by `toDate`.
   *
   * @param {Date|Number} argument - the value to convert
   * @returns {Date} the parsed date in the local time zone
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // Clone the date:
   * const result = toDate(new Date(2014, 1, 11, 11, 30, 30))
   * //=> Tue Feb 11 2014 11:30:30
   *
   * @example
   * // Convert the timestamp to date:
   * const result = toDate(1392098430000)
   * //=> Tue Feb 11 2014 11:30:30
   */
  function toDate(argument) {
    requiredArgs(1, arguments);
    var argStr = Object.prototype.toString.call(argument);

    // Clone the date
    if (argument instanceof Date || _typeof(argument) === 'object' && argStr === '[object Date]') {
      // Prevent the date to lose the milliseconds when passed to new Date() in IE10
      return new Date(argument.getTime());
    } else if (typeof argument === 'number' || argStr === '[object Number]') {
      return new Date(argument);
    } else {
      if ((typeof argument === 'string' || argStr === '[object String]') && typeof console !== 'undefined') {
        // eslint-disable-next-line no-console
        console.warn("Starting with v2.0.0-beta.1 date-fns doesn't accept strings as date arguments. Please use `parseISO` to parse strings. See: https://github.com/date-fns/date-fns/blob/master/docs/upgradeGuide.md#string-arguments");
        // eslint-disable-next-line no-console
        console.warn(new Error().stack);
      }
      return new Date(NaN);
    }
  }

  /**
   * @name addDays
   * @category Day Helpers
   * @summary Add the specified number of days to the given date.
   *
   * @description
   * Add the specified number of days to the given date.
   *
   * @param {Date|Number} date - the date to be changed
   * @param {Number} amount - the amount of days to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.
   * @returns {Date} - the new date with the days added
   * @throws {TypeError} - 2 arguments required
   *
   * @example
   * // Add 10 days to 1 September 2014:
   * const result = addDays(new Date(2014, 8, 1), 10)
   * //=> Thu Sep 11 2014 00:00:00
   */
  function addDays(dirtyDate, dirtyAmount) {
    requiredArgs(2, arguments);
    var date = toDate(dirtyDate);
    var amount = toInteger(dirtyAmount);
    if (isNaN(amount)) {
      return new Date(NaN);
    }
    if (!amount) {
      // If 0 days, no-op to avoid changing times in the hour before end of DST
      return date;
    }
    date.setDate(date.getDate() + amount);
    return date;
  }

  /**
   * @name addMonths
   * @category Month Helpers
   * @summary Add the specified number of months to the given date.
   *
   * @description
   * Add the specified number of months to the given date.
   *
   * @param {Date|Number} date - the date to be changed
   * @param {Number} amount - the amount of months to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.
   * @returns {Date} the new date with the months added
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // Add 5 months to 1 September 2014:
   * const result = addMonths(new Date(2014, 8, 1), 5)
   * //=> Sun Feb 01 2015 00:00:00
   */
  function addMonths(dirtyDate, dirtyAmount) {
    requiredArgs(2, arguments);
    var date = toDate(dirtyDate);
    var amount = toInteger(dirtyAmount);
    if (isNaN(amount)) {
      return new Date(NaN);
    }
    if (!amount) {
      // If 0 months, no-op to avoid changing times in the hour before end of DST
      return date;
    }
    var dayOfMonth = date.getDate();

    // The JS Date object supports date math by accepting out-of-bounds values for
    // month, day, etc. For example, new Date(2020, 0, 0) returns 31 Dec 2019 and
    // new Date(2020, 13, 1) returns 1 Feb 2021.  This is *almost* the behavior we
    // want except that dates will wrap around the end of a month, meaning that
    // new Date(2020, 13, 31) will return 3 Mar 2021 not 28 Feb 2021 as desired. So
    // we'll default to the end of the desired month by adding 1 to the desired
    // month and using a date of 0 to back up one day to the end of the desired
    // month.
    var endOfDesiredMonth = new Date(date.getTime());
    endOfDesiredMonth.setMonth(date.getMonth() + amount + 1, 0);
    var daysInMonth = endOfDesiredMonth.getDate();
    if (dayOfMonth >= daysInMonth) {
      // If we're already at the end of the month, then this is the correct date
      // and we're done.
      return endOfDesiredMonth;
    } else {
      // Otherwise, we now know that setting the original day-of-month value won't
      // cause an overflow, so set the desired day-of-month. Note that we can't
      // just set the date of `endOfDesiredMonth` because that object may have had
      // its time changed in the unusual case where where a DST transition was on
      // the last day of the month and its local time was in the hour skipped or
      // repeated next to a DST transition.  So we use `date` instead which is
      // guaranteed to still have the original time.
      date.setFullYear(endOfDesiredMonth.getFullYear(), endOfDesiredMonth.getMonth(), dayOfMonth);
      return date;
    }
  }

  /**
   * @name addMilliseconds
   * @category Millisecond Helpers
   * @summary Add the specified number of milliseconds to the given date.
   *
   * @description
   * Add the specified number of milliseconds to the given date.
   *
   * @param {Date|Number} date - the date to be changed
   * @param {Number} amount - the amount of milliseconds to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.
   * @returns {Date} the new date with the milliseconds added
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // Add 750 milliseconds to 10 July 2014 12:45:30.000:
   * const result = addMilliseconds(new Date(2014, 6, 10, 12, 45, 30, 0), 750)
   * //=> Thu Jul 10 2014 12:45:30.750
   */
  function addMilliseconds(dirtyDate, dirtyAmount) {
    requiredArgs(2, arguments);
    var timestamp = toDate(dirtyDate).getTime();
    var amount = toInteger(dirtyAmount);
    return new Date(timestamp + amount);
  }

  var MILLISECONDS_IN_HOUR = 3600000;

  /**
   * @name addHours
   * @category Hour Helpers
   * @summary Add the specified number of hours to the given date.
   *
   * @description
   * Add the specified number of hours to the given date.
   *
   * @param {Date|Number} date - the date to be changed
   * @param {Number} amount - the amount of hours to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.
   * @returns {Date} the new date with the hours added
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // Add 2 hours to 10 July 2014 23:00:00:
   * const result = addHours(new Date(2014, 6, 10, 23, 0), 2)
   * //=> Fri Jul 11 2014 01:00:00
   */
  function addHours(dirtyDate, dirtyAmount) {
    requiredArgs(2, arguments);
    var amount = toInteger(dirtyAmount);
    return addMilliseconds(dirtyDate, amount * MILLISECONDS_IN_HOUR);
  }

  var defaultOptions = {};
  function getDefaultOptions() {
    return defaultOptions;
  }

  /**
   * @name startOfWeek
   * @category Week Helpers
   * @summary Return the start of a week for the given date.
   *
   * @description
   * Return the start of a week for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @param {Object} [options] - an object with options.
   * @param {Locale} [options.locale=defaultLocale] - the locale object. See [Locale]{@link https://date-fns.org/docs/Locale}
   * @param {0|1|2|3|4|5|6} [options.weekStartsOn=0] - the index of the first day of the week (0 - Sunday)
   * @returns {Date} the start of a week
   * @throws {TypeError} 1 argument required
   * @throws {RangeError} `options.weekStartsOn` must be between 0 and 6
   *
   * @example
   * // The start of a week for 2 September 2014 11:55:00:
   * const result = startOfWeek(new Date(2014, 8, 2, 11, 55, 0))
   * //=> Sun Aug 31 2014 00:00:00
   *
   * @example
   * // If the week starts on Monday, the start of the week for 2 September 2014 11:55:00:
   * const result = startOfWeek(new Date(2014, 8, 2, 11, 55, 0), { weekStartsOn: 1 })
   * //=> Mon Sep 01 2014 00:00:00
   */
  function startOfWeek(dirtyDate, options) {
    var _ref, _ref2, _ref3, _options$weekStartsOn, _options$locale, _options$locale$optio, _defaultOptions$local, _defaultOptions$local2;
    requiredArgs(1, arguments);
    var defaultOptions = getDefaultOptions();
    var weekStartsOn = toInteger((_ref = (_ref2 = (_ref3 = (_options$weekStartsOn = options === null || options === void 0 ? void 0 : options.weekStartsOn) !== null && _options$weekStartsOn !== void 0 ? _options$weekStartsOn : options === null || options === void 0 ? void 0 : (_options$locale = options.locale) === null || _options$locale === void 0 ? void 0 : (_options$locale$optio = _options$locale.options) === null || _options$locale$optio === void 0 ? void 0 : _options$locale$optio.weekStartsOn) !== null && _ref3 !== void 0 ? _ref3 : defaultOptions.weekStartsOn) !== null && _ref2 !== void 0 ? _ref2 : (_defaultOptions$local = defaultOptions.locale) === null || _defaultOptions$local === void 0 ? void 0 : (_defaultOptions$local2 = _defaultOptions$local.options) === null || _defaultOptions$local2 === void 0 ? void 0 : _defaultOptions$local2.weekStartsOn) !== null && _ref !== void 0 ? _ref : 0);

    // Test if weekStartsOn is between 0 and 6 _and_ is not NaN
    if (!(weekStartsOn >= 0 && weekStartsOn <= 6)) {
      throw new RangeError('weekStartsOn must be between 0 and 6 inclusively');
    }
    var date = toDate(dirtyDate);
    var day = date.getDay();
    var diff = (day < weekStartsOn ? 7 : 0) + day - weekStartsOn;
    date.setDate(date.getDate() - diff);
    date.setHours(0, 0, 0, 0);
    return date;
  }

  /**
   * Google Chrome as of 67.0.3396.87 introduced timezones with offset that includes seconds.
   * They usually appear for dates that denote time before the timezones were introduced
   * (e.g. for 'Europe/Prague' timezone the offset is GMT+00:57:44 before 1 October 1891
   * and GMT+01:00:00 after that date)
   *
   * Date#getTimezoneOffset returns the offset in minutes and would return 57 for the example above,
   * which would lead to incorrect calculations.
   *
   * This function returns the timezone offset in milliseconds that takes seconds in account.
   */
  function getTimezoneOffsetInMilliseconds(date) {
    var utcDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate(), date.getHours(), date.getMinutes(), date.getSeconds(), date.getMilliseconds()));
    utcDate.setUTCFullYear(date.getFullYear());
    return date.getTime() - utcDate.getTime();
  }

  /**
   * @name startOfDay
   * @category Day Helpers
   * @summary Return the start of a day for the given date.
   *
   * @description
   * Return the start of a day for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @returns {Date} the start of a day
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // The start of a day for 2 September 2014 11:55:00:
   * const result = startOfDay(new Date(2014, 8, 2, 11, 55, 0))
   * //=> Tue Sep 02 2014 00:00:00
   */
  function startOfDay(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    date.setHours(0, 0, 0, 0);
    return date;
  }

  var MILLISECONDS_IN_DAY$1 = 86400000;

  /**
   * @name differenceInCalendarDays
   * @category Day Helpers
   * @summary Get the number of calendar days between the given dates.
   *
   * @description
   * Get the number of calendar days between the given dates. This means that the times are removed
   * from the dates and then the difference in days is calculated.
   *
   * @param {Date|Number} dateLeft - the later date
   * @param {Date|Number} dateRight - the earlier date
   * @returns {Number} the number of calendar days
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // How many calendar days are between
   * // 2 July 2011 23:00:00 and 2 July 2012 00:00:00?
   * const result = differenceInCalendarDays(
   *   new Date(2012, 6, 2, 0, 0),
   *   new Date(2011, 6, 2, 23, 0)
   * )
   * //=> 366
   * // How many calendar days are between
   * // 2 July 2011 23:59:00 and 3 July 2011 00:01:00?
   * const result = differenceInCalendarDays(
   *   new Date(2011, 6, 3, 0, 1),
   *   new Date(2011, 6, 2, 23, 59)
   * )
   * //=> 1
   */
  function differenceInCalendarDays(dirtyDateLeft, dirtyDateRight) {
    requiredArgs(2, arguments);
    var startOfDayLeft = startOfDay(dirtyDateLeft);
    var startOfDayRight = startOfDay(dirtyDateRight);
    var timestampLeft = startOfDayLeft.getTime() - getTimezoneOffsetInMilliseconds(startOfDayLeft);
    var timestampRight = startOfDayRight.getTime() - getTimezoneOffsetInMilliseconds(startOfDayRight);

    // Round the number of days to the nearest integer
    // because the number of milliseconds in a day is not constant
    // (e.g. it's different in the day of the daylight saving time clock shift)
    return Math.round((timestampLeft - timestampRight) / MILLISECONDS_IN_DAY$1);
  }

  var MILLISECONDS_IN_MINUTE = 60000;

  /**
   * @name addMinutes
   * @category Minute Helpers
   * @summary Add the specified number of minutes to the given date.
   *
   * @description
   * Add the specified number of minutes to the given date.
   *
   * @param {Date|Number} date - the date to be changed
   * @param {Number} amount - the amount of minutes to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.
   * @returns {Date} the new date with the minutes added
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // Add 30 minutes to 10 July 2014 12:00:00:
   * const result = addMinutes(new Date(2014, 6, 10, 12, 0), 30)
   * //=> Thu Jul 10 2014 12:30:00
   */
  function addMinutes(dirtyDate, dirtyAmount) {
    requiredArgs(2, arguments);
    var amount = toInteger(dirtyAmount);
    return addMilliseconds(dirtyDate, amount * MILLISECONDS_IN_MINUTE);
  }

  /**
   * @name addQuarters
   * @category Quarter Helpers
   * @summary Add the specified number of year quarters to the given date.
   *
   * @description
   * Add the specified number of year quarters to the given date.
   *
   * @param {Date|Number} date - the date to be changed
   * @param {Number} amount - the amount of quarters to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.
   * @returns {Date} the new date with the quarters added
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // Add 1 quarter to 1 September 2014:
   * const result = addQuarters(new Date(2014, 8, 1), 1)
   * //=> Mon Dec 01 2014 00:00:00
   */
  function addQuarters(dirtyDate, dirtyAmount) {
    requiredArgs(2, arguments);
    var amount = toInteger(dirtyAmount);
    var months = amount * 3;
    return addMonths(dirtyDate, months);
  }

  /**
   * @name addSeconds
   * @category Second Helpers
   * @summary Add the specified number of seconds to the given date.
   *
   * @description
   * Add the specified number of seconds to the given date.
   *
   * @param {Date|Number} date - the date to be changed
   * @param {Number} amount - the amount of seconds to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.
   * @returns {Date} the new date with the seconds added
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // Add 30 seconds to 10 July 2014 12:45:00:
   * const result = addSeconds(new Date(2014, 6, 10, 12, 45, 0), 30)
   * //=> Thu Jul 10 2014 12:45:30
   */
  function addSeconds(dirtyDate, dirtyAmount) {
    requiredArgs(2, arguments);
    var amount = toInteger(dirtyAmount);
    return addMilliseconds(dirtyDate, amount * 1000);
  }

  /**
   * @name addWeeks
   * @category Week Helpers
   * @summary Add the specified number of weeks to the given date.
   *
   * @description
   * Add the specified number of week to the given date.
   *
   * @param {Date|Number} date - the date to be changed
   * @param {Number} amount - the amount of weeks to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.
   * @returns {Date} the new date with the weeks added
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // Add 4 weeks to 1 September 2014:
   * const result = addWeeks(new Date(2014, 8, 1), 4)
   * //=> Mon Sep 29 2014 00:00:00
   */
  function addWeeks(dirtyDate, dirtyAmount) {
    requiredArgs(2, arguments);
    var amount = toInteger(dirtyAmount);
    var days = amount * 7;
    return addDays(dirtyDate, days);
  }

  /**
   * @name addYears
   * @category Year Helpers
   * @summary Add the specified number of years to the given date.
   *
   * @description
   * Add the specified number of years to the given date.
   *
   * @param {Date|Number} date - the date to be changed
   * @param {Number} amount - the amount of years to be added. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.
   * @returns {Date} the new date with the years added
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // Add 5 years to 1 September 2014:
   * const result = addYears(new Date(2014, 8, 1), 5)
   * //=> Sun Sep 01 2019 00:00:00
   */
  function addYears(dirtyDate, dirtyAmount) {
    requiredArgs(2, arguments);
    var amount = toInteger(dirtyAmount);
    return addMonths(dirtyDate, amount * 12);
  }

  /**
   * @name compareAsc
   * @category Common Helpers
   * @summary Compare the two dates and return -1, 0 or 1.
   *
   * @description
   * Compare the two dates and return 1 if the first date is after the second,
   * -1 if the first date is before the second or 0 if dates are equal.
   *
   * @param {Date|Number} dateLeft - the first date to compare
   * @param {Date|Number} dateRight - the second date to compare
   * @returns {Number} the result of the comparison
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // Compare 11 February 1987 and 10 July 1989:
   * const result = compareAsc(new Date(1987, 1, 11), new Date(1989, 6, 10))
   * //=> -1
   *
   * @example
   * // Sort the array of dates:
   * const result = [
   *   new Date(1995, 6, 2),
   *   new Date(1987, 1, 11),
   *   new Date(1989, 6, 10)
   * ].sort(compareAsc)
   * //=> [
   * //   Wed Feb 11 1987 00:00:00,
   * //   Mon Jul 10 1989 00:00:00,
   * //   Sun Jul 02 1995 00:00:00
   * // ]
   */
  function compareAsc(dirtyDateLeft, dirtyDateRight) {
    requiredArgs(2, arguments);
    var dateLeft = toDate(dirtyDateLeft);
    var dateRight = toDate(dirtyDateRight);
    var diff = dateLeft.getTime() - dateRight.getTime();
    if (diff < 0) {
      return -1;
    } else if (diff > 0) {
      return 1;
      // Return 0 if diff is 0; return NaN if diff is NaN
    } else {
      return diff;
    }
  }

  /**
   * Days in 1 week.
   *
   * @name daysInWeek
   * @constant
   * @type {number}
   * @default
   */

  /**
   * Milliseconds in 1 minute
   *
   * @name millisecondsInMinute
   * @constant
   * @type {number}
   * @default
   */
  var millisecondsInMinute = 60000;

  /**
   * Milliseconds in 1 hour
   *
   * @name millisecondsInHour
   * @constant
   * @type {number}
   * @default
   */
  var millisecondsInHour = 3600000;

  /**
   * Milliseconds in 1 second
   *
   * @name millisecondsInSecond
   * @constant
   * @type {number}
   * @default
   */
  var millisecondsInSecond = 1000;

  /**
   * @name isDate
   * @category Common Helpers
   * @summary Is the given value a date?
   *
   * @description
   * Returns true if the given value is an instance of Date. The function works for dates transferred across iframes.
   *
   * @param {*} value - the value to check
   * @returns {boolean} true if the given value is a date
   * @throws {TypeError} 1 arguments required
   *
   * @example
   * // For a valid date:
   * const result = isDate(new Date())
   * //=> true
   *
   * @example
   * // For an invalid date:
   * const result = isDate(new Date(NaN))
   * //=> true
   *
   * @example
   * // For some value:
   * const result = isDate('2014-02-31')
   * //=> false
   *
   * @example
   * // For an object:
   * const result = isDate({})
   * //=> false
   */
  function isDate(value) {
    requiredArgs(1, arguments);
    return value instanceof Date || _typeof(value) === 'object' && Object.prototype.toString.call(value) === '[object Date]';
  }

  /**
   * @name isValid
   * @category Common Helpers
   * @summary Is the given date valid?
   *
   * @description
   * Returns false if argument is Invalid Date and true otherwise.
   * Argument is converted to Date using `toDate`. See [toDate]{@link https://date-fns.org/docs/toDate}
   * Invalid Date is a Date, whose time value is NaN.
   *
   * Time value of Date: http://es5.github.io/#x15.9.1.1
   *
   * @param {*} date - the date to check
   * @returns {Boolean} the date is valid
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // For the valid date:
   * const result = isValid(new Date(2014, 1, 31))
   * //=> true
   *
   * @example
   * // For the value, convertable into a date:
   * const result = isValid(1393804800000)
   * //=> true
   *
   * @example
   * // For the invalid date:
   * const result = isValid(new Date(''))
   * //=> false
   */
  function isValid(dirtyDate) {
    requiredArgs(1, arguments);
    if (!isDate(dirtyDate) && typeof dirtyDate !== 'number') {
      return false;
    }
    var date = toDate(dirtyDate);
    return !isNaN(Number(date));
  }

  /**
   * @name differenceInCalendarMonths
   * @category Month Helpers
   * @summary Get the number of calendar months between the given dates.
   *
   * @description
   * Get the number of calendar months between the given dates.
   *
   * @param {Date|Number} dateLeft - the later date
   * @param {Date|Number} dateRight - the earlier date
   * @returns {Number} the number of calendar months
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // How many calendar months are between 31 January 2014 and 1 September 2014?
   * const result = differenceInCalendarMonths(
   *   new Date(2014, 8, 1),
   *   new Date(2014, 0, 31)
   * )
   * //=> 8
   */
  function differenceInCalendarMonths(dirtyDateLeft, dirtyDateRight) {
    requiredArgs(2, arguments);
    var dateLeft = toDate(dirtyDateLeft);
    var dateRight = toDate(dirtyDateRight);
    var yearDiff = dateLeft.getFullYear() - dateRight.getFullYear();
    var monthDiff = dateLeft.getMonth() - dateRight.getMonth();
    return yearDiff * 12 + monthDiff;
  }

  /**
   * @name differenceInCalendarYears
   * @category Year Helpers
   * @summary Get the number of calendar years between the given dates.
   *
   * @description
   * Get the number of calendar years between the given dates.
   *
   * @param {Date|Number} dateLeft - the later date
   * @param {Date|Number} dateRight - the earlier date
   * @returns {Number} the number of calendar years
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // How many calendar years are between 31 December 2013 and 11 February 2015?
   * const result = differenceInCalendarYears(
   *   new Date(2015, 1, 11),
   *   new Date(2013, 11, 31)
   * )
   * //=> 2
   */
  function differenceInCalendarYears(dirtyDateLeft, dirtyDateRight) {
    requiredArgs(2, arguments);
    var dateLeft = toDate(dirtyDateLeft);
    var dateRight = toDate(dirtyDateRight);
    return dateLeft.getFullYear() - dateRight.getFullYear();
  }

  // for accurate equality comparisons of UTC timestamps that end up
  // having the same representation in local time, e.g. one hour before
  // DST ends vs. the instant that DST ends.
  function compareLocalAsc(dateLeft, dateRight) {
    var diff = dateLeft.getFullYear() - dateRight.getFullYear() || dateLeft.getMonth() - dateRight.getMonth() || dateLeft.getDate() - dateRight.getDate() || dateLeft.getHours() - dateRight.getHours() || dateLeft.getMinutes() - dateRight.getMinutes() || dateLeft.getSeconds() - dateRight.getSeconds() || dateLeft.getMilliseconds() - dateRight.getMilliseconds();
    if (diff < 0) {
      return -1;
    } else if (diff > 0) {
      return 1;
      // Return 0 if diff is 0; return NaN if diff is NaN
    } else {
      return diff;
    }
  }

  /**
   * @name differenceInDays
   * @category Day Helpers
   * @summary Get the number of full days between the given dates.
   *
   * @description
   * Get the number of full day periods between two dates. Fractional days are
   * truncated towards zero.
   *
   * One "full day" is the distance between a local time in one day to the same
   * local time on the next or previous day. A full day can sometimes be less than
   * or more than 24 hours if a daylight savings change happens between two dates.
   *
   * To ignore DST and only measure exact 24-hour periods, use this instead:
   * `Math.floor(differenceInHours(dateLeft, dateRight)/24)|0`.
   *
   *
   * @param {Date|Number} dateLeft - the later date
   * @param {Date|Number} dateRight - the earlier date
   * @returns {Number} the number of full days according to the local timezone
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // How many full days are between
   * // 2 July 2011 23:00:00 and 2 July 2012 00:00:00?
   * const result = differenceInDays(
   *   new Date(2012, 6, 2, 0, 0),
   *   new Date(2011, 6, 2, 23, 0)
   * )
   * //=> 365
   * // How many full days are between
   * // 2 July 2011 23:59:00 and 3 July 2011 00:01:00?
   * const result = differenceInDays(
   *   new Date(2011, 6, 3, 0, 1),
   *   new Date(2011, 6, 2, 23, 59)
   * )
   * //=> 0
   * // How many full days are between
   * // 1 March 2020 0:00 and 1 June 2020 0:00 ?
   * // Note: because local time is used, the
   * // result will always be 92 days, even in
   * // time zones where DST starts and the
   * // period has only 92*24-1 hours.
   * const result = differenceInDays(
   *   new Date(2020, 5, 1),
   *   new Date(2020, 2, 1)
   * )
  //=> 92
   */
  function differenceInDays(dirtyDateLeft, dirtyDateRight) {
    requiredArgs(2, arguments);
    var dateLeft = toDate(dirtyDateLeft);
    var dateRight = toDate(dirtyDateRight);
    var sign = compareLocalAsc(dateLeft, dateRight);
    var difference = Math.abs(differenceInCalendarDays(dateLeft, dateRight));
    dateLeft.setDate(dateLeft.getDate() - sign * difference);

    // Math.abs(diff in full days - diff in calendar days) === 1 if last calendar day is not full
    // If so, result must be decreased by 1 in absolute value
    var isLastDayNotFull = Number(compareLocalAsc(dateLeft, dateRight) === -sign);
    var result = sign * (difference - isLastDayNotFull);
    // Prevent negative zero
    return result === 0 ? 0 : result;
  }

  /**
   * @name differenceInMilliseconds
   * @category Millisecond Helpers
   * @summary Get the number of milliseconds between the given dates.
   *
   * @description
   * Get the number of milliseconds between the given dates.
   *
   * @param {Date|Number} dateLeft - the later date
   * @param {Date|Number} dateRight - the earlier date
   * @returns {Number} the number of milliseconds
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // How many milliseconds are between
   * // 2 July 2014 12:30:20.600 and 2 July 2014 12:30:21.700?
   * const result = differenceInMilliseconds(
   *   new Date(2014, 6, 2, 12, 30, 21, 700),
   *   new Date(2014, 6, 2, 12, 30, 20, 600)
   * )
   * //=> 1100
   */
  function differenceInMilliseconds(dateLeft, dateRight) {
    requiredArgs(2, arguments);
    return toDate(dateLeft).getTime() - toDate(dateRight).getTime();
  }

  var roundingMap = {
    ceil: Math.ceil,
    round: Math.round,
    floor: Math.floor,
    trunc: function trunc(value) {
      return value < 0 ? Math.ceil(value) : Math.floor(value);
    } // Math.trunc is not supported by IE
  };
  var defaultRoundingMethod = 'trunc';
  function getRoundingMethod(method) {
    return method ? roundingMap[method] : roundingMap[defaultRoundingMethod];
  }

  /**
   * @name differenceInHours
   * @category Hour Helpers
   * @summary Get the number of hours between the given dates.
   *
   * @description
   * Get the number of hours between the given dates.
   *
   * @param {Date|Number} dateLeft - the later date
   * @param {Date|Number} dateRight - the earlier date
   * @param {Object} [options] - an object with options.
   * @param {String} [options.roundingMethod='trunc'] - a rounding method (`ceil`, `floor`, `round` or `trunc`)
   * @returns {Number} the number of hours
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // How many hours are between 2 July 2014 06:50:00 and 2 July 2014 19:00:00?
   * const result = differenceInHours(
   *   new Date(2014, 6, 2, 19, 0),
   *   new Date(2014, 6, 2, 6, 50)
   * )
   * //=> 12
   */
  function differenceInHours(dateLeft, dateRight, options) {
    requiredArgs(2, arguments);
    var diff = differenceInMilliseconds(dateLeft, dateRight) / millisecondsInHour;
    return getRoundingMethod(options === null || options === void 0 ? void 0 : options.roundingMethod)(diff);
  }

  /**
   * @name differenceInMinutes
   * @category Minute Helpers
   * @summary Get the number of minutes between the given dates.
   *
   * @description
   * Get the signed number of full (rounded towards 0) minutes between the given dates.
   *
   * @param {Date|Number} dateLeft - the later date
   * @param {Date|Number} dateRight - the earlier date
   * @param {Object} [options] - an object with options.
   * @param {String} [options.roundingMethod='trunc'] - a rounding method (`ceil`, `floor`, `round` or `trunc`)
   * @returns {Number} the number of minutes
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // How many minutes are between 2 July 2014 12:07:59 and 2 July 2014 12:20:00?
   * const result = differenceInMinutes(
   *   new Date(2014, 6, 2, 12, 20, 0),
   *   new Date(2014, 6, 2, 12, 7, 59)
   * )
   * //=> 12
   *
   * @example
   * // How many minutes are between 10:01:59 and 10:00:00
   * const result = differenceInMinutes(
   *   new Date(2000, 0, 1, 10, 0, 0),
   *   new Date(2000, 0, 1, 10, 1, 59)
   * )
   * //=> -1
   */
  function differenceInMinutes(dateLeft, dateRight, options) {
    requiredArgs(2, arguments);
    var diff = differenceInMilliseconds(dateLeft, dateRight) / millisecondsInMinute;
    return getRoundingMethod(options === null || options === void 0 ? void 0 : options.roundingMethod)(diff);
  }

  /**
   * @name endOfDay
   * @category Day Helpers
   * @summary Return the end of a day for the given date.
   *
   * @description
   * Return the end of a day for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @returns {Date} the end of a day
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // The end of a day for 2 September 2014 11:55:00:
   * const result = endOfDay(new Date(2014, 8, 2, 11, 55, 0))
   * //=> Tue Sep 02 2014 23:59:59.999
   */
  function endOfDay(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    date.setHours(23, 59, 59, 999);
    return date;
  }

  /**
   * @name endOfMonth
   * @category Month Helpers
   * @summary Return the end of a month for the given date.
   *
   * @description
   * Return the end of a month for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @returns {Date} the end of a month
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // The end of a month for 2 September 2014 11:55:00:
   * const result = endOfMonth(new Date(2014, 8, 2, 11, 55, 0))
   * //=> Tue Sep 30 2014 23:59:59.999
   */
  function endOfMonth(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    var month = date.getMonth();
    date.setFullYear(date.getFullYear(), month + 1, 0);
    date.setHours(23, 59, 59, 999);
    return date;
  }

  /**
   * @name isLastDayOfMonth
   * @category Month Helpers
   * @summary Is the given date the last day of a month?
   *
   * @description
   * Is the given date the last day of a month?
   *
   * @param {Date|Number} date - the date to check
   * @returns {Boolean} the date is the last day of a month
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // Is 28 February 2014 the last day of a month?
   * const result = isLastDayOfMonth(new Date(2014, 1, 28))
   * //=> true
   */
  function isLastDayOfMonth(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    return endOfDay(date).getTime() === endOfMonth(date).getTime();
  }

  /**
   * @name differenceInMonths
   * @category Month Helpers
   * @summary Get the number of full months between the given dates.
   *
   * @description
   * Get the number of full months between the given dates using trunc as a default rounding method.
   *
   * @param {Date|Number} dateLeft - the later date
   * @param {Date|Number} dateRight - the earlier date
   * @returns {Number} the number of full months
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // How many full months are between 31 January 2014 and 1 September 2014?
   * const result = differenceInMonths(new Date(2014, 8, 1), new Date(2014, 0, 31))
   * //=> 7
   */
  function differenceInMonths(dirtyDateLeft, dirtyDateRight) {
    requiredArgs(2, arguments);
    var dateLeft = toDate(dirtyDateLeft);
    var dateRight = toDate(dirtyDateRight);
    var sign = compareAsc(dateLeft, dateRight);
    var difference = Math.abs(differenceInCalendarMonths(dateLeft, dateRight));
    var result;

    // Check for the difference of less than month
    if (difference < 1) {
      result = 0;
    } else {
      if (dateLeft.getMonth() === 1 && dateLeft.getDate() > 27) {
        // This will check if the date is end of Feb and assign a higher end of month date
        // to compare it with Jan
        dateLeft.setDate(30);
      }
      dateLeft.setMonth(dateLeft.getMonth() - sign * difference);

      // Math.abs(diff in full months - diff in calendar months) === 1 if last calendar month is not full
      // If so, result must be decreased by 1 in absolute value
      var isLastMonthNotFull = compareAsc(dateLeft, dateRight) === -sign;

      // Check for cases of one full calendar month
      if (isLastDayOfMonth(toDate(dirtyDateLeft)) && difference === 1 && compareAsc(dirtyDateLeft, dateRight) === 1) {
        isLastMonthNotFull = false;
      }
      result = sign * (difference - Number(isLastMonthNotFull));
    }

    // Prevent negative zero
    return result === 0 ? 0 : result;
  }

  /**
   * @name differenceInQuarters
   * @category Quarter Helpers
   * @summary Get the number of quarters between the given dates.
   *
   * @description
   * Get the number of quarters between the given dates.
   *
   * @param {Date|Number} dateLeft - the later date
   * @param {Date|Number} dateRight - the earlier date
   * @param {Object} [options] - an object with options.
   * @param {String} [options.roundingMethod='trunc'] - a rounding method (`ceil`, `floor`, `round` or `trunc`)
   * @returns {Number} the number of full quarters
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // How many full quarters are between 31 December 2013 and 2 July 2014?
   * const result = differenceInQuarters(new Date(2014, 6, 2), new Date(2013, 11, 31))
   * //=> 2
   */
  function differenceInQuarters(dateLeft, dateRight, options) {
    requiredArgs(2, arguments);
    var diff = differenceInMonths(dateLeft, dateRight) / 3;
    return getRoundingMethod(options === null || options === void 0 ? void 0 : options.roundingMethod)(diff);
  }

  /**
   * @name differenceInSeconds
   * @category Second Helpers
   * @summary Get the number of seconds between the given dates.
   *
   * @description
   * Get the number of seconds between the given dates.
   *
   * @param {Date|Number} dateLeft - the later date
   * @param {Date|Number} dateRight - the earlier date
   * @param {Object} [options] - an object with options.
   * @param {String} [options.roundingMethod='trunc'] - a rounding method (`ceil`, `floor`, `round` or `trunc`)
   * @returns {Number} the number of seconds
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // How many seconds are between
   * // 2 July 2014 12:30:07.999 and 2 July 2014 12:30:20.000?
   * const result = differenceInSeconds(
   *   new Date(2014, 6, 2, 12, 30, 20, 0),
   *   new Date(2014, 6, 2, 12, 30, 7, 999)
   * )
   * //=> 12
   */
  function differenceInSeconds(dateLeft, dateRight, options) {
    requiredArgs(2, arguments);
    var diff = differenceInMilliseconds(dateLeft, dateRight) / 1000;
    return getRoundingMethod(options === null || options === void 0 ? void 0 : options.roundingMethod)(diff);
  }

  /**
   * @name differenceInWeeks
   * @category Week Helpers
   * @summary Get the number of full weeks between the given dates.
   *
   * @description
   * Get the number of full weeks between two dates. Fractional weeks are
   * truncated towards zero by default.
   *
   * One "full week" is the distance between a local time in one day to the same
   * local time 7 days earlier or later. A full week can sometimes be less than
   * or more than 7*24 hours if a daylight savings change happens between two dates.
   *
   * To ignore DST and only measure exact 7*24-hour periods, use this instead:
   * `Math.floor(differenceInHours(dateLeft, dateRight)/(7*24))|0`.
   *
   *
   * @param {Date|Number} dateLeft - the later date
   * @param {Date|Number} dateRight - the earlier date
   * @param {Object} [options] - an object with options.
   * @param {String} [options.roundingMethod='trunc'] - a rounding method (`ceil`, `floor`, `round` or `trunc`)
   * @returns {Number} the number of full weeks
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // How many full weeks are between 5 July 2014 and 20 July 2014?
   * const result = differenceInWeeks(new Date(2014, 6, 20), new Date(2014, 6, 5))
   * //=> 2
   *
   * // How many full weeks are between
   * // 1 March 2020 0:00 and 6 June 2020 0:00 ?
   * // Note: because local time is used, the
   * // result will always be 8 weeks (54 days),
   * // even if DST starts and the period has
   * // only 54*24-1 hours.
   * const result = differenceInWeeks(
   *   new Date(2020, 5, 1),
   *   new Date(2020, 2, 6)
   * )
   * //=> 8
   */
  function differenceInWeeks(dateLeft, dateRight, options) {
    requiredArgs(2, arguments);
    var diff = differenceInDays(dateLeft, dateRight) / 7;
    return getRoundingMethod(options === null || options === void 0 ? void 0 : options.roundingMethod)(diff);
  }

  /**
   * @name differenceInYears
   * @category Year Helpers
   * @summary Get the number of full years between the given dates.
   *
   * @description
   * Get the number of full years between the given dates.
   *
   * @param {Date|Number} dateLeft - the later date
   * @param {Date|Number} dateRight - the earlier date
   * @returns {Number} the number of full years
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // How many full years are between 31 December 2013 and 11 February 2015?
   * const result = differenceInYears(new Date(2015, 1, 11), new Date(2013, 11, 31))
   * //=> 1
   */
  function differenceInYears(dirtyDateLeft, dirtyDateRight) {
    requiredArgs(2, arguments);
    var dateLeft = toDate(dirtyDateLeft);
    var dateRight = toDate(dirtyDateRight);
    var sign = compareAsc(dateLeft, dateRight);
    var difference = Math.abs(differenceInCalendarYears(dateLeft, dateRight));

    // Set both dates to a valid leap year for accurate comparison when dealing
    // with leap days
    dateLeft.setFullYear(1584);
    dateRight.setFullYear(1584);

    // Math.abs(diff in full years - diff in calendar years) === 1 if last calendar year is not full
    // If so, result must be decreased by 1 in absolute value
    var isLastYearNotFull = compareAsc(dateLeft, dateRight) === -sign;
    var result = sign * (difference - Number(isLastYearNotFull));
    // Prevent negative zero
    return result === 0 ? 0 : result;
  }

  /**
   * @name startOfMinute
   * @category Minute Helpers
   * @summary Return the start of a minute for the given date.
   *
   * @description
   * Return the start of a minute for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @returns {Date} the start of a minute
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // The start of a minute for 1 December 2014 22:15:45.400:
   * const result = startOfMinute(new Date(2014, 11, 1, 22, 15, 45, 400))
   * //=> Mon Dec 01 2014 22:15:00
   */
  function startOfMinute(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    date.setSeconds(0, 0);
    return date;
  }

  /**
   * @name startOfQuarter
   * @category Quarter Helpers
   * @summary Return the start of a year quarter for the given date.
   *
   * @description
   * Return the start of a year quarter for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @returns {Date} the start of a quarter
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // The start of a quarter for 2 September 2014 11:55:00:
   * const result = startOfQuarter(new Date(2014, 8, 2, 11, 55, 0))
   * //=> Tue Jul 01 2014 00:00:00
   */
  function startOfQuarter(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    var currentMonth = date.getMonth();
    var month = currentMonth - currentMonth % 3;
    date.setMonth(month, 1);
    date.setHours(0, 0, 0, 0);
    return date;
  }

  /**
   * @name startOfMonth
   * @category Month Helpers
   * @summary Return the start of a month for the given date.
   *
   * @description
   * Return the start of a month for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @returns {Date} the start of a month
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // The start of a month for 2 September 2014 11:55:00:
   * const result = startOfMonth(new Date(2014, 8, 2, 11, 55, 0))
   * //=> Mon Sep 01 2014 00:00:00
   */
  function startOfMonth(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    date.setDate(1);
    date.setHours(0, 0, 0, 0);
    return date;
  }

  /**
   * @name endOfYear
   * @category Year Helpers
   * @summary Return the end of a year for the given date.
   *
   * @description
   * Return the end of a year for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @returns {Date} the end of a year
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // The end of a year for 2 September 2014 11:55:00:
   * const result = endOfYear(new Date(2014, 8, 2, 11, 55, 00))
   * //=> Wed Dec 31 2014 23:59:59.999
   */
  function endOfYear(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    var year = date.getFullYear();
    date.setFullYear(year + 1, 0, 0);
    date.setHours(23, 59, 59, 999);
    return date;
  }

  /**
   * @name startOfYear
   * @category Year Helpers
   * @summary Return the start of a year for the given date.
   *
   * @description
   * Return the start of a year for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @returns {Date} the start of a year
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // The start of a year for 2 September 2014 11:55:00:
   * const result = startOfYear(new Date(2014, 8, 2, 11, 55, 00))
   * //=> Wed Jan 01 2014 00:00:00
   */
  function startOfYear(dirtyDate) {
    requiredArgs(1, arguments);
    var cleanDate = toDate(dirtyDate);
    var date = new Date(0);
    date.setFullYear(cleanDate.getFullYear(), 0, 1);
    date.setHours(0, 0, 0, 0);
    return date;
  }

  /**
   * @name endOfHour
   * @category Hour Helpers
   * @summary Return the end of an hour for the given date.
   *
   * @description
   * Return the end of an hour for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @returns {Date} the end of an hour
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // The end of an hour for 2 September 2014 11:55:00:
   * const result = endOfHour(new Date(2014, 8, 2, 11, 55))
   * //=> Tue Sep 02 2014 11:59:59.999
   */
  function endOfHour(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    date.setMinutes(59, 59, 999);
    return date;
  }

  /**
   * @name endOfWeek
   * @category Week Helpers
   * @summary Return the end of a week for the given date.
   *
   * @description
   * Return the end of a week for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @param {Object} [options] - an object with options.
   * @param {Locale} [options.locale=defaultLocale] - the locale object. See [Locale]{@link https://date-fns.org/docs/Locale}
   * @param {0|1|2|3|4|5|6} [options.weekStartsOn=0] - the index of the first day of the week (0 - Sunday)
   * @returns {Date} the end of a week
   * @throws {TypeError} 1 argument required
   * @throws {RangeError} `options.weekStartsOn` must be between 0 and 6
   *
   * @example
   * // The end of a week for 2 September 2014 11:55:00:
   * const result = endOfWeek(new Date(2014, 8, 2, 11, 55, 0))
   * //=> Sat Sep 06 2014 23:59:59.999
   *
   * @example
   * // If the week starts on Monday, the end of the week for 2 September 2014 11:55:00:
   * const result = endOfWeek(new Date(2014, 8, 2, 11, 55, 0), { weekStartsOn: 1 })
   * //=> Sun Sep 07 2014 23:59:59.999
   */
  function endOfWeek(dirtyDate, options) {
    var _ref, _ref2, _ref3, _options$weekStartsOn, _options$locale, _options$locale$optio, _defaultOptions$local, _defaultOptions$local2;
    requiredArgs(1, arguments);
    var defaultOptions = getDefaultOptions();
    var weekStartsOn = toInteger((_ref = (_ref2 = (_ref3 = (_options$weekStartsOn = options === null || options === void 0 ? void 0 : options.weekStartsOn) !== null && _options$weekStartsOn !== void 0 ? _options$weekStartsOn : options === null || options === void 0 ? void 0 : (_options$locale = options.locale) === null || _options$locale === void 0 ? void 0 : (_options$locale$optio = _options$locale.options) === null || _options$locale$optio === void 0 ? void 0 : _options$locale$optio.weekStartsOn) !== null && _ref3 !== void 0 ? _ref3 : defaultOptions.weekStartsOn) !== null && _ref2 !== void 0 ? _ref2 : (_defaultOptions$local = defaultOptions.locale) === null || _defaultOptions$local === void 0 ? void 0 : (_defaultOptions$local2 = _defaultOptions$local.options) === null || _defaultOptions$local2 === void 0 ? void 0 : _defaultOptions$local2.weekStartsOn) !== null && _ref !== void 0 ? _ref : 0);

    // Test if weekStartsOn is between 0 and 6 _and_ is not NaN
    if (!(weekStartsOn >= 0 && weekStartsOn <= 6)) {
      throw new RangeError('weekStartsOn must be between 0 and 6 inclusively');
    }
    var date = toDate(dirtyDate);
    var day = date.getDay();
    var diff = (day < weekStartsOn ? -7 : 0) + 6 - (day - weekStartsOn);
    date.setDate(date.getDate() + diff);
    date.setHours(23, 59, 59, 999);
    return date;
  }

  /**
   * @name endOfMinute
   * @category Minute Helpers
   * @summary Return the end of a minute for the given date.
   *
   * @description
   * Return the end of a minute for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @returns {Date} the end of a minute
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // The end of a minute for 1 December 2014 22:15:45.400:
   * const result = endOfMinute(new Date(2014, 11, 1, 22, 15, 45, 400))
   * //=> Mon Dec 01 2014 22:15:59.999
   */
  function endOfMinute(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    date.setSeconds(59, 999);
    return date;
  }

  /**
   * @name endOfQuarter
   * @category Quarter Helpers
   * @summary Return the end of a year quarter for the given date.
   *
   * @description
   * Return the end of a year quarter for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @returns {Date} the end of a quarter
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // The end of a quarter for 2 September 2014 11:55:00:
   * const result = endOfQuarter(new Date(2014, 8, 2, 11, 55, 0))
   * //=> Tue Sep 30 2014 23:59:59.999
   */
  function endOfQuarter(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    var currentMonth = date.getMonth();
    var month = currentMonth - currentMonth % 3 + 3;
    date.setMonth(month, 0);
    date.setHours(23, 59, 59, 999);
    return date;
  }

  /**
   * @name endOfSecond
   * @category Second Helpers
   * @summary Return the end of a second for the given date.
   *
   * @description
   * Return the end of a second for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @returns {Date} the end of a second
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // The end of a second for 1 December 2014 22:15:45.400:
   * const result = endOfSecond(new Date(2014, 11, 1, 22, 15, 45, 400))
   * //=> Mon Dec 01 2014 22:15:45.999
   */
  function endOfSecond(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    date.setMilliseconds(999);
    return date;
  }

  /**
   * @name subMilliseconds
   * @category Millisecond Helpers
   * @summary Subtract the specified number of milliseconds from the given date.
   *
   * @description
   * Subtract the specified number of milliseconds from the given date.
   *
   * @param {Date|Number} date - the date to be changed
   * @param {Number} amount - the amount of milliseconds to be subtracted. Positive decimals will be rounded using `Math.floor`, decimals less than zero will be rounded using `Math.ceil`.
   * @returns {Date} the new date with the milliseconds subtracted
   * @throws {TypeError} 2 arguments required
   *
   * @example
   * // Subtract 750 milliseconds from 10 July 2014 12:45:30.000:
   * const result = subMilliseconds(new Date(2014, 6, 10, 12, 45, 30, 0), 750)
   * //=> Thu Jul 10 2014 12:45:29.250
   */
  function subMilliseconds(dirtyDate, dirtyAmount) {
    requiredArgs(2, arguments);
    var amount = toInteger(dirtyAmount);
    return addMilliseconds(dirtyDate, -amount);
  }

  var MILLISECONDS_IN_DAY = 86400000;
  function getUTCDayOfYear(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    var timestamp = date.getTime();
    date.setUTCMonth(0, 1);
    date.setUTCHours(0, 0, 0, 0);
    var startOfYearTimestamp = date.getTime();
    var difference = timestamp - startOfYearTimestamp;
    return Math.floor(difference / MILLISECONDS_IN_DAY) + 1;
  }

  function startOfUTCISOWeek(dirtyDate) {
    requiredArgs(1, arguments);
    var weekStartsOn = 1;
    var date = toDate(dirtyDate);
    var day = date.getUTCDay();
    var diff = (day < weekStartsOn ? 7 : 0) + day - weekStartsOn;
    date.setUTCDate(date.getUTCDate() - diff);
    date.setUTCHours(0, 0, 0, 0);
    return date;
  }

  function getUTCISOWeekYear(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    var year = date.getUTCFullYear();
    var fourthOfJanuaryOfNextYear = new Date(0);
    fourthOfJanuaryOfNextYear.setUTCFullYear(year + 1, 0, 4);
    fourthOfJanuaryOfNextYear.setUTCHours(0, 0, 0, 0);
    var startOfNextYear = startOfUTCISOWeek(fourthOfJanuaryOfNextYear);
    var fourthOfJanuaryOfThisYear = new Date(0);
    fourthOfJanuaryOfThisYear.setUTCFullYear(year, 0, 4);
    fourthOfJanuaryOfThisYear.setUTCHours(0, 0, 0, 0);
    var startOfThisYear = startOfUTCISOWeek(fourthOfJanuaryOfThisYear);
    if (date.getTime() >= startOfNextYear.getTime()) {
      return year + 1;
    } else if (date.getTime() >= startOfThisYear.getTime()) {
      return year;
    } else {
      return year - 1;
    }
  }

  function startOfUTCISOWeekYear(dirtyDate) {
    requiredArgs(1, arguments);
    var year = getUTCISOWeekYear(dirtyDate);
    var fourthOfJanuary = new Date(0);
    fourthOfJanuary.setUTCFullYear(year, 0, 4);
    fourthOfJanuary.setUTCHours(0, 0, 0, 0);
    var date = startOfUTCISOWeek(fourthOfJanuary);
    return date;
  }

  var MILLISECONDS_IN_WEEK$1 = 604800000;
  function getUTCISOWeek(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    var diff = startOfUTCISOWeek(date).getTime() - startOfUTCISOWeekYear(date).getTime();

    // Round the number of days to the nearest integer
    // because the number of milliseconds in a week is not constant
    // (e.g. it's different in the week of the daylight saving time clock shift)
    return Math.round(diff / MILLISECONDS_IN_WEEK$1) + 1;
  }

  function startOfUTCWeek(dirtyDate, options) {
    var _ref, _ref2, _ref3, _options$weekStartsOn, _options$locale, _options$locale$optio, _defaultOptions$local, _defaultOptions$local2;
    requiredArgs(1, arguments);
    var defaultOptions = getDefaultOptions();
    var weekStartsOn = toInteger((_ref = (_ref2 = (_ref3 = (_options$weekStartsOn = options === null || options === void 0 ? void 0 : options.weekStartsOn) !== null && _options$weekStartsOn !== void 0 ? _options$weekStartsOn : options === null || options === void 0 ? void 0 : (_options$locale = options.locale) === null || _options$locale === void 0 ? void 0 : (_options$locale$optio = _options$locale.options) === null || _options$locale$optio === void 0 ? void 0 : _options$locale$optio.weekStartsOn) !== null && _ref3 !== void 0 ? _ref3 : defaultOptions.weekStartsOn) !== null && _ref2 !== void 0 ? _ref2 : (_defaultOptions$local = defaultOptions.locale) === null || _defaultOptions$local === void 0 ? void 0 : (_defaultOptions$local2 = _defaultOptions$local.options) === null || _defaultOptions$local2 === void 0 ? void 0 : _defaultOptions$local2.weekStartsOn) !== null && _ref !== void 0 ? _ref : 0);

    // Test if weekStartsOn is between 0 and 6 _and_ is not NaN
    if (!(weekStartsOn >= 0 && weekStartsOn <= 6)) {
      throw new RangeError('weekStartsOn must be between 0 and 6 inclusively');
    }
    var date = toDate(dirtyDate);
    var day = date.getUTCDay();
    var diff = (day < weekStartsOn ? 7 : 0) + day - weekStartsOn;
    date.setUTCDate(date.getUTCDate() - diff);
    date.setUTCHours(0, 0, 0, 0);
    return date;
  }

  function getUTCWeekYear(dirtyDate, options) {
    var _ref, _ref2, _ref3, _options$firstWeekCon, _options$locale, _options$locale$optio, _defaultOptions$local, _defaultOptions$local2;
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    var year = date.getUTCFullYear();
    var defaultOptions = getDefaultOptions();
    var firstWeekContainsDate = toInteger((_ref = (_ref2 = (_ref3 = (_options$firstWeekCon = options === null || options === void 0 ? void 0 : options.firstWeekContainsDate) !== null && _options$firstWeekCon !== void 0 ? _options$firstWeekCon : options === null || options === void 0 ? void 0 : (_options$locale = options.locale) === null || _options$locale === void 0 ? void 0 : (_options$locale$optio = _options$locale.options) === null || _options$locale$optio === void 0 ? void 0 : _options$locale$optio.firstWeekContainsDate) !== null && _ref3 !== void 0 ? _ref3 : defaultOptions.firstWeekContainsDate) !== null && _ref2 !== void 0 ? _ref2 : (_defaultOptions$local = defaultOptions.locale) === null || _defaultOptions$local === void 0 ? void 0 : (_defaultOptions$local2 = _defaultOptions$local.options) === null || _defaultOptions$local2 === void 0 ? void 0 : _defaultOptions$local2.firstWeekContainsDate) !== null && _ref !== void 0 ? _ref : 1);

    // Test if weekStartsOn is between 1 and 7 _and_ is not NaN
    if (!(firstWeekContainsDate >= 1 && firstWeekContainsDate <= 7)) {
      throw new RangeError('firstWeekContainsDate must be between 1 and 7 inclusively');
    }
    var firstWeekOfNextYear = new Date(0);
    firstWeekOfNextYear.setUTCFullYear(year + 1, 0, firstWeekContainsDate);
    firstWeekOfNextYear.setUTCHours(0, 0, 0, 0);
    var startOfNextYear = startOfUTCWeek(firstWeekOfNextYear, options);
    var firstWeekOfThisYear = new Date(0);
    firstWeekOfThisYear.setUTCFullYear(year, 0, firstWeekContainsDate);
    firstWeekOfThisYear.setUTCHours(0, 0, 0, 0);
    var startOfThisYear = startOfUTCWeek(firstWeekOfThisYear, options);
    if (date.getTime() >= startOfNextYear.getTime()) {
      return year + 1;
    } else if (date.getTime() >= startOfThisYear.getTime()) {
      return year;
    } else {
      return year - 1;
    }
  }

  function startOfUTCWeekYear(dirtyDate, options) {
    var _ref, _ref2, _ref3, _options$firstWeekCon, _options$locale, _options$locale$optio, _defaultOptions$local, _defaultOptions$local2;
    requiredArgs(1, arguments);
    var defaultOptions = getDefaultOptions();
    var firstWeekContainsDate = toInteger((_ref = (_ref2 = (_ref3 = (_options$firstWeekCon = options === null || options === void 0 ? void 0 : options.firstWeekContainsDate) !== null && _options$firstWeekCon !== void 0 ? _options$firstWeekCon : options === null || options === void 0 ? void 0 : (_options$locale = options.locale) === null || _options$locale === void 0 ? void 0 : (_options$locale$optio = _options$locale.options) === null || _options$locale$optio === void 0 ? void 0 : _options$locale$optio.firstWeekContainsDate) !== null && _ref3 !== void 0 ? _ref3 : defaultOptions.firstWeekContainsDate) !== null && _ref2 !== void 0 ? _ref2 : (_defaultOptions$local = defaultOptions.locale) === null || _defaultOptions$local === void 0 ? void 0 : (_defaultOptions$local2 = _defaultOptions$local.options) === null || _defaultOptions$local2 === void 0 ? void 0 : _defaultOptions$local2.firstWeekContainsDate) !== null && _ref !== void 0 ? _ref : 1);
    var year = getUTCWeekYear(dirtyDate, options);
    var firstWeek = new Date(0);
    firstWeek.setUTCFullYear(year, 0, firstWeekContainsDate);
    firstWeek.setUTCHours(0, 0, 0, 0);
    var date = startOfUTCWeek(firstWeek, options);
    return date;
  }

  var MILLISECONDS_IN_WEEK = 604800000;
  function getUTCWeek(dirtyDate, options) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    var diff = startOfUTCWeek(date, options).getTime() - startOfUTCWeekYear(date, options).getTime();

    // Round the number of days to the nearest integer
    // because the number of milliseconds in a week is not constant
    // (e.g. it's different in the week of the daylight saving time clock shift)
    return Math.round(diff / MILLISECONDS_IN_WEEK) + 1;
  }

  function addLeadingZeros(number, targetLength) {
    var sign = number < 0 ? '-' : '';
    var output = Math.abs(number).toString();
    while (output.length < targetLength) {
      output = '0' + output;
    }
    return sign + output;
  }

  /*
   * |     | Unit                           |     | Unit                           |
   * |-----|--------------------------------|-----|--------------------------------|
   * |  a  | AM, PM                         |  A* |                                |
   * |  d  | Day of month                   |  D  |                                |
   * |  h  | Hour [1-12]                    |  H  | Hour [0-23]                    |
   * |  m  | Minute                         |  M  | Month                          |
   * |  s  | Second                         |  S  | Fraction of second             |
   * |  y  | Year (abs)                     |  Y  |                                |
   *
   * Letters marked by * are not implemented but reserved by Unicode standard.
   */
  var formatters$2 = {
    // Year
    y: function y(date, token) {
      // From http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_tokens
      // | Year     |     y | yy |   yyy |  yyyy | yyyyy |
      // |----------|-------|----|-------|-------|-------|
      // | AD 1     |     1 | 01 |   001 |  0001 | 00001 |
      // | AD 12    |    12 | 12 |   012 |  0012 | 00012 |
      // | AD 123   |   123 | 23 |   123 |  0123 | 00123 |
      // | AD 1234  |  1234 | 34 |  1234 |  1234 | 01234 |
      // | AD 12345 | 12345 | 45 | 12345 | 12345 | 12345 |

      var signedYear = date.getUTCFullYear();
      // Returns 1 for 1 BC (which is year 0 in JavaScript)
      var year = signedYear > 0 ? signedYear : 1 - signedYear;
      return addLeadingZeros(token === 'yy' ? year % 100 : year, token.length);
    },
    // Month
    M: function M(date, token) {
      var month = date.getUTCMonth();
      return token === 'M' ? String(month + 1) : addLeadingZeros(month + 1, 2);
    },
    // Day of the month
    d: function d(date, token) {
      return addLeadingZeros(date.getUTCDate(), token.length);
    },
    // AM or PM
    a: function a(date, token) {
      var dayPeriodEnumValue = date.getUTCHours() / 12 >= 1 ? 'pm' : 'am';
      switch (token) {
        case 'a':
        case 'aa':
          return dayPeriodEnumValue.toUpperCase();
        case 'aaa':
          return dayPeriodEnumValue;
        case 'aaaaa':
          return dayPeriodEnumValue[0];
        case 'aaaa':
        default:
          return dayPeriodEnumValue === 'am' ? 'a.m.' : 'p.m.';
      }
    },
    // Hour [1-12]
    h: function h(date, token) {
      return addLeadingZeros(date.getUTCHours() % 12 || 12, token.length);
    },
    // Hour [0-23]
    H: function H(date, token) {
      return addLeadingZeros(date.getUTCHours(), token.length);
    },
    // Minute
    m: function m(date, token) {
      return addLeadingZeros(date.getUTCMinutes(), token.length);
    },
    // Second
    s: function s(date, token) {
      return addLeadingZeros(date.getUTCSeconds(), token.length);
    },
    // Fraction of second
    S: function S(date, token) {
      var numberOfDigits = token.length;
      var milliseconds = date.getUTCMilliseconds();
      var fractionalSeconds = Math.floor(milliseconds * Math.pow(10, numberOfDigits - 3));
      return addLeadingZeros(fractionalSeconds, token.length);
    }
  };
  var formatters$3 = formatters$2;

  var dayPeriodEnum = {
    am: 'am',
    pm: 'pm',
    midnight: 'midnight',
    noon: 'noon',
    morning: 'morning',
    afternoon: 'afternoon',
    evening: 'evening',
    night: 'night'
  };
  /*
   * |     | Unit                           |     | Unit                           |
   * |-----|--------------------------------|-----|--------------------------------|
   * |  a  | AM, PM                         |  A* | Milliseconds in day            |
   * |  b  | AM, PM, noon, midnight         |  B  | Flexible day period            |
   * |  c  | Stand-alone local day of week  |  C* | Localized hour w/ day period   |
   * |  d  | Day of month                   |  D  | Day of year                    |
   * |  e  | Local day of week              |  E  | Day of week                    |
   * |  f  |                                |  F* | Day of week in month           |
   * |  g* | Modified Julian day            |  G  | Era                            |
   * |  h  | Hour [1-12]                    |  H  | Hour [0-23]                    |
   * |  i! | ISO day of week                |  I! | ISO week of year               |
   * |  j* | Localized hour w/ day period   |  J* | Localized hour w/o day period  |
   * |  k  | Hour [1-24]                    |  K  | Hour [0-11]                    |
   * |  l* | (deprecated)                   |  L  | Stand-alone month              |
   * |  m  | Minute                         |  M  | Month                          |
   * |  n  |                                |  N  |                                |
   * |  o! | Ordinal number modifier        |  O  | Timezone (GMT)                 |
   * |  p! | Long localized time            |  P! | Long localized date            |
   * |  q  | Stand-alone quarter            |  Q  | Quarter                        |
   * |  r* | Related Gregorian year         |  R! | ISO week-numbering year        |
   * |  s  | Second                         |  S  | Fraction of second             |
   * |  t! | Seconds timestamp              |  T! | Milliseconds timestamp         |
   * |  u  | Extended year                  |  U* | Cyclic year                    |
   * |  v* | Timezone (generic non-locat.)  |  V* | Timezone (location)            |
   * |  w  | Local week of year             |  W* | Week of month                  |
   * |  x  | Timezone (ISO-8601 w/o Z)      |  X  | Timezone (ISO-8601)            |
   * |  y  | Year (abs)                     |  Y  | Local week-numbering year      |
   * |  z  | Timezone (specific non-locat.) |  Z* | Timezone (aliases)             |
   *
   * Letters marked by * are not implemented but reserved by Unicode standard.
   *
   * Letters marked by ! are non-standard, but implemented by date-fns:
   * - `o` modifies the previous token to turn it into an ordinal (see `format` docs)
   * - `i` is ISO day of week. For `i` and `ii` is returns numeric ISO week days,
   *   i.e. 7 for Sunday, 1 for Monday, etc.
   * - `I` is ISO week of year, as opposed to `w` which is local week of year.
   * - `R` is ISO week-numbering year, as opposed to `Y` which is local week-numbering year.
   *   `R` is supposed to be used in conjunction with `I` and `i`
   *   for universal ISO week-numbering date, whereas
   *   `Y` is supposed to be used in conjunction with `w` and `e`
   *   for week-numbering date specific to the locale.
   * - `P` is long localized date format
   * - `p` is long localized time format
   */

  var formatters = {
    // Era
    G: function G(date, token, localize) {
      var era = date.getUTCFullYear() > 0 ? 1 : 0;
      switch (token) {
        // AD, BC
        case 'G':
        case 'GG':
        case 'GGG':
          return localize.era(era, {
            width: 'abbreviated'
          });
        // A, B
        case 'GGGGG':
          return localize.era(era, {
            width: 'narrow'
          });
        // Anno Domini, Before Christ
        case 'GGGG':
        default:
          return localize.era(era, {
            width: 'wide'
          });
      }
    },
    // Year
    y: function y(date, token, localize) {
      // Ordinal number
      if (token === 'yo') {
        var signedYear = date.getUTCFullYear();
        // Returns 1 for 1 BC (which is year 0 in JavaScript)
        var year = signedYear > 0 ? signedYear : 1 - signedYear;
        return localize.ordinalNumber(year, {
          unit: 'year'
        });
      }
      return formatters$3.y(date, token);
    },
    // Local week-numbering year
    Y: function Y(date, token, localize, options) {
      var signedWeekYear = getUTCWeekYear(date, options);
      // Returns 1 for 1 BC (which is year 0 in JavaScript)
      var weekYear = signedWeekYear > 0 ? signedWeekYear : 1 - signedWeekYear;

      // Two digit year
      if (token === 'YY') {
        var twoDigitYear = weekYear % 100;
        return addLeadingZeros(twoDigitYear, 2);
      }

      // Ordinal number
      if (token === 'Yo') {
        return localize.ordinalNumber(weekYear, {
          unit: 'year'
        });
      }

      // Padding
      return addLeadingZeros(weekYear, token.length);
    },
    // ISO week-numbering year
    R: function R(date, token) {
      var isoWeekYear = getUTCISOWeekYear(date);

      // Padding
      return addLeadingZeros(isoWeekYear, token.length);
    },
    // Extended year. This is a single number designating the year of this calendar system.
    // The main difference between `y` and `u` localizers are B.C. years:
    // | Year | `y` | `u` |
    // |------|-----|-----|
    // | AC 1 |   1 |   1 |
    // | BC 1 |   1 |   0 |
    // | BC 2 |   2 |  -1 |
    // Also `yy` always returns the last two digits of a year,
    // while `uu` pads single digit years to 2 characters and returns other years unchanged.
    u: function u(date, token) {
      var year = date.getUTCFullYear();
      return addLeadingZeros(year, token.length);
    },
    // Quarter
    Q: function Q(date, token, localize) {
      var quarter = Math.ceil((date.getUTCMonth() + 1) / 3);
      switch (token) {
        // 1, 2, 3, 4
        case 'Q':
          return String(quarter);
        // 01, 02, 03, 04
        case 'QQ':
          return addLeadingZeros(quarter, 2);
        // 1st, 2nd, 3rd, 4th
        case 'Qo':
          return localize.ordinalNumber(quarter, {
            unit: 'quarter'
          });
        // Q1, Q2, Q3, Q4
        case 'QQQ':
          return localize.quarter(quarter, {
            width: 'abbreviated',
            context: 'formatting'
          });
        // 1, 2, 3, 4 (narrow quarter; could be not numerical)
        case 'QQQQQ':
          return localize.quarter(quarter, {
            width: 'narrow',
            context: 'formatting'
          });
        // 1st quarter, 2nd quarter, ...
        case 'QQQQ':
        default:
          return localize.quarter(quarter, {
            width: 'wide',
            context: 'formatting'
          });
      }
    },
    // Stand-alone quarter
    q: function q(date, token, localize) {
      var quarter = Math.ceil((date.getUTCMonth() + 1) / 3);
      switch (token) {
        // 1, 2, 3, 4
        case 'q':
          return String(quarter);
        // 01, 02, 03, 04
        case 'qq':
          return addLeadingZeros(quarter, 2);
        // 1st, 2nd, 3rd, 4th
        case 'qo':
          return localize.ordinalNumber(quarter, {
            unit: 'quarter'
          });
        // Q1, Q2, Q3, Q4
        case 'qqq':
          return localize.quarter(quarter, {
            width: 'abbreviated',
            context: 'standalone'
          });
        // 1, 2, 3, 4 (narrow quarter; could be not numerical)
        case 'qqqqq':
          return localize.quarter(quarter, {
            width: 'narrow',
            context: 'standalone'
          });
        // 1st quarter, 2nd quarter, ...
        case 'qqqq':
        default:
          return localize.quarter(quarter, {
            width: 'wide',
            context: 'standalone'
          });
      }
    },
    // Month
    M: function M(date, token, localize) {
      var month = date.getUTCMonth();
      switch (token) {
        case 'M':
        case 'MM':
          return formatters$3.M(date, token);
        // 1st, 2nd, ..., 12th
        case 'Mo':
          return localize.ordinalNumber(month + 1, {
            unit: 'month'
          });
        // Jan, Feb, ..., Dec
        case 'MMM':
          return localize.month(month, {
            width: 'abbreviated',
            context: 'formatting'
          });
        // J, F, ..., D
        case 'MMMMM':
          return localize.month(month, {
            width: 'narrow',
            context: 'formatting'
          });
        // January, February, ..., December
        case 'MMMM':
        default:
          return localize.month(month, {
            width: 'wide',
            context: 'formatting'
          });
      }
    },
    // Stand-alone month
    L: function L(date, token, localize) {
      var month = date.getUTCMonth();
      switch (token) {
        // 1, 2, ..., 12
        case 'L':
          return String(month + 1);
        // 01, 02, ..., 12
        case 'LL':
          return addLeadingZeros(month + 1, 2);
        // 1st, 2nd, ..., 12th
        case 'Lo':
          return localize.ordinalNumber(month + 1, {
            unit: 'month'
          });
        // Jan, Feb, ..., Dec
        case 'LLL':
          return localize.month(month, {
            width: 'abbreviated',
            context: 'standalone'
          });
        // J, F, ..., D
        case 'LLLLL':
          return localize.month(month, {
            width: 'narrow',
            context: 'standalone'
          });
        // January, February, ..., December
        case 'LLLL':
        default:
          return localize.month(month, {
            width: 'wide',
            context: 'standalone'
          });
      }
    },
    // Local week of year
    w: function w(date, token, localize, options) {
      var week = getUTCWeek(date, options);
      if (token === 'wo') {
        return localize.ordinalNumber(week, {
          unit: 'week'
        });
      }
      return addLeadingZeros(week, token.length);
    },
    // ISO week of year
    I: function I(date, token, localize) {
      var isoWeek = getUTCISOWeek(date);
      if (token === 'Io') {
        return localize.ordinalNumber(isoWeek, {
          unit: 'week'
        });
      }
      return addLeadingZeros(isoWeek, token.length);
    },
    // Day of the month
    d: function d(date, token, localize) {
      if (token === 'do') {
        return localize.ordinalNumber(date.getUTCDate(), {
          unit: 'date'
        });
      }
      return formatters$3.d(date, token);
    },
    // Day of year
    D: function D(date, token, localize) {
      var dayOfYear = getUTCDayOfYear(date);
      if (token === 'Do') {
        return localize.ordinalNumber(dayOfYear, {
          unit: 'dayOfYear'
        });
      }
      return addLeadingZeros(dayOfYear, token.length);
    },
    // Day of week
    E: function E(date, token, localize) {
      var dayOfWeek = date.getUTCDay();
      switch (token) {
        // Tue
        case 'E':
        case 'EE':
        case 'EEE':
          return localize.day(dayOfWeek, {
            width: 'abbreviated',
            context: 'formatting'
          });
        // T
        case 'EEEEE':
          return localize.day(dayOfWeek, {
            width: 'narrow',
            context: 'formatting'
          });
        // Tu
        case 'EEEEEE':
          return localize.day(dayOfWeek, {
            width: 'short',
            context: 'formatting'
          });
        // Tuesday
        case 'EEEE':
        default:
          return localize.day(dayOfWeek, {
            width: 'wide',
            context: 'formatting'
          });
      }
    },
    // Local day of week
    e: function e(date, token, localize, options) {
      var dayOfWeek = date.getUTCDay();
      var localDayOfWeek = (dayOfWeek - options.weekStartsOn + 8) % 7 || 7;
      switch (token) {
        // Numerical value (Nth day of week with current locale or weekStartsOn)
        case 'e':
          return String(localDayOfWeek);
        // Padded numerical value
        case 'ee':
          return addLeadingZeros(localDayOfWeek, 2);
        // 1st, 2nd, ..., 7th
        case 'eo':
          return localize.ordinalNumber(localDayOfWeek, {
            unit: 'day'
          });
        case 'eee':
          return localize.day(dayOfWeek, {
            width: 'abbreviated',
            context: 'formatting'
          });
        // T
        case 'eeeee':
          return localize.day(dayOfWeek, {
            width: 'narrow',
            context: 'formatting'
          });
        // Tu
        case 'eeeeee':
          return localize.day(dayOfWeek, {
            width: 'short',
            context: 'formatting'
          });
        // Tuesday
        case 'eeee':
        default:
          return localize.day(dayOfWeek, {
            width: 'wide',
            context: 'formatting'
          });
      }
    },
    // Stand-alone local day of week
    c: function c(date, token, localize, options) {
      var dayOfWeek = date.getUTCDay();
      var localDayOfWeek = (dayOfWeek - options.weekStartsOn + 8) % 7 || 7;
      switch (token) {
        // Numerical value (same as in `e`)
        case 'c':
          return String(localDayOfWeek);
        // Padded numerical value
        case 'cc':
          return addLeadingZeros(localDayOfWeek, token.length);
        // 1st, 2nd, ..., 7th
        case 'co':
          return localize.ordinalNumber(localDayOfWeek, {
            unit: 'day'
          });
        case 'ccc':
          return localize.day(dayOfWeek, {
            width: 'abbreviated',
            context: 'standalone'
          });
        // T
        case 'ccccc':
          return localize.day(dayOfWeek, {
            width: 'narrow',
            context: 'standalone'
          });
        // Tu
        case 'cccccc':
          return localize.day(dayOfWeek, {
            width: 'short',
            context: 'standalone'
          });
        // Tuesday
        case 'cccc':
        default:
          return localize.day(dayOfWeek, {
            width: 'wide',
            context: 'standalone'
          });
      }
    },
    // ISO day of week
    i: function i(date, token, localize) {
      var dayOfWeek = date.getUTCDay();
      var isoDayOfWeek = dayOfWeek === 0 ? 7 : dayOfWeek;
      switch (token) {
        // 2
        case 'i':
          return String(isoDayOfWeek);
        // 02
        case 'ii':
          return addLeadingZeros(isoDayOfWeek, token.length);
        // 2nd
        case 'io':
          return localize.ordinalNumber(isoDayOfWeek, {
            unit: 'day'
          });
        // Tue
        case 'iii':
          return localize.day(dayOfWeek, {
            width: 'abbreviated',
            context: 'formatting'
          });
        // T
        case 'iiiii':
          return localize.day(dayOfWeek, {
            width: 'narrow',
            context: 'formatting'
          });
        // Tu
        case 'iiiiii':
          return localize.day(dayOfWeek, {
            width: 'short',
            context: 'formatting'
          });
        // Tuesday
        case 'iiii':
        default:
          return localize.day(dayOfWeek, {
            width: 'wide',
            context: 'formatting'
          });
      }
    },
    // AM or PM
    a: function a(date, token, localize) {
      var hours = date.getUTCHours();
      var dayPeriodEnumValue = hours / 12 >= 1 ? 'pm' : 'am';
      switch (token) {
        case 'a':
        case 'aa':
          return localize.dayPeriod(dayPeriodEnumValue, {
            width: 'abbreviated',
            context: 'formatting'
          });
        case 'aaa':
          return localize.dayPeriod(dayPeriodEnumValue, {
            width: 'abbreviated',
            context: 'formatting'
          }).toLowerCase();
        case 'aaaaa':
          return localize.dayPeriod(dayPeriodEnumValue, {
            width: 'narrow',
            context: 'formatting'
          });
        case 'aaaa':
        default:
          return localize.dayPeriod(dayPeriodEnumValue, {
            width: 'wide',
            context: 'formatting'
          });
      }
    },
    // AM, PM, midnight, noon
    b: function b(date, token, localize) {
      var hours = date.getUTCHours();
      var dayPeriodEnumValue;
      if (hours === 12) {
        dayPeriodEnumValue = dayPeriodEnum.noon;
      } else if (hours === 0) {
        dayPeriodEnumValue = dayPeriodEnum.midnight;
      } else {
        dayPeriodEnumValue = hours / 12 >= 1 ? 'pm' : 'am';
      }
      switch (token) {
        case 'b':
        case 'bb':
          return localize.dayPeriod(dayPeriodEnumValue, {
            width: 'abbreviated',
            context: 'formatting'
          });
        case 'bbb':
          return localize.dayPeriod(dayPeriodEnumValue, {
            width: 'abbreviated',
            context: 'formatting'
          }).toLowerCase();
        case 'bbbbb':
          return localize.dayPeriod(dayPeriodEnumValue, {
            width: 'narrow',
            context: 'formatting'
          });
        case 'bbbb':
        default:
          return localize.dayPeriod(dayPeriodEnumValue, {
            width: 'wide',
            context: 'formatting'
          });
      }
    },
    // in the morning, in the afternoon, in the evening, at night
    B: function B(date, token, localize) {
      var hours = date.getUTCHours();
      var dayPeriodEnumValue;
      if (hours >= 17) {
        dayPeriodEnumValue = dayPeriodEnum.evening;
      } else if (hours >= 12) {
        dayPeriodEnumValue = dayPeriodEnum.afternoon;
      } else if (hours >= 4) {
        dayPeriodEnumValue = dayPeriodEnum.morning;
      } else {
        dayPeriodEnumValue = dayPeriodEnum.night;
      }
      switch (token) {
        case 'B':
        case 'BB':
        case 'BBB':
          return localize.dayPeriod(dayPeriodEnumValue, {
            width: 'abbreviated',
            context: 'formatting'
          });
        case 'BBBBB':
          return localize.dayPeriod(dayPeriodEnumValue, {
            width: 'narrow',
            context: 'formatting'
          });
        case 'BBBB':
        default:
          return localize.dayPeriod(dayPeriodEnumValue, {
            width: 'wide',
            context: 'formatting'
          });
      }
    },
    // Hour [1-12]
    h: function h(date, token, localize) {
      if (token === 'ho') {
        var hours = date.getUTCHours() % 12;
        if (hours === 0) hours = 12;
        return localize.ordinalNumber(hours, {
          unit: 'hour'
        });
      }
      return formatters$3.h(date, token);
    },
    // Hour [0-23]
    H: function H(date, token, localize) {
      if (token === 'Ho') {
        return localize.ordinalNumber(date.getUTCHours(), {
          unit: 'hour'
        });
      }
      return formatters$3.H(date, token);
    },
    // Hour [0-11]
    K: function K(date, token, localize) {
      var hours = date.getUTCHours() % 12;
      if (token === 'Ko') {
        return localize.ordinalNumber(hours, {
          unit: 'hour'
        });
      }
      return addLeadingZeros(hours, token.length);
    },
    // Hour [1-24]
    k: function k(date, token, localize) {
      var hours = date.getUTCHours();
      if (hours === 0) hours = 24;
      if (token === 'ko') {
        return localize.ordinalNumber(hours, {
          unit: 'hour'
        });
      }
      return addLeadingZeros(hours, token.length);
    },
    // Minute
    m: function m(date, token, localize) {
      if (token === 'mo') {
        return localize.ordinalNumber(date.getUTCMinutes(), {
          unit: 'minute'
        });
      }
      return formatters$3.m(date, token);
    },
    // Second
    s: function s(date, token, localize) {
      if (token === 'so') {
        return localize.ordinalNumber(date.getUTCSeconds(), {
          unit: 'second'
        });
      }
      return formatters$3.s(date, token);
    },
    // Fraction of second
    S: function S(date, token) {
      return formatters$3.S(date, token);
    },
    // Timezone (ISO-8601. If offset is 0, output is always `'Z'`)
    X: function X(date, token, _localize, options) {
      var originalDate = options._originalDate || date;
      var timezoneOffset = originalDate.getTimezoneOffset();
      if (timezoneOffset === 0) {
        return 'Z';
      }
      switch (token) {
        // Hours and optional minutes
        case 'X':
          return formatTimezoneWithOptionalMinutes(timezoneOffset);

        // Hours, minutes and optional seconds without `:` delimiter
        // Note: neither ISO-8601 nor JavaScript supports seconds in timezone offsets
        // so this token always has the same output as `XX`
        case 'XXXX':
        case 'XX':
          // Hours and minutes without `:` delimiter
          return formatTimezone(timezoneOffset);

        // Hours, minutes and optional seconds with `:` delimiter
        // Note: neither ISO-8601 nor JavaScript supports seconds in timezone offsets
        // so this token always has the same output as `XXX`
        case 'XXXXX':
        case 'XXX': // Hours and minutes with `:` delimiter
        default:
          return formatTimezone(timezoneOffset, ':');
      }
    },
    // Timezone (ISO-8601. If offset is 0, output is `'+00:00'` or equivalent)
    x: function x(date, token, _localize, options) {
      var originalDate = options._originalDate || date;
      var timezoneOffset = originalDate.getTimezoneOffset();
      switch (token) {
        // Hours and optional minutes
        case 'x':
          return formatTimezoneWithOptionalMinutes(timezoneOffset);

        // Hours, minutes and optional seconds without `:` delimiter
        // Note: neither ISO-8601 nor JavaScript supports seconds in timezone offsets
        // so this token always has the same output as `xx`
        case 'xxxx':
        case 'xx':
          // Hours and minutes without `:` delimiter
          return formatTimezone(timezoneOffset);

        // Hours, minutes and optional seconds with `:` delimiter
        // Note: neither ISO-8601 nor JavaScript supports seconds in timezone offsets
        // so this token always has the same output as `xxx`
        case 'xxxxx':
        case 'xxx': // Hours and minutes with `:` delimiter
        default:
          return formatTimezone(timezoneOffset, ':');
      }
    },
    // Timezone (GMT)
    O: function O(date, token, _localize, options) {
      var originalDate = options._originalDate || date;
      var timezoneOffset = originalDate.getTimezoneOffset();
      switch (token) {
        // Short
        case 'O':
        case 'OO':
        case 'OOO':
          return 'GMT' + formatTimezoneShort(timezoneOffset, ':');
        // Long
        case 'OOOO':
        default:
          return 'GMT' + formatTimezone(timezoneOffset, ':');
      }
    },
    // Timezone (specific non-location)
    z: function z(date, token, _localize, options) {
      var originalDate = options._originalDate || date;
      var timezoneOffset = originalDate.getTimezoneOffset();
      switch (token) {
        // Short
        case 'z':
        case 'zz':
        case 'zzz':
          return 'GMT' + formatTimezoneShort(timezoneOffset, ':');
        // Long
        case 'zzzz':
        default:
          return 'GMT' + formatTimezone(timezoneOffset, ':');
      }
    },
    // Seconds timestamp
    t: function t(date, token, _localize, options) {
      var originalDate = options._originalDate || date;
      var timestamp = Math.floor(originalDate.getTime() / 1000);
      return addLeadingZeros(timestamp, token.length);
    },
    // Milliseconds timestamp
    T: function T(date, token, _localize, options) {
      var originalDate = options._originalDate || date;
      var timestamp = originalDate.getTime();
      return addLeadingZeros(timestamp, token.length);
    }
  };
  function formatTimezoneShort(offset, dirtyDelimiter) {
    var sign = offset > 0 ? '-' : '+';
    var absOffset = Math.abs(offset);
    var hours = Math.floor(absOffset / 60);
    var minutes = absOffset % 60;
    if (minutes === 0) {
      return sign + String(hours);
    }
    var delimiter = dirtyDelimiter || '';
    return sign + String(hours) + delimiter + addLeadingZeros(minutes, 2);
  }
  function formatTimezoneWithOptionalMinutes(offset, dirtyDelimiter) {
    if (offset % 60 === 0) {
      var sign = offset > 0 ? '-' : '+';
      return sign + addLeadingZeros(Math.abs(offset) / 60, 2);
    }
    return formatTimezone(offset, dirtyDelimiter);
  }
  function formatTimezone(offset, dirtyDelimiter) {
    var delimiter = dirtyDelimiter || '';
    var sign = offset > 0 ? '-' : '+';
    var absOffset = Math.abs(offset);
    var hours = addLeadingZeros(Math.floor(absOffset / 60), 2);
    var minutes = addLeadingZeros(absOffset % 60, 2);
    return sign + hours + delimiter + minutes;
  }
  var formatters$1 = formatters;

  var dateLongFormatter = function dateLongFormatter(pattern, formatLong) {
    switch (pattern) {
      case 'P':
        return formatLong.date({
          width: 'short'
        });
      case 'PP':
        return formatLong.date({
          width: 'medium'
        });
      case 'PPP':
        return formatLong.date({
          width: 'long'
        });
      case 'PPPP':
      default:
        return formatLong.date({
          width: 'full'
        });
    }
  };
  var timeLongFormatter = function timeLongFormatter(pattern, formatLong) {
    switch (pattern) {
      case 'p':
        return formatLong.time({
          width: 'short'
        });
      case 'pp':
        return formatLong.time({
          width: 'medium'
        });
      case 'ppp':
        return formatLong.time({
          width: 'long'
        });
      case 'pppp':
      default:
        return formatLong.time({
          width: 'full'
        });
    }
  };
  var dateTimeLongFormatter = function dateTimeLongFormatter(pattern, formatLong) {
    var matchResult = pattern.match(/(P+)(p+)?/) || [];
    var datePattern = matchResult[1];
    var timePattern = matchResult[2];
    if (!timePattern) {
      return dateLongFormatter(pattern, formatLong);
    }
    var dateTimeFormat;
    switch (datePattern) {
      case 'P':
        dateTimeFormat = formatLong.dateTime({
          width: 'short'
        });
        break;
      case 'PP':
        dateTimeFormat = formatLong.dateTime({
          width: 'medium'
        });
        break;
      case 'PPP':
        dateTimeFormat = formatLong.dateTime({
          width: 'long'
        });
        break;
      case 'PPPP':
      default:
        dateTimeFormat = formatLong.dateTime({
          width: 'full'
        });
        break;
    }
    return dateTimeFormat.replace('{{date}}', dateLongFormatter(datePattern, formatLong)).replace('{{time}}', timeLongFormatter(timePattern, formatLong));
  };
  var longFormatters = {
    p: timeLongFormatter,
    P: dateTimeLongFormatter
  };
  var longFormatters$1 = longFormatters;

  var protectedDayOfYearTokens = ['D', 'DD'];
  var protectedWeekYearTokens = ['YY', 'YYYY'];
  function isProtectedDayOfYearToken(token) {
    return protectedDayOfYearTokens.indexOf(token) !== -1;
  }
  function isProtectedWeekYearToken(token) {
    return protectedWeekYearTokens.indexOf(token) !== -1;
  }
  function throwProtectedError(token, format, input) {
    if (token === 'YYYY') {
      throw new RangeError("Use `yyyy` instead of `YYYY` (in `".concat(format, "`) for formatting years to the input `").concat(input, "`; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md"));
    } else if (token === 'YY') {
      throw new RangeError("Use `yy` instead of `YY` (in `".concat(format, "`) for formatting years to the input `").concat(input, "`; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md"));
    } else if (token === 'D') {
      throw new RangeError("Use `d` instead of `D` (in `".concat(format, "`) for formatting days of the month to the input `").concat(input, "`; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md"));
    } else if (token === 'DD') {
      throw new RangeError("Use `dd` instead of `DD` (in `".concat(format, "`) for formatting days of the month to the input `").concat(input, "`; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md"));
    }
  }

  var formatDistanceLocale = {
    lessThanXSeconds: {
      one: 'less than a second',
      other: 'less than {{count}} seconds'
    },
    xSeconds: {
      one: '1 second',
      other: '{{count}} seconds'
    },
    halfAMinute: 'half a minute',
    lessThanXMinutes: {
      one: 'less than a minute',
      other: 'less than {{count}} minutes'
    },
    xMinutes: {
      one: '1 minute',
      other: '{{count}} minutes'
    },
    aboutXHours: {
      one: 'about 1 hour',
      other: 'about {{count}} hours'
    },
    xHours: {
      one: '1 hour',
      other: '{{count}} hours'
    },
    xDays: {
      one: '1 day',
      other: '{{count}} days'
    },
    aboutXWeeks: {
      one: 'about 1 week',
      other: 'about {{count}} weeks'
    },
    xWeeks: {
      one: '1 week',
      other: '{{count}} weeks'
    },
    aboutXMonths: {
      one: 'about 1 month',
      other: 'about {{count}} months'
    },
    xMonths: {
      one: '1 month',
      other: '{{count}} months'
    },
    aboutXYears: {
      one: 'about 1 year',
      other: 'about {{count}} years'
    },
    xYears: {
      one: '1 year',
      other: '{{count}} years'
    },
    overXYears: {
      one: 'over 1 year',
      other: 'over {{count}} years'
    },
    almostXYears: {
      one: 'almost 1 year',
      other: 'almost {{count}} years'
    }
  };
  var formatDistance = function formatDistance(token, count, options) {
    var result;
    var tokenValue = formatDistanceLocale[token];
    if (typeof tokenValue === 'string') {
      result = tokenValue;
    } else if (count === 1) {
      result = tokenValue.one;
    } else {
      result = tokenValue.other.replace('{{count}}', count.toString());
    }
    if (options !== null && options !== void 0 && options.addSuffix) {
      if (options.comparison && options.comparison > 0) {
        return 'in ' + result;
      } else {
        return result + ' ago';
      }
    }
    return result;
  };
  var formatDistance$1 = formatDistance;

  function buildFormatLongFn(args) {
    return function () {
      var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
      // TODO: Remove String()
      var width = options.width ? String(options.width) : args.defaultWidth;
      var format = args.formats[width] || args.formats[args.defaultWidth];
      return format;
    };
  }

  var dateFormats = {
    full: 'EEEE, MMMM do, y',
    "long": 'MMMM do, y',
    medium: 'MMM d, y',
    "short": 'MM/dd/yyyy'
  };
  var timeFormats = {
    full: 'h:mm:ss a zzzz',
    "long": 'h:mm:ss a z',
    medium: 'h:mm:ss a',
    "short": 'h:mm a'
  };
  var dateTimeFormats = {
    full: "{{date}} 'at' {{time}}",
    "long": "{{date}} 'at' {{time}}",
    medium: '{{date}}, {{time}}',
    "short": '{{date}}, {{time}}'
  };
  var formatLong = {
    date: buildFormatLongFn({
      formats: dateFormats,
      defaultWidth: 'full'
    }),
    time: buildFormatLongFn({
      formats: timeFormats,
      defaultWidth: 'full'
    }),
    dateTime: buildFormatLongFn({
      formats: dateTimeFormats,
      defaultWidth: 'full'
    })
  };
  var formatLong$1 = formatLong;

  var formatRelativeLocale = {
    lastWeek: "'last' eeee 'at' p",
    yesterday: "'yesterday at' p",
    today: "'today at' p",
    tomorrow: "'tomorrow at' p",
    nextWeek: "eeee 'at' p",
    other: 'P'
  };
  var formatRelative = function formatRelative(token, _date, _baseDate, _options) {
    return formatRelativeLocale[token];
  };
  var formatRelative$1 = formatRelative;

  function buildLocalizeFn(args) {
    return function (dirtyIndex, options) {
      var context = options !== null && options !== void 0 && options.context ? String(options.context) : 'standalone';
      var valuesArray;
      if (context === 'formatting' && args.formattingValues) {
        var defaultWidth = args.defaultFormattingWidth || args.defaultWidth;
        var width = options !== null && options !== void 0 && options.width ? String(options.width) : defaultWidth;
        valuesArray = args.formattingValues[width] || args.formattingValues[defaultWidth];
      } else {
        var _defaultWidth = args.defaultWidth;
        var _width = options !== null && options !== void 0 && options.width ? String(options.width) : args.defaultWidth;
        valuesArray = args.values[_width] || args.values[_defaultWidth];
      }
      var index = args.argumentCallback ? args.argumentCallback(dirtyIndex) : dirtyIndex;
      // @ts-ignore: For some reason TypeScript just don't want to match it, no matter how hard we try. I challenge you to try to remove it!
      return valuesArray[index];
    };
  }

  var eraValues = {
    narrow: ['B', 'A'],
    abbreviated: ['BC', 'AD'],
    wide: ['Before Christ', 'Anno Domini']
  };
  var quarterValues = {
    narrow: ['1', '2', '3', '4'],
    abbreviated: ['Q1', 'Q2', 'Q3', 'Q4'],
    wide: ['1st quarter', '2nd quarter', '3rd quarter', '4th quarter']
  };

  // Note: in English, the names of days of the week and months are capitalized.
  // If you are making a new locale based on this one, check if the same is true for the language you're working on.
  // Generally, formatted dates should look like they are in the middle of a sentence,
  // e.g. in Spanish language the weekdays and months should be in the lowercase.
  var monthValues = {
    narrow: ['J', 'F', 'M', 'A', 'M', 'J', 'J', 'A', 'S', 'O', 'N', 'D'],
    abbreviated: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
    wide: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
  };
  var dayValues = {
    narrow: ['S', 'M', 'T', 'W', 'T', 'F', 'S'],
    "short": ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'],
    abbreviated: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
    wide: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
  };
  var dayPeriodValues = {
    narrow: {
      am: 'a',
      pm: 'p',
      midnight: 'mi',
      noon: 'n',
      morning: 'morning',
      afternoon: 'afternoon',
      evening: 'evening',
      night: 'night'
    },
    abbreviated: {
      am: 'AM',
      pm: 'PM',
      midnight: 'midnight',
      noon: 'noon',
      morning: 'morning',
      afternoon: 'afternoon',
      evening: 'evening',
      night: 'night'
    },
    wide: {
      am: 'a.m.',
      pm: 'p.m.',
      midnight: 'midnight',
      noon: 'noon',
      morning: 'morning',
      afternoon: 'afternoon',
      evening: 'evening',
      night: 'night'
    }
  };
  var formattingDayPeriodValues = {
    narrow: {
      am: 'a',
      pm: 'p',
      midnight: 'mi',
      noon: 'n',
      morning: 'in the morning',
      afternoon: 'in the afternoon',
      evening: 'in the evening',
      night: 'at night'
    },
    abbreviated: {
      am: 'AM',
      pm: 'PM',
      midnight: 'midnight',
      noon: 'noon',
      morning: 'in the morning',
      afternoon: 'in the afternoon',
      evening: 'in the evening',
      night: 'at night'
    },
    wide: {
      am: 'a.m.',
      pm: 'p.m.',
      midnight: 'midnight',
      noon: 'noon',
      morning: 'in the morning',
      afternoon: 'in the afternoon',
      evening: 'in the evening',
      night: 'at night'
    }
  };
  var ordinalNumber = function ordinalNumber(dirtyNumber, _options) {
    var number = Number(dirtyNumber);

    // If ordinal numbers depend on context, for example,
    // if they are different for different grammatical genders,
    // use `options.unit`.
    //
    // `unit` can be 'year', 'quarter', 'month', 'week', 'date', 'dayOfYear',
    // 'day', 'hour', 'minute', 'second'.

    var rem100 = number % 100;
    if (rem100 > 20 || rem100 < 10) {
      switch (rem100 % 10) {
        case 1:
          return number + 'st';
        case 2:
          return number + 'nd';
        case 3:
          return number + 'rd';
      }
    }
    return number + 'th';
  };
  var localize = {
    ordinalNumber: ordinalNumber,
    era: buildLocalizeFn({
      values: eraValues,
      defaultWidth: 'wide'
    }),
    quarter: buildLocalizeFn({
      values: quarterValues,
      defaultWidth: 'wide',
      argumentCallback: function argumentCallback(quarter) {
        return quarter - 1;
      }
    }),
    month: buildLocalizeFn({
      values: monthValues,
      defaultWidth: 'wide'
    }),
    day: buildLocalizeFn({
      values: dayValues,
      defaultWidth: 'wide'
    }),
    dayPeriod: buildLocalizeFn({
      values: dayPeriodValues,
      defaultWidth: 'wide',
      formattingValues: formattingDayPeriodValues,
      defaultFormattingWidth: 'wide'
    })
  };
  var localize$1 = localize;

  function buildMatchFn(args) {
    return function (string) {
      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      var width = options.width;
      var matchPattern = width && args.matchPatterns[width] || args.matchPatterns[args.defaultMatchWidth];
      var matchResult = string.match(matchPattern);
      if (!matchResult) {
        return null;
      }
      var matchedString = matchResult[0];
      var parsePatterns = width && args.parsePatterns[width] || args.parsePatterns[args.defaultParseWidth];
      var key = Array.isArray(parsePatterns) ? findIndex(parsePatterns, function (pattern) {
        return pattern.test(matchedString);
      }) : findKey(parsePatterns, function (pattern) {
        return pattern.test(matchedString);
      });
      var value;
      value = args.valueCallback ? args.valueCallback(key) : key;
      value = options.valueCallback ? options.valueCallback(value) : value;
      var rest = string.slice(matchedString.length);
      return {
        value: value,
        rest: rest
      };
    };
  }
  function findKey(object, predicate) {
    for (var key in object) {
      if (object.hasOwnProperty(key) && predicate(object[key])) {
        return key;
      }
    }
    return undefined;
  }
  function findIndex(array, predicate) {
    for (var key = 0; key < array.length; key++) {
      if (predicate(array[key])) {
        return key;
      }
    }
    return undefined;
  }

  function buildMatchPatternFn(args) {
    return function (string) {
      var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
      var matchResult = string.match(args.matchPattern);
      if (!matchResult) return null;
      var matchedString = matchResult[0];
      var parseResult = string.match(args.parsePattern);
      if (!parseResult) return null;
      var value = args.valueCallback ? args.valueCallback(parseResult[0]) : parseResult[0];
      value = options.valueCallback ? options.valueCallback(value) : value;
      var rest = string.slice(matchedString.length);
      return {
        value: value,
        rest: rest
      };
    };
  }

  var matchOrdinalNumberPattern = /^(\d+)(th|st|nd|rd)?/i;
  var parseOrdinalNumberPattern = /\d+/i;
  var matchEraPatterns = {
    narrow: /^(b|a)/i,
    abbreviated: /^(b\.?\s?c\.?|b\.?\s?c\.?\s?e\.?|a\.?\s?d\.?|c\.?\s?e\.?)/i,
    wide: /^(before christ|before common era|anno domini|common era)/i
  };
  var parseEraPatterns = {
    any: [/^b/i, /^(a|c)/i]
  };
  var matchQuarterPatterns = {
    narrow: /^[1234]/i,
    abbreviated: /^q[1234]/i,
    wide: /^[1234](th|st|nd|rd)? quarter/i
  };
  var parseQuarterPatterns = {
    any: [/1/i, /2/i, /3/i, /4/i]
  };
  var matchMonthPatterns = {
    narrow: /^[jfmasond]/i,
    abbreviated: /^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i,
    wide: /^(january|february|march|april|may|june|july|august|september|october|november|december)/i
  };
  var parseMonthPatterns = {
    narrow: [/^j/i, /^f/i, /^m/i, /^a/i, /^m/i, /^j/i, /^j/i, /^a/i, /^s/i, /^o/i, /^n/i, /^d/i],
    any: [/^ja/i, /^f/i, /^mar/i, /^ap/i, /^may/i, /^jun/i, /^jul/i, /^au/i, /^s/i, /^o/i, /^n/i, /^d/i]
  };
  var matchDayPatterns = {
    narrow: /^[smtwf]/i,
    "short": /^(su|mo|tu|we|th|fr|sa)/i,
    abbreviated: /^(sun|mon|tue|wed|thu|fri|sat)/i,
    wide: /^(sunday|monday|tuesday|wednesday|thursday|friday|saturday)/i
  };
  var parseDayPatterns = {
    narrow: [/^s/i, /^m/i, /^t/i, /^w/i, /^t/i, /^f/i, /^s/i],
    any: [/^su/i, /^m/i, /^tu/i, /^w/i, /^th/i, /^f/i, /^sa/i]
  };
  var matchDayPeriodPatterns = {
    narrow: /^(a|p|mi|n|(in the|at) (morning|afternoon|evening|night))/i,
    any: /^([ap]\.?\s?m\.?|midnight|noon|(in the|at) (morning|afternoon|evening|night))/i
  };
  var parseDayPeriodPatterns = {
    any: {
      am: /^a/i,
      pm: /^p/i,
      midnight: /^mi/i,
      noon: /^no/i,
      morning: /morning/i,
      afternoon: /afternoon/i,
      evening: /evening/i,
      night: /night/i
    }
  };
  var match = {
    ordinalNumber: buildMatchPatternFn({
      matchPattern: matchOrdinalNumberPattern,
      parsePattern: parseOrdinalNumberPattern,
      valueCallback: function valueCallback(value) {
        return parseInt(value, 10);
      }
    }),
    era: buildMatchFn({
      matchPatterns: matchEraPatterns,
      defaultMatchWidth: 'wide',
      parsePatterns: parseEraPatterns,
      defaultParseWidth: 'any'
    }),
    quarter: buildMatchFn({
      matchPatterns: matchQuarterPatterns,
      defaultMatchWidth: 'wide',
      parsePatterns: parseQuarterPatterns,
      defaultParseWidth: 'any',
      valueCallback: function valueCallback(index) {
        return index + 1;
      }
    }),
    month: buildMatchFn({
      matchPatterns: matchMonthPatterns,
      defaultMatchWidth: 'wide',
      parsePatterns: parseMonthPatterns,
      defaultParseWidth: 'any'
    }),
    day: buildMatchFn({
      matchPatterns: matchDayPatterns,
      defaultMatchWidth: 'wide',
      parsePatterns: parseDayPatterns,
      defaultParseWidth: 'any'
    }),
    dayPeriod: buildMatchFn({
      matchPatterns: matchDayPeriodPatterns,
      defaultMatchWidth: 'any',
      parsePatterns: parseDayPeriodPatterns,
      defaultParseWidth: 'any'
    })
  };
  var match$1 = match;

  /**
   * @type {Locale}
   * @category Locales
   * @summary English locale (United States).
   * @language English
   * @iso-639-2 eng
   * @author Sasha Koss [@kossnocorp]{@link https://github.com/kossnocorp}
   * @author Lesha Koss [@leshakoss]{@link https://github.com/leshakoss}
   */
  var locale = {
    code: 'en-US',
    formatDistance: formatDistance$1,
    formatLong: formatLong$1,
    formatRelative: formatRelative$1,
    localize: localize$1,
    match: match$1,
    options: {
      weekStartsOn: 0 /* Sunday */,
      firstWeekContainsDate: 1
    }
  };
  var defaultLocale = locale;

  // - [yYQqMLwIdDecihHKkms]o matches any available ordinal number token
  //   (one of the certain letters followed by `o`)
  // - (\w)\1* matches any sequences of the same letter
  // - '' matches two quote characters in a row
  // - '(''|[^'])+('|$) matches anything surrounded by two quote characters ('),
  //   except a single quote symbol, which ends the sequence.
  //   Two quote characters do not end the sequence.
  //   If there is no matching single quote
  //   then the sequence will continue until the end of the string.
  // - . matches any single character unmatched by previous parts of the RegExps
  var formattingTokensRegExp$1 = /[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g;

  // This RegExp catches symbols escaped by quotes, and also
  // sequences of symbols P, p, and the combinations like `PPPPPPPppppp`
  var longFormattingTokensRegExp$1 = /P+p+|P+|p+|''|'(''|[^'])+('|$)|./g;
  var escapedStringRegExp$1 = /^'([^]*?)'?$/;
  var doubleQuoteRegExp$1 = /''/g;
  var unescapedLatinCharacterRegExp$1 = /[a-zA-Z]/;

  /**
   * @name format
   * @category Common Helpers
   * @summary Format the date.
   *
   * @description
   * Return the formatted date string in the given format. The result may vary by locale.
   *
   * > ⚠️ Please note that the `format` tokens differ from Moment.js and other libraries.
   * > See: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   *
   * The characters wrapped between two single quotes characters (') are escaped.
   * Two single quotes in a row, whether inside or outside a quoted sequence, represent a 'real' single quote.
   * (see the last example)
   *
   * Format of the string is based on Unicode Technical Standard #35:
   * https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
   * with a few additions (see note 7 below the table).
   *
   * Accepted patterns:
   * | Unit                            | Pattern | Result examples                   | Notes |
   * |---------------------------------|---------|-----------------------------------|-------|
   * | Era                             | G..GGG  | AD, BC                            |       |
   * |                                 | GGGG    | Anno Domini, Before Christ        | 2     |
   * |                                 | GGGGG   | A, B                              |       |
   * | Calendar year                   | y       | 44, 1, 1900, 2017                 | 5     |
   * |                                 | yo      | 44th, 1st, 0th, 17th              | 5,7   |
   * |                                 | yy      | 44, 01, 00, 17                    | 5     |
   * |                                 | yyy     | 044, 001, 1900, 2017              | 5     |
   * |                                 | yyyy    | 0044, 0001, 1900, 2017            | 5     |
   * |                                 | yyyyy   | ...                               | 3,5   |
   * | Local week-numbering year       | Y       | 44, 1, 1900, 2017                 | 5     |
   * |                                 | Yo      | 44th, 1st, 1900th, 2017th         | 5,7   |
   * |                                 | YY      | 44, 01, 00, 17                    | 5,8   |
   * |                                 | YYY     | 044, 001, 1900, 2017              | 5     |
   * |                                 | YYYY    | 0044, 0001, 1900, 2017            | 5,8   |
   * |                                 | YYYYY   | ...                               | 3,5   |
   * | ISO week-numbering year         | R       | -43, 0, 1, 1900, 2017             | 5,7   |
   * |                                 | RR      | -43, 00, 01, 1900, 2017           | 5,7   |
   * |                                 | RRR     | -043, 000, 001, 1900, 2017        | 5,7   |
   * |                                 | RRRR    | -0043, 0000, 0001, 1900, 2017     | 5,7   |
   * |                                 | RRRRR   | ...                               | 3,5,7 |
   * | Extended year                   | u       | -43, 0, 1, 1900, 2017             | 5     |
   * |                                 | uu      | -43, 01, 1900, 2017               | 5     |
   * |                                 | uuu     | -043, 001, 1900, 2017             | 5     |
   * |                                 | uuuu    | -0043, 0001, 1900, 2017           | 5     |
   * |                                 | uuuuu   | ...                               | 3,5   |
   * | Quarter (formatting)            | Q       | 1, 2, 3, 4                        |       |
   * |                                 | Qo      | 1st, 2nd, 3rd, 4th                | 7     |
   * |                                 | QQ      | 01, 02, 03, 04                    |       |
   * |                                 | QQQ     | Q1, Q2, Q3, Q4                    |       |
   * |                                 | QQQQ    | 1st quarter, 2nd quarter, ...     | 2     |
   * |                                 | QQQQQ   | 1, 2, 3, 4                        | 4     |
   * | Quarter (stand-alone)           | q       | 1, 2, 3, 4                        |       |
   * |                                 | qo      | 1st, 2nd, 3rd, 4th                | 7     |
   * |                                 | qq      | 01, 02, 03, 04                    |       |
   * |                                 | qqq     | Q1, Q2, Q3, Q4                    |       |
   * |                                 | qqqq    | 1st quarter, 2nd quarter, ...     | 2     |
   * |                                 | qqqqq   | 1, 2, 3, 4                        | 4     |
   * | Month (formatting)              | M       | 1, 2, ..., 12                     |       |
   * |                                 | Mo      | 1st, 2nd, ..., 12th               | 7     |
   * |                                 | MM      | 01, 02, ..., 12                   |       |
   * |                                 | MMM     | Jan, Feb, ..., Dec                |       |
   * |                                 | MMMM    | January, February, ..., December  | 2     |
   * |                                 | MMMMM   | J, F, ..., D                      |       |
   * | Month (stand-alone)             | L       | 1, 2, ..., 12                     |       |
   * |                                 | Lo      | 1st, 2nd, ..., 12th               | 7     |
   * |                                 | LL      | 01, 02, ..., 12                   |       |
   * |                                 | LLL     | Jan, Feb, ..., Dec                |       |
   * |                                 | LLLL    | January, February, ..., December  | 2     |
   * |                                 | LLLLL   | J, F, ..., D                      |       |
   * | Local week of year              | w       | 1, 2, ..., 53                     |       |
   * |                                 | wo      | 1st, 2nd, ..., 53th               | 7     |
   * |                                 | ww      | 01, 02, ..., 53                   |       |
   * | ISO week of year                | I       | 1, 2, ..., 53                     | 7     |
   * |                                 | Io      | 1st, 2nd, ..., 53th               | 7     |
   * |                                 | II      | 01, 02, ..., 53                   | 7     |
   * | Day of month                    | d       | 1, 2, ..., 31                     |       |
   * |                                 | do      | 1st, 2nd, ..., 31st               | 7     |
   * |                                 | dd      | 01, 02, ..., 31                   |       |
   * | Day of year                     | D       | 1, 2, ..., 365, 366               | 9     |
   * |                                 | Do      | 1st, 2nd, ..., 365th, 366th       | 7     |
   * |                                 | DD      | 01, 02, ..., 365, 366             | 9     |
   * |                                 | DDD     | 001, 002, ..., 365, 366           |       |
   * |                                 | DDDD    | ...                               | 3     |
   * | Day of week (formatting)        | E..EEE  | Mon, Tue, Wed, ..., Sun           |       |
   * |                                 | EEEE    | Monday, Tuesday, ..., Sunday      | 2     |
   * |                                 | EEEEE   | M, T, W, T, F, S, S               |       |
   * |                                 | EEEEEE  | Mo, Tu, We, Th, Fr, Sa, Su        |       |
   * | ISO day of week (formatting)    | i       | 1, 2, 3, ..., 7                   | 7     |
   * |                                 | io      | 1st, 2nd, ..., 7th                | 7     |
   * |                                 | ii      | 01, 02, ..., 07                   | 7     |
   * |                                 | iii     | Mon, Tue, Wed, ..., Sun           | 7     |
   * |                                 | iiii    | Monday, Tuesday, ..., Sunday      | 2,7   |
   * |                                 | iiiii   | M, T, W, T, F, S, S               | 7     |
   * |                                 | iiiiii  | Mo, Tu, We, Th, Fr, Sa, Su        | 7     |
   * | Local day of week (formatting)  | e       | 2, 3, 4, ..., 1                   |       |
   * |                                 | eo      | 2nd, 3rd, ..., 1st                | 7     |
   * |                                 | ee      | 02, 03, ..., 01                   |       |
   * |                                 | eee     | Mon, Tue, Wed, ..., Sun           |       |
   * |                                 | eeee    | Monday, Tuesday, ..., Sunday      | 2     |
   * |                                 | eeeee   | M, T, W, T, F, S, S               |       |
   * |                                 | eeeeee  | Mo, Tu, We, Th, Fr, Sa, Su        |       |
   * | Local day of week (stand-alone) | c       | 2, 3, 4, ..., 1                   |       |
   * |                                 | co      | 2nd, 3rd, ..., 1st                | 7     |
   * |                                 | cc      | 02, 03, ..., 01                   |       |
   * |                                 | ccc     | Mon, Tue, Wed, ..., Sun           |       |
   * |                                 | cccc    | Monday, Tuesday, ..., Sunday      | 2     |
   * |                                 | ccccc   | M, T, W, T, F, S, S               |       |
   * |                                 | cccccc  | Mo, Tu, We, Th, Fr, Sa, Su        |       |
   * | AM, PM                          | a..aa   | AM, PM                            |       |
   * |                                 | aaa     | am, pm                            |       |
   * |                                 | aaaa    | a.m., p.m.                        | 2     |
   * |                                 | aaaaa   | a, p                              |       |
   * | AM, PM, noon, midnight          | b..bb   | AM, PM, noon, midnight            |       |
   * |                                 | bbb     | am, pm, noon, midnight            |       |
   * |                                 | bbbb    | a.m., p.m., noon, midnight        | 2     |
   * |                                 | bbbbb   | a, p, n, mi                       |       |
   * | Flexible day period             | B..BBB  | at night, in the morning, ...     |       |
   * |                                 | BBBB    | at night, in the morning, ...     | 2     |
   * |                                 | BBBBB   | at night, in the morning, ...     |       |
   * | Hour [1-12]                     | h       | 1, 2, ..., 11, 12                 |       |
   * |                                 | ho      | 1st, 2nd, ..., 11th, 12th         | 7     |
   * |                                 | hh      | 01, 02, ..., 11, 12               |       |
   * | Hour [0-23]                     | H       | 0, 1, 2, ..., 23                  |       |
   * |                                 | Ho      | 0th, 1st, 2nd, ..., 23rd          | 7     |
   * |                                 | HH      | 00, 01, 02, ..., 23               |       |
   * | Hour [0-11]                     | K       | 1, 2, ..., 11, 0                  |       |
   * |                                 | Ko      | 1st, 2nd, ..., 11th, 0th          | 7     |
   * |                                 | KK      | 01, 02, ..., 11, 00               |       |
   * | Hour [1-24]                     | k       | 24, 1, 2, ..., 23                 |       |
   * |                                 | ko      | 24th, 1st, 2nd, ..., 23rd         | 7     |
   * |                                 | kk      | 24, 01, 02, ..., 23               |       |
   * | Minute                          | m       | 0, 1, ..., 59                     |       |
   * |                                 | mo      | 0th, 1st, ..., 59th               | 7     |
   * |                                 | mm      | 00, 01, ..., 59                   |       |
   * | Second                          | s       | 0, 1, ..., 59                     |       |
   * |                                 | so      | 0th, 1st, ..., 59th               | 7     |
   * |                                 | ss      | 00, 01, ..., 59                   |       |
   * | Fraction of second              | S       | 0, 1, ..., 9                      |       |
   * |                                 | SS      | 00, 01, ..., 99                   |       |
   * |                                 | SSS     | 000, 001, ..., 999                |       |
   * |                                 | SSSS    | ...                               | 3     |
   * | Timezone (ISO-8601 w/ Z)        | X       | -08, +0530, Z                     |       |
   * |                                 | XX      | -0800, +0530, Z                   |       |
   * |                                 | XXX     | -08:00, +05:30, Z                 |       |
   * |                                 | XXXX    | -0800, +0530, Z, +123456          | 2     |
   * |                                 | XXXXX   | -08:00, +05:30, Z, +12:34:56      |       |
   * | Timezone (ISO-8601 w/o Z)       | x       | -08, +0530, +00                   |       |
   * |                                 | xx      | -0800, +0530, +0000               |       |
   * |                                 | xxx     | -08:00, +05:30, +00:00            | 2     |
   * |                                 | xxxx    | -0800, +0530, +0000, +123456      |       |
   * |                                 | xxxxx   | -08:00, +05:30, +00:00, +12:34:56 |       |
   * | Timezone (GMT)                  | O...OOO | GMT-8, GMT+5:30, GMT+0            |       |
   * |                                 | OOOO    | GMT-08:00, GMT+05:30, GMT+00:00   | 2     |
   * | Timezone (specific non-locat.)  | z...zzz | GMT-8, GMT+5:30, GMT+0            | 6     |
   * |                                 | zzzz    | GMT-08:00, GMT+05:30, GMT+00:00   | 2,6   |
   * | Seconds timestamp               | t       | 512969520                         | 7     |
   * |                                 | tt      | ...                               | 3,7   |
   * | Milliseconds timestamp          | T       | 512969520900                      | 7     |
   * |                                 | TT      | ...                               | 3,7   |
   * | Long localized date             | P       | 04/29/1453                        | 7     |
   * |                                 | PP      | Apr 29, 1453                      | 7     |
   * |                                 | PPP     | April 29th, 1453                  | 7     |
   * |                                 | PPPP    | Friday, April 29th, 1453          | 2,7   |
   * | Long localized time             | p       | 12:00 AM                          | 7     |
   * |                                 | pp      | 12:00:00 AM                       | 7     |
   * |                                 | ppp     | 12:00:00 AM GMT+2                 | 7     |
   * |                                 | pppp    | 12:00:00 AM GMT+02:00             | 2,7   |
   * | Combination of date and time    | Pp      | 04/29/1453, 12:00 AM              | 7     |
   * |                                 | PPpp    | Apr 29, 1453, 12:00:00 AM         | 7     |
   * |                                 | PPPppp  | April 29th, 1453 at ...           | 7     |
   * |                                 | PPPPpppp| Friday, April 29th, 1453 at ...   | 2,7   |
   * Notes:
   * 1. "Formatting" units (e.g. formatting quarter) in the default en-US locale
   *    are the same as "stand-alone" units, but are different in some languages.
   *    "Formatting" units are declined according to the rules of the language
   *    in the context of a date. "Stand-alone" units are always nominative singular:
   *
   *    `format(new Date(2017, 10, 6), 'do LLLL', {locale: cs}) //=> '6. listopad'`
   *
   *    `format(new Date(2017, 10, 6), 'do MMMM', {locale: cs}) //=> '6. listopadu'`
   *
   * 2. Any sequence of the identical letters is a pattern, unless it is escaped by
   *    the single quote characters (see below).
   *    If the sequence is longer than listed in table (e.g. `EEEEEEEEEEE`)
   *    the output will be the same as default pattern for this unit, usually
   *    the longest one (in case of ISO weekdays, `EEEE`). Default patterns for units
   *    are marked with "2" in the last column of the table.
   *
   *    `format(new Date(2017, 10, 6), 'MMM') //=> 'Nov'`
   *
   *    `format(new Date(2017, 10, 6), 'MMMM') //=> 'November'`
   *
   *    `format(new Date(2017, 10, 6), 'MMMMM') //=> 'N'`
   *
   *    `format(new Date(2017, 10, 6), 'MMMMMM') //=> 'November'`
   *
   *    `format(new Date(2017, 10, 6), 'MMMMMMM') //=> 'November'`
   *
   * 3. Some patterns could be unlimited length (such as `yyyyyyyy`).
   *    The output will be padded with zeros to match the length of the pattern.
   *
   *    `format(new Date(2017, 10, 6), 'yyyyyyyy') //=> '00002017'`
   *
   * 4. `QQQQQ` and `qqqqq` could be not strictly numerical in some locales.
   *    These tokens represent the shortest form of the quarter.
   *
   * 5. The main difference between `y` and `u` patterns are B.C. years:
   *
   *    | Year | `y` | `u` |
   *    |------|-----|-----|
   *    | AC 1 |   1 |   1 |
   *    | BC 1 |   1 |   0 |
   *    | BC 2 |   2 |  -1 |
   *
   *    Also `yy` always returns the last two digits of a year,
   *    while `uu` pads single digit years to 2 characters and returns other years unchanged:
   *
   *    | Year | `yy` | `uu` |
   *    |------|------|------|
   *    | 1    |   01 |   01 |
   *    | 14   |   14 |   14 |
   *    | 376  |   76 |  376 |
   *    | 1453 |   53 | 1453 |
   *
   *    The same difference is true for local and ISO week-numbering years (`Y` and `R`),
   *    except local week-numbering years are dependent on `options.weekStartsOn`
   *    and `options.firstWeekContainsDate` (compare [getISOWeekYear]{@link https://date-fns.org/docs/getISOWeekYear}
   *    and [getWeekYear]{@link https://date-fns.org/docs/getWeekYear}).
   *
   * 6. Specific non-location timezones are currently unavailable in `date-fns`,
   *    so right now these tokens fall back to GMT timezones.
   *
   * 7. These patterns are not in the Unicode Technical Standard #35:
   *    - `i`: ISO day of week
   *    - `I`: ISO week of year
   *    - `R`: ISO week-numbering year
   *    - `t`: seconds timestamp
   *    - `T`: milliseconds timestamp
   *    - `o`: ordinal number modifier
   *    - `P`: long localized date
   *    - `p`: long localized time
   *
   * 8. `YY` and `YYYY` tokens represent week-numbering years but they are often confused with years.
   *    You should enable `options.useAdditionalWeekYearTokens` to use them. See: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   *
   * 9. `D` and `DD` tokens represent days of the year but they are often confused with days of the month.
   *    You should enable `options.useAdditionalDayOfYearTokens` to use them. See: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   *
   * @param {Date|Number} date - the original date
   * @param {String} format - the string of tokens
   * @param {Object} [options] - an object with options.
   * @param {Locale} [options.locale=defaultLocale] - the locale object. See [Locale]{@link https://date-fns.org/docs/Locale}
   * @param {0|1|2|3|4|5|6} [options.weekStartsOn=0] - the index of the first day of the week (0 - Sunday)
   * @param {Number} [options.firstWeekContainsDate=1] - the day of January, which is
   * @param {Boolean} [options.useAdditionalWeekYearTokens=false] - if true, allows usage of the week-numbering year tokens `YY` and `YYYY`;
   *   see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   * @param {Boolean} [options.useAdditionalDayOfYearTokens=false] - if true, allows usage of the day of year tokens `D` and `DD`;
   *   see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   * @returns {String} the formatted date string
   * @throws {TypeError} 2 arguments required
   * @throws {RangeError} `date` must not be Invalid Date
   * @throws {RangeError} `options.locale` must contain `localize` property
   * @throws {RangeError} `options.locale` must contain `formatLong` property
   * @throws {RangeError} `options.weekStartsOn` must be between 0 and 6
   * @throws {RangeError} `options.firstWeekContainsDate` must be between 1 and 7
   * @throws {RangeError} use `yyyy` instead of `YYYY` for formatting years using [format provided] to the input [input provided]; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   * @throws {RangeError} use `yy` instead of `YY` for formatting years using [format provided] to the input [input provided]; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   * @throws {RangeError} use `d` instead of `D` for formatting days of the month using [format provided] to the input [input provided]; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   * @throws {RangeError} use `dd` instead of `DD` for formatting days of the month using [format provided] to the input [input provided]; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   * @throws {RangeError} format string contains an unescaped latin alphabet character
   *
   * @example
   * // Represent 11 February 2014 in middle-endian format:
   * const result = format(new Date(2014, 1, 11), 'MM/dd/yyyy')
   * //=> '02/11/2014'
   *
   * @example
   * // Represent 2 July 2014 in Esperanto:
   * import { eoLocale } from 'date-fns/locale/eo'
   * const result = format(new Date(2014, 6, 2), "do 'de' MMMM yyyy", {
   *   locale: eoLocale
   * })
   * //=> '2-a de julio 2014'
   *
   * @example
   * // Escape string by single quote characters:
   * const result = format(new Date(2014, 6, 2, 15), "h 'o''clock'")
   * //=> "3 o'clock"
   */

  function format(dirtyDate, dirtyFormatStr, options) {
    var _ref, _options$locale, _ref2, _ref3, _ref4, _options$firstWeekCon, _options$locale2, _options$locale2$opti, _defaultOptions$local, _defaultOptions$local2, _ref5, _ref6, _ref7, _options$weekStartsOn, _options$locale3, _options$locale3$opti, _defaultOptions$local3, _defaultOptions$local4;
    requiredArgs(2, arguments);
    var formatStr = String(dirtyFormatStr);
    var defaultOptions = getDefaultOptions();
    var locale = (_ref = (_options$locale = options === null || options === void 0 ? void 0 : options.locale) !== null && _options$locale !== void 0 ? _options$locale : defaultOptions.locale) !== null && _ref !== void 0 ? _ref : defaultLocale;
    var firstWeekContainsDate = toInteger((_ref2 = (_ref3 = (_ref4 = (_options$firstWeekCon = options === null || options === void 0 ? void 0 : options.firstWeekContainsDate) !== null && _options$firstWeekCon !== void 0 ? _options$firstWeekCon : options === null || options === void 0 ? void 0 : (_options$locale2 = options.locale) === null || _options$locale2 === void 0 ? void 0 : (_options$locale2$opti = _options$locale2.options) === null || _options$locale2$opti === void 0 ? void 0 : _options$locale2$opti.firstWeekContainsDate) !== null && _ref4 !== void 0 ? _ref4 : defaultOptions.firstWeekContainsDate) !== null && _ref3 !== void 0 ? _ref3 : (_defaultOptions$local = defaultOptions.locale) === null || _defaultOptions$local === void 0 ? void 0 : (_defaultOptions$local2 = _defaultOptions$local.options) === null || _defaultOptions$local2 === void 0 ? void 0 : _defaultOptions$local2.firstWeekContainsDate) !== null && _ref2 !== void 0 ? _ref2 : 1);

    // Test if weekStartsOn is between 1 and 7 _and_ is not NaN
    if (!(firstWeekContainsDate >= 1 && firstWeekContainsDate <= 7)) {
      throw new RangeError('firstWeekContainsDate must be between 1 and 7 inclusively');
    }
    var weekStartsOn = toInteger((_ref5 = (_ref6 = (_ref7 = (_options$weekStartsOn = options === null || options === void 0 ? void 0 : options.weekStartsOn) !== null && _options$weekStartsOn !== void 0 ? _options$weekStartsOn : options === null || options === void 0 ? void 0 : (_options$locale3 = options.locale) === null || _options$locale3 === void 0 ? void 0 : (_options$locale3$opti = _options$locale3.options) === null || _options$locale3$opti === void 0 ? void 0 : _options$locale3$opti.weekStartsOn) !== null && _ref7 !== void 0 ? _ref7 : defaultOptions.weekStartsOn) !== null && _ref6 !== void 0 ? _ref6 : (_defaultOptions$local3 = defaultOptions.locale) === null || _defaultOptions$local3 === void 0 ? void 0 : (_defaultOptions$local4 = _defaultOptions$local3.options) === null || _defaultOptions$local4 === void 0 ? void 0 : _defaultOptions$local4.weekStartsOn) !== null && _ref5 !== void 0 ? _ref5 : 0);

    // Test if weekStartsOn is between 0 and 6 _and_ is not NaN
    if (!(weekStartsOn >= 0 && weekStartsOn <= 6)) {
      throw new RangeError('weekStartsOn must be between 0 and 6 inclusively');
    }
    if (!locale.localize) {
      throw new RangeError('locale must contain localize property');
    }
    if (!locale.formatLong) {
      throw new RangeError('locale must contain formatLong property');
    }
    var originalDate = toDate(dirtyDate);
    if (!isValid(originalDate)) {
      throw new RangeError('Invalid time value');
    }

    // Convert the date in system timezone to the same date in UTC+00:00 timezone.
    // This ensures that when UTC functions will be implemented, locales will be compatible with them.
    // See an issue about UTC functions: https://github.com/date-fns/date-fns/issues/376
    var timezoneOffset = getTimezoneOffsetInMilliseconds(originalDate);
    var utcDate = subMilliseconds(originalDate, timezoneOffset);
    var formatterOptions = {
      firstWeekContainsDate: firstWeekContainsDate,
      weekStartsOn: weekStartsOn,
      locale: locale,
      _originalDate: originalDate
    };
    var result = formatStr.match(longFormattingTokensRegExp$1).map(function (substring) {
      var firstCharacter = substring[0];
      if (firstCharacter === 'p' || firstCharacter === 'P') {
        var longFormatter = longFormatters$1[firstCharacter];
        return longFormatter(substring, locale.formatLong);
      }
      return substring;
    }).join('').match(formattingTokensRegExp$1).map(function (substring) {
      // Replace two single quote characters with one single quote character
      if (substring === "''") {
        return "'";
      }
      var firstCharacter = substring[0];
      if (firstCharacter === "'") {
        return cleanEscapedString$1(substring);
      }
      var formatter = formatters$1[firstCharacter];
      if (formatter) {
        if (!(options !== null && options !== void 0 && options.useAdditionalWeekYearTokens) && isProtectedWeekYearToken(substring)) {
          throwProtectedError(substring, dirtyFormatStr, String(dirtyDate));
        }
        if (!(options !== null && options !== void 0 && options.useAdditionalDayOfYearTokens) && isProtectedDayOfYearToken(substring)) {
          throwProtectedError(substring, dirtyFormatStr, String(dirtyDate));
        }
        return formatter(utcDate, substring, locale.localize, formatterOptions);
      }
      if (firstCharacter.match(unescapedLatinCharacterRegExp$1)) {
        throw new RangeError('Format string contains an unescaped latin alphabet character `' + firstCharacter + '`');
      }
      return substring;
    }).join('');
    return result;
  }
  function cleanEscapedString$1(input) {
    var matched = input.match(escapedStringRegExp$1);
    if (!matched) {
      return input;
    }
    return matched[1].replace(doubleQuoteRegExp$1, "'");
  }

  function assign(target, object) {
    if (target == null) {
      throw new TypeError('assign requires that input parameter not be null or undefined');
    }
    for (var property in object) {
      if (Object.prototype.hasOwnProperty.call(object, property)) {
        target[property] = object[property];
      }
    }
    return target;
  }

  function _arrayLikeToArray(arr, len) {
    if (len == null || len > arr.length) len = arr.length;
    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
    return arr2;
  }

  function _unsupportedIterableToArray(o, minLen) {
    if (!o) return;
    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
    var n = Object.prototype.toString.call(o).slice(8, -1);
    if (n === "Object" && o.constructor) n = o.constructor.name;
    if (n === "Map" || n === "Set") return Array.from(o);
    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
  }

  function _createForOfIteratorHelper(o, allowArrayLike) {
    var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
    if (!it) {
      if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
        if (it) o = it;
        var i = 0;
        var F = function F() {};
        return {
          s: F,
          n: function n() {
            if (i >= o.length) return {
              done: true
            };
            return {
              done: false,
              value: o[i++]
            };
          },
          e: function e(_e) {
            throw _e;
          },
          f: F
        };
      }
      throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
    }
    var normalCompletion = true,
      didErr = false,
      err;
    return {
      s: function s() {
        it = it.call(o);
      },
      n: function n() {
        var step = it.next();
        normalCompletion = step.done;
        return step;
      },
      e: function e(_e2) {
        didErr = true;
        err = _e2;
      },
      f: function f() {
        try {
          if (!normalCompletion && it["return"] != null) it["return"]();
        } finally {
          if (didErr) throw err;
        }
      }
    };
  }

  function _assertThisInitialized(self) {
    if (self === void 0) {
      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }
    return self;
  }

  function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };
    return _setPrototypeOf(o, p);
  }

  function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
      throw new TypeError("Super expression must either be null or a function");
    }
    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass,
        writable: true,
        configurable: true
      }
    });
    Object.defineProperty(subClass, "prototype", {
      writable: false
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
  }

  function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) {
      return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf(o);
  }

  function _isNativeReflectConstruct() {
    try {
      var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
    } catch (t) {}
    return (_isNativeReflectConstruct = function _isNativeReflectConstruct() {
      return !!t;
    })();
  }

  function _possibleConstructorReturn(self, call) {
    if (call && (_typeof(call) === "object" || typeof call === "function")) {
      return call;
    } else if (call !== void 0) {
      throw new TypeError("Derived constructors may only return object or undefined");
    }
    return _assertThisInitialized(self);
  }

  function _createSuper(Derived) {
    var hasNativeReflectConstruct = _isNativeReflectConstruct();
    return function _createSuperInternal() {
      var Super = _getPrototypeOf(Derived),
        result;
      if (hasNativeReflectConstruct) {
        var NewTarget = _getPrototypeOf(this).constructor;
        result = Reflect.construct(Super, arguments, NewTarget);
      } else {
        result = Super.apply(this, arguments);
      }
      return _possibleConstructorReturn(this, result);
    };
  }

  function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  }

  function toPrimitive(t, r) {
    if ("object" != _typeof(t) || !t) return t;
    var e = t[Symbol.toPrimitive];
    if (void 0 !== e) {
      var i = e.call(t, r || "default");
      if ("object" != _typeof(i)) return i;
      throw new TypeError("@@toPrimitive must return a primitive value.");
    }
    return ("string" === r ? String : Number)(t);
  }

  function toPropertyKey(t) {
    var i = toPrimitive(t, "string");
    return "symbol" == _typeof(i) ? i : i + "";
  }

  function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, toPropertyKey(descriptor.key), descriptor);
    }
  }
  function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    Object.defineProperty(Constructor, "prototype", {
      writable: false
    });
    return Constructor;
  }

  function _defineProperty(obj, key, value) {
    key = toPropertyKey(key);
    if (key in obj) {
      Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
      });
    } else {
      obj[key] = value;
    }
    return obj;
  }

  var TIMEZONE_UNIT_PRIORITY = 10;
  var Setter = /*#__PURE__*/function () {
    function Setter() {
      _classCallCheck(this, Setter);
      _defineProperty(this, "priority", void 0);
      _defineProperty(this, "subPriority", 0);
    }
    _createClass(Setter, [{
      key: "validate",
      value: function validate(_utcDate, _options) {
        return true;
      }
    }]);
    return Setter;
  }();
  var ValueSetter = /*#__PURE__*/function (_Setter) {
    _inherits(ValueSetter, _Setter);
    var _super = _createSuper(ValueSetter);
    function ValueSetter(value, validateValue, setValue, priority, subPriority) {
      var _this;
      _classCallCheck(this, ValueSetter);
      _this = _super.call(this);
      _this.value = value;
      _this.validateValue = validateValue;
      _this.setValue = setValue;
      _this.priority = priority;
      if (subPriority) {
        _this.subPriority = subPriority;
      }
      return _this;
    }
    _createClass(ValueSetter, [{
      key: "validate",
      value: function validate(utcDate, options) {
        return this.validateValue(utcDate, this.value, options);
      }
    }, {
      key: "set",
      value: function set(utcDate, flags, options) {
        return this.setValue(utcDate, flags, this.value, options);
      }
    }]);
    return ValueSetter;
  }(Setter);
  var DateToSystemTimezoneSetter = /*#__PURE__*/function (_Setter2) {
    _inherits(DateToSystemTimezoneSetter, _Setter2);
    var _super2 = _createSuper(DateToSystemTimezoneSetter);
    function DateToSystemTimezoneSetter() {
      var _this2;
      _classCallCheck(this, DateToSystemTimezoneSetter);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this2 = _super2.call.apply(_super2, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this2), "priority", TIMEZONE_UNIT_PRIORITY);
      _defineProperty(_assertThisInitialized(_this2), "subPriority", -1);
      return _this2;
    }
    _createClass(DateToSystemTimezoneSetter, [{
      key: "set",
      value: function set(date, flags) {
        if (flags.timestampIsSet) {
          return date;
        }
        var convertedDate = new Date(0);
        convertedDate.setFullYear(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate());
        convertedDate.setHours(date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds());
        return convertedDate;
      }
    }]);
    return DateToSystemTimezoneSetter;
  }(Setter);

  var Parser = /*#__PURE__*/function () {
    function Parser() {
      _classCallCheck(this, Parser);
      _defineProperty(this, "incompatibleTokens", void 0);
      _defineProperty(this, "priority", void 0);
      _defineProperty(this, "subPriority", void 0);
    }
    _createClass(Parser, [{
      key: "run",
      value: function run(dateString, token, match, options) {
        var result = this.parse(dateString, token, match, options);
        if (!result) {
          return null;
        }
        return {
          setter: new ValueSetter(result.value, this.validate, this.set, this.priority, this.subPriority),
          rest: result.rest
        };
      }
    }, {
      key: "validate",
      value: function validate(_utcDate, _value, _options) {
        return true;
      }
    }]);
    return Parser;
  }();

  var EraParser = /*#__PURE__*/function (_Parser) {
    _inherits(EraParser, _Parser);
    var _super = _createSuper(EraParser);
    function EraParser() {
      var _this;
      _classCallCheck(this, EraParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 140);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['R', 'u', 't', 'T']);
      return _this;
    }
    _createClass(EraParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          // AD, BC
          case 'G':
          case 'GG':
          case 'GGG':
            return match.era(dateString, {
              width: 'abbreviated'
            }) || match.era(dateString, {
              width: 'narrow'
            });
          // A, B
          case 'GGGGG':
            return match.era(dateString, {
              width: 'narrow'
            });
          // Anno Domini, Before Christ
          case 'GGGG':
          default:
            return match.era(dateString, {
              width: 'wide'
            }) || match.era(dateString, {
              width: 'abbreviated'
            }) || match.era(dateString, {
              width: 'narrow'
            });
        }
      }
    }, {
      key: "set",
      value: function set(date, flags, value) {
        flags.era = value;
        date.setUTCFullYear(value, 0, 1);
        date.setUTCHours(0, 0, 0, 0);
        return date;
      }
    }]);
    return EraParser;
  }(Parser);

  var numericPatterns = {
    month: /^(1[0-2]|0?\d)/,
    // 0 to 12
    date: /^(3[0-1]|[0-2]?\d)/,
    // 0 to 31
    dayOfYear: /^(36[0-6]|3[0-5]\d|[0-2]?\d?\d)/,
    // 0 to 366
    week: /^(5[0-3]|[0-4]?\d)/,
    // 0 to 53
    hour23h: /^(2[0-3]|[0-1]?\d)/,
    // 0 to 23
    hour24h: /^(2[0-4]|[0-1]?\d)/,
    // 0 to 24
    hour11h: /^(1[0-1]|0?\d)/,
    // 0 to 11
    hour12h: /^(1[0-2]|0?\d)/,
    // 0 to 12
    minute: /^[0-5]?\d/,
    // 0 to 59
    second: /^[0-5]?\d/,
    // 0 to 59

    singleDigit: /^\d/,
    // 0 to 9
    twoDigits: /^\d{1,2}/,
    // 0 to 99
    threeDigits: /^\d{1,3}/,
    // 0 to 999
    fourDigits: /^\d{1,4}/,
    // 0 to 9999

    anyDigitsSigned: /^-?\d+/,
    singleDigitSigned: /^-?\d/,
    // 0 to 9, -0 to -9
    twoDigitsSigned: /^-?\d{1,2}/,
    // 0 to 99, -0 to -99
    threeDigitsSigned: /^-?\d{1,3}/,
    // 0 to 999, -0 to -999
    fourDigitsSigned: /^-?\d{1,4}/ // 0 to 9999, -0 to -9999
  };
  var timezonePatterns = {
    basicOptionalMinutes: /^([+-])(\d{2})(\d{2})?|Z/,
    basic: /^([+-])(\d{2})(\d{2})|Z/,
    basicOptionalSeconds: /^([+-])(\d{2})(\d{2})((\d{2}))?|Z/,
    extended: /^([+-])(\d{2}):(\d{2})|Z/,
    extendedOptionalSeconds: /^([+-])(\d{2}):(\d{2})(:(\d{2}))?|Z/
  };

  function mapValue(parseFnResult, mapFn) {
    if (!parseFnResult) {
      return parseFnResult;
    }
    return {
      value: mapFn(parseFnResult.value),
      rest: parseFnResult.rest
    };
  }
  function parseNumericPattern(pattern, dateString) {
    var matchResult = dateString.match(pattern);
    if (!matchResult) {
      return null;
    }
    return {
      value: parseInt(matchResult[0], 10),
      rest: dateString.slice(matchResult[0].length)
    };
  }
  function parseTimezonePattern(pattern, dateString) {
    var matchResult = dateString.match(pattern);
    if (!matchResult) {
      return null;
    }

    // Input is 'Z'
    if (matchResult[0] === 'Z') {
      return {
        value: 0,
        rest: dateString.slice(1)
      };
    }
    var sign = matchResult[1] === '+' ? 1 : -1;
    var hours = matchResult[2] ? parseInt(matchResult[2], 10) : 0;
    var minutes = matchResult[3] ? parseInt(matchResult[3], 10) : 0;
    var seconds = matchResult[5] ? parseInt(matchResult[5], 10) : 0;
    return {
      value: sign * (hours * millisecondsInHour + minutes * millisecondsInMinute + seconds * millisecondsInSecond),
      rest: dateString.slice(matchResult[0].length)
    };
  }
  function parseAnyDigitsSigned(dateString) {
    return parseNumericPattern(numericPatterns.anyDigitsSigned, dateString);
  }
  function parseNDigits(n, dateString) {
    switch (n) {
      case 1:
        return parseNumericPattern(numericPatterns.singleDigit, dateString);
      case 2:
        return parseNumericPattern(numericPatterns.twoDigits, dateString);
      case 3:
        return parseNumericPattern(numericPatterns.threeDigits, dateString);
      case 4:
        return parseNumericPattern(numericPatterns.fourDigits, dateString);
      default:
        return parseNumericPattern(new RegExp('^\\d{1,' + n + '}'), dateString);
    }
  }
  function parseNDigitsSigned(n, dateString) {
    switch (n) {
      case 1:
        return parseNumericPattern(numericPatterns.singleDigitSigned, dateString);
      case 2:
        return parseNumericPattern(numericPatterns.twoDigitsSigned, dateString);
      case 3:
        return parseNumericPattern(numericPatterns.threeDigitsSigned, dateString);
      case 4:
        return parseNumericPattern(numericPatterns.fourDigitsSigned, dateString);
      default:
        return parseNumericPattern(new RegExp('^-?\\d{1,' + n + '}'), dateString);
    }
  }
  function dayPeriodEnumToHours(dayPeriod) {
    switch (dayPeriod) {
      case 'morning':
        return 4;
      case 'evening':
        return 17;
      case 'pm':
      case 'noon':
      case 'afternoon':
        return 12;
      case 'am':
      case 'midnight':
      case 'night':
      default:
        return 0;
    }
  }
  function normalizeTwoDigitYear(twoDigitYear, currentYear) {
    var isCommonEra = currentYear > 0;
    // Absolute number of the current year:
    // 1 -> 1 AC
    // 0 -> 1 BC
    // -1 -> 2 BC
    var absCurrentYear = isCommonEra ? currentYear : 1 - currentYear;
    var result;
    if (absCurrentYear <= 50) {
      result = twoDigitYear || 100;
    } else {
      var rangeEnd = absCurrentYear + 50;
      var rangeEndCentury = Math.floor(rangeEnd / 100) * 100;
      var isPreviousCentury = twoDigitYear >= rangeEnd % 100;
      result = twoDigitYear + rangeEndCentury - (isPreviousCentury ? 100 : 0);
    }
    return isCommonEra ? result : 1 - result;
  }
  function isLeapYearIndex$1(year) {
    return year % 400 === 0 || year % 4 === 0 && year % 100 !== 0;
  }

  // From http://www.unicode.org/reports/tr35/tr35-31/tr35-dates.html#Date_Format_Patterns
  // | Year     |     y | yy |   yyy |  yyyy | yyyyy |
  // |----------|-------|----|-------|-------|-------|
  // | AD 1     |     1 | 01 |   001 |  0001 | 00001 |
  // | AD 12    |    12 | 12 |   012 |  0012 | 00012 |
  // | AD 123   |   123 | 23 |   123 |  0123 | 00123 |
  // | AD 1234  |  1234 | 34 |  1234 |  1234 | 01234 |
  // | AD 12345 | 12345 | 45 | 12345 | 12345 | 12345 |
  var YearParser = /*#__PURE__*/function (_Parser) {
    _inherits(YearParser, _Parser);
    var _super = _createSuper(YearParser);
    function YearParser() {
      var _this;
      _classCallCheck(this, YearParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 130);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['Y', 'R', 'u', 'w', 'I', 'i', 'e', 'c', 't', 'T']);
      return _this;
    }
    _createClass(YearParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        var valueCallback = function valueCallback(year) {
          return {
            year: year,
            isTwoDigitYear: token === 'yy'
          };
        };
        switch (token) {
          case 'y':
            return mapValue(parseNDigits(4, dateString), valueCallback);
          case 'yo':
            return mapValue(match.ordinalNumber(dateString, {
              unit: 'year'
            }), valueCallback);
          default:
            return mapValue(parseNDigits(token.length, dateString), valueCallback);
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value.isTwoDigitYear || value.year > 0;
      }
    }, {
      key: "set",
      value: function set(date, flags, value) {
        var currentYear = date.getUTCFullYear();
        if (value.isTwoDigitYear) {
          var normalizedTwoDigitYear = normalizeTwoDigitYear(value.year, currentYear);
          date.setUTCFullYear(normalizedTwoDigitYear, 0, 1);
          date.setUTCHours(0, 0, 0, 0);
          return date;
        }
        var year = !('era' in flags) || flags.era === 1 ? value.year : 1 - value.year;
        date.setUTCFullYear(year, 0, 1);
        date.setUTCHours(0, 0, 0, 0);
        return date;
      }
    }]);
    return YearParser;
  }(Parser);

  // Local week-numbering year
  var LocalWeekYearParser = /*#__PURE__*/function (_Parser) {
    _inherits(LocalWeekYearParser, _Parser);
    var _super = _createSuper(LocalWeekYearParser);
    function LocalWeekYearParser() {
      var _this;
      _classCallCheck(this, LocalWeekYearParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 130);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['y', 'R', 'u', 'Q', 'q', 'M', 'L', 'I', 'd', 'D', 'i', 't', 'T']);
      return _this;
    }
    _createClass(LocalWeekYearParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        var valueCallback = function valueCallback(year) {
          return {
            year: year,
            isTwoDigitYear: token === 'YY'
          };
        };
        switch (token) {
          case 'Y':
            return mapValue(parseNDigits(4, dateString), valueCallback);
          case 'Yo':
            return mapValue(match.ordinalNumber(dateString, {
              unit: 'year'
            }), valueCallback);
          default:
            return mapValue(parseNDigits(token.length, dateString), valueCallback);
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value.isTwoDigitYear || value.year > 0;
      }
    }, {
      key: "set",
      value: function set(date, flags, value, options) {
        var currentYear = getUTCWeekYear(date, options);
        if (value.isTwoDigitYear) {
          var normalizedTwoDigitYear = normalizeTwoDigitYear(value.year, currentYear);
          date.setUTCFullYear(normalizedTwoDigitYear, 0, options.firstWeekContainsDate);
          date.setUTCHours(0, 0, 0, 0);
          return startOfUTCWeek(date, options);
        }
        var year = !('era' in flags) || flags.era === 1 ? value.year : 1 - value.year;
        date.setUTCFullYear(year, 0, options.firstWeekContainsDate);
        date.setUTCHours(0, 0, 0, 0);
        return startOfUTCWeek(date, options);
      }
    }]);
    return LocalWeekYearParser;
  }(Parser);

  var ISOWeekYearParser = /*#__PURE__*/function (_Parser) {
    _inherits(ISOWeekYearParser, _Parser);
    var _super = _createSuper(ISOWeekYearParser);
    function ISOWeekYearParser() {
      var _this;
      _classCallCheck(this, ISOWeekYearParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 130);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['G', 'y', 'Y', 'u', 'Q', 'q', 'M', 'L', 'w', 'd', 'D', 'e', 'c', 't', 'T']);
      return _this;
    }
    _createClass(ISOWeekYearParser, [{
      key: "parse",
      value: function parse(dateString, token) {
        if (token === 'R') {
          return parseNDigitsSigned(4, dateString);
        }
        return parseNDigitsSigned(token.length, dateString);
      }
    }, {
      key: "set",
      value: function set(_date, _flags, value) {
        var firstWeekOfYear = new Date(0);
        firstWeekOfYear.setUTCFullYear(value, 0, 4);
        firstWeekOfYear.setUTCHours(0, 0, 0, 0);
        return startOfUTCISOWeek(firstWeekOfYear);
      }
    }]);
    return ISOWeekYearParser;
  }(Parser);

  var ExtendedYearParser = /*#__PURE__*/function (_Parser) {
    _inherits(ExtendedYearParser, _Parser);
    var _super = _createSuper(ExtendedYearParser);
    function ExtendedYearParser() {
      var _this;
      _classCallCheck(this, ExtendedYearParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 130);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['G', 'y', 'Y', 'R', 'w', 'I', 'i', 'e', 'c', 't', 'T']);
      return _this;
    }
    _createClass(ExtendedYearParser, [{
      key: "parse",
      value: function parse(dateString, token) {
        if (token === 'u') {
          return parseNDigitsSigned(4, dateString);
        }
        return parseNDigitsSigned(token.length, dateString);
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date.setUTCFullYear(value, 0, 1);
        date.setUTCHours(0, 0, 0, 0);
        return date;
      }
    }]);
    return ExtendedYearParser;
  }(Parser);

  var QuarterParser = /*#__PURE__*/function (_Parser) {
    _inherits(QuarterParser, _Parser);
    var _super = _createSuper(QuarterParser);
    function QuarterParser() {
      var _this;
      _classCallCheck(this, QuarterParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 120);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['Y', 'R', 'q', 'M', 'L', 'w', 'I', 'd', 'D', 'i', 'e', 'c', 't', 'T']);
      return _this;
    }
    _createClass(QuarterParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          // 1, 2, 3, 4
          case 'Q':
          case 'QQ':
            // 01, 02, 03, 04
            return parseNDigits(token.length, dateString);
          // 1st, 2nd, 3rd, 4th
          case 'Qo':
            return match.ordinalNumber(dateString, {
              unit: 'quarter'
            });
          // Q1, Q2, Q3, Q4
          case 'QQQ':
            return match.quarter(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.quarter(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          // 1, 2, 3, 4 (narrow quarter; could be not numerical)
          case 'QQQQQ':
            return match.quarter(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          // 1st quarter, 2nd quarter, ...
          case 'QQQQ':
          default:
            return match.quarter(dateString, {
              width: 'wide',
              context: 'formatting'
            }) || match.quarter(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.quarter(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 1 && value <= 4;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date.setUTCMonth((value - 1) * 3, 1);
        date.setUTCHours(0, 0, 0, 0);
        return date;
      }
    }]);
    return QuarterParser;
  }(Parser);

  var StandAloneQuarterParser = /*#__PURE__*/function (_Parser) {
    _inherits(StandAloneQuarterParser, _Parser);
    var _super = _createSuper(StandAloneQuarterParser);
    function StandAloneQuarterParser() {
      var _this;
      _classCallCheck(this, StandAloneQuarterParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 120);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['Y', 'R', 'Q', 'M', 'L', 'w', 'I', 'd', 'D', 'i', 'e', 'c', 't', 'T']);
      return _this;
    }
    _createClass(StandAloneQuarterParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          // 1, 2, 3, 4
          case 'q':
          case 'qq':
            // 01, 02, 03, 04
            return parseNDigits(token.length, dateString);
          // 1st, 2nd, 3rd, 4th
          case 'qo':
            return match.ordinalNumber(dateString, {
              unit: 'quarter'
            });
          // Q1, Q2, Q3, Q4
          case 'qqq':
            return match.quarter(dateString, {
              width: 'abbreviated',
              context: 'standalone'
            }) || match.quarter(dateString, {
              width: 'narrow',
              context: 'standalone'
            });
          // 1, 2, 3, 4 (narrow quarter; could be not numerical)
          case 'qqqqq':
            return match.quarter(dateString, {
              width: 'narrow',
              context: 'standalone'
            });
          // 1st quarter, 2nd quarter, ...
          case 'qqqq':
          default:
            return match.quarter(dateString, {
              width: 'wide',
              context: 'standalone'
            }) || match.quarter(dateString, {
              width: 'abbreviated',
              context: 'standalone'
            }) || match.quarter(dateString, {
              width: 'narrow',
              context: 'standalone'
            });
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 1 && value <= 4;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date.setUTCMonth((value - 1) * 3, 1);
        date.setUTCHours(0, 0, 0, 0);
        return date;
      }
    }]);
    return StandAloneQuarterParser;
  }(Parser);

  var MonthParser = /*#__PURE__*/function (_Parser) {
    _inherits(MonthParser, _Parser);
    var _super = _createSuper(MonthParser);
    function MonthParser() {
      var _this;
      _classCallCheck(this, MonthParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['Y', 'R', 'q', 'Q', 'L', 'w', 'I', 'D', 'i', 'e', 'c', 't', 'T']);
      _defineProperty(_assertThisInitialized(_this), "priority", 110);
      return _this;
    }
    _createClass(MonthParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        var valueCallback = function valueCallback(value) {
          return value - 1;
        };
        switch (token) {
          // 1, 2, ..., 12
          case 'M':
            return mapValue(parseNumericPattern(numericPatterns.month, dateString), valueCallback);
          // 01, 02, ..., 12
          case 'MM':
            return mapValue(parseNDigits(2, dateString), valueCallback);
          // 1st, 2nd, ..., 12th
          case 'Mo':
            return mapValue(match.ordinalNumber(dateString, {
              unit: 'month'
            }), valueCallback);
          // Jan, Feb, ..., Dec
          case 'MMM':
            return match.month(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.month(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          // J, F, ..., D
          case 'MMMMM':
            return match.month(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          // January, February, ..., December
          case 'MMMM':
          default:
            return match.month(dateString, {
              width: 'wide',
              context: 'formatting'
            }) || match.month(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.month(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 0 && value <= 11;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date.setUTCMonth(value, 1);
        date.setUTCHours(0, 0, 0, 0);
        return date;
      }
    }]);
    return MonthParser;
  }(Parser);

  var StandAloneMonthParser = /*#__PURE__*/function (_Parser) {
    _inherits(StandAloneMonthParser, _Parser);
    var _super = _createSuper(StandAloneMonthParser);
    function StandAloneMonthParser() {
      var _this;
      _classCallCheck(this, StandAloneMonthParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 110);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['Y', 'R', 'q', 'Q', 'M', 'w', 'I', 'D', 'i', 'e', 'c', 't', 'T']);
      return _this;
    }
    _createClass(StandAloneMonthParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        var valueCallback = function valueCallback(value) {
          return value - 1;
        };
        switch (token) {
          // 1, 2, ..., 12
          case 'L':
            return mapValue(parseNumericPattern(numericPatterns.month, dateString), valueCallback);
          // 01, 02, ..., 12
          case 'LL':
            return mapValue(parseNDigits(2, dateString), valueCallback);
          // 1st, 2nd, ..., 12th
          case 'Lo':
            return mapValue(match.ordinalNumber(dateString, {
              unit: 'month'
            }), valueCallback);
          // Jan, Feb, ..., Dec
          case 'LLL':
            return match.month(dateString, {
              width: 'abbreviated',
              context: 'standalone'
            }) || match.month(dateString, {
              width: 'narrow',
              context: 'standalone'
            });
          // J, F, ..., D
          case 'LLLLL':
            return match.month(dateString, {
              width: 'narrow',
              context: 'standalone'
            });
          // January, February, ..., December
          case 'LLLL':
          default:
            return match.month(dateString, {
              width: 'wide',
              context: 'standalone'
            }) || match.month(dateString, {
              width: 'abbreviated',
              context: 'standalone'
            }) || match.month(dateString, {
              width: 'narrow',
              context: 'standalone'
            });
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 0 && value <= 11;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date.setUTCMonth(value, 1);
        date.setUTCHours(0, 0, 0, 0);
        return date;
      }
    }]);
    return StandAloneMonthParser;
  }(Parser);

  function setUTCWeek(dirtyDate, dirtyWeek, options) {
    requiredArgs(2, arguments);
    var date = toDate(dirtyDate);
    var week = toInteger(dirtyWeek);
    var diff = getUTCWeek(date, options) - week;
    date.setUTCDate(date.getUTCDate() - diff * 7);
    return date;
  }

  var LocalWeekParser = /*#__PURE__*/function (_Parser) {
    _inherits(LocalWeekParser, _Parser);
    var _super = _createSuper(LocalWeekParser);
    function LocalWeekParser() {
      var _this;
      _classCallCheck(this, LocalWeekParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 100);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['y', 'R', 'u', 'q', 'Q', 'M', 'L', 'I', 'd', 'D', 'i', 't', 'T']);
      return _this;
    }
    _createClass(LocalWeekParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          case 'w':
            return parseNumericPattern(numericPatterns.week, dateString);
          case 'wo':
            return match.ordinalNumber(dateString, {
              unit: 'week'
            });
          default:
            return parseNDigits(token.length, dateString);
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 1 && value <= 53;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value, options) {
        return startOfUTCWeek(setUTCWeek(date, value, options), options);
      }
    }]);
    return LocalWeekParser;
  }(Parser);

  function setUTCISOWeek(dirtyDate, dirtyISOWeek) {
    requiredArgs(2, arguments);
    var date = toDate(dirtyDate);
    var isoWeek = toInteger(dirtyISOWeek);
    var diff = getUTCISOWeek(date) - isoWeek;
    date.setUTCDate(date.getUTCDate() - diff * 7);
    return date;
  }

  var ISOWeekParser = /*#__PURE__*/function (_Parser) {
    _inherits(ISOWeekParser, _Parser);
    var _super = _createSuper(ISOWeekParser);
    function ISOWeekParser() {
      var _this;
      _classCallCheck(this, ISOWeekParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 100);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['y', 'Y', 'u', 'q', 'Q', 'M', 'L', 'w', 'd', 'D', 'e', 'c', 't', 'T']);
      return _this;
    }
    _createClass(ISOWeekParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          case 'I':
            return parseNumericPattern(numericPatterns.week, dateString);
          case 'Io':
            return match.ordinalNumber(dateString, {
              unit: 'week'
            });
          default:
            return parseNDigits(token.length, dateString);
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 1 && value <= 53;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        return startOfUTCISOWeek(setUTCISOWeek(date, value));
      }
    }]);
    return ISOWeekParser;
  }(Parser);

  var DAYS_IN_MONTH = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  var DAYS_IN_MONTH_LEAP_YEAR = [31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];

  // Day of the month
  var DateParser = /*#__PURE__*/function (_Parser) {
    _inherits(DateParser, _Parser);
    var _super = _createSuper(DateParser);
    function DateParser() {
      var _this;
      _classCallCheck(this, DateParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 90);
      _defineProperty(_assertThisInitialized(_this), "subPriority", 1);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['Y', 'R', 'q', 'Q', 'w', 'I', 'D', 'i', 'e', 'c', 't', 'T']);
      return _this;
    }
    _createClass(DateParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          case 'd':
            return parseNumericPattern(numericPatterns.date, dateString);
          case 'do':
            return match.ordinalNumber(dateString, {
              unit: 'date'
            });
          default:
            return parseNDigits(token.length, dateString);
        }
      }
    }, {
      key: "validate",
      value: function validate(date, value) {
        var year = date.getUTCFullYear();
        var isLeapYear = isLeapYearIndex$1(year);
        var month = date.getUTCMonth();
        if (isLeapYear) {
          return value >= 1 && value <= DAYS_IN_MONTH_LEAP_YEAR[month];
        } else {
          return value >= 1 && value <= DAYS_IN_MONTH[month];
        }
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date.setUTCDate(value);
        date.setUTCHours(0, 0, 0, 0);
        return date;
      }
    }]);
    return DateParser;
  }(Parser);

  var DayOfYearParser = /*#__PURE__*/function (_Parser) {
    _inherits(DayOfYearParser, _Parser);
    var _super = _createSuper(DayOfYearParser);
    function DayOfYearParser() {
      var _this;
      _classCallCheck(this, DayOfYearParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 90);
      _defineProperty(_assertThisInitialized(_this), "subpriority", 1);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['Y', 'R', 'q', 'Q', 'M', 'L', 'w', 'I', 'd', 'E', 'i', 'e', 'c', 't', 'T']);
      return _this;
    }
    _createClass(DayOfYearParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          case 'D':
          case 'DD':
            return parseNumericPattern(numericPatterns.dayOfYear, dateString);
          case 'Do':
            return match.ordinalNumber(dateString, {
              unit: 'date'
            });
          default:
            return parseNDigits(token.length, dateString);
        }
      }
    }, {
      key: "validate",
      value: function validate(date, value) {
        var year = date.getUTCFullYear();
        var isLeapYear = isLeapYearIndex$1(year);
        if (isLeapYear) {
          return value >= 1 && value <= 366;
        } else {
          return value >= 1 && value <= 365;
        }
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date.setUTCMonth(0, value);
        date.setUTCHours(0, 0, 0, 0);
        return date;
      }
    }]);
    return DayOfYearParser;
  }(Parser);

  function setUTCDay(dirtyDate, dirtyDay, options) {
    var _ref, _ref2, _ref3, _options$weekStartsOn, _options$locale, _options$locale$optio, _defaultOptions$local, _defaultOptions$local2;
    requiredArgs(2, arguments);
    var defaultOptions = getDefaultOptions();
    var weekStartsOn = toInteger((_ref = (_ref2 = (_ref3 = (_options$weekStartsOn = options === null || options === void 0 ? void 0 : options.weekStartsOn) !== null && _options$weekStartsOn !== void 0 ? _options$weekStartsOn : options === null || options === void 0 ? void 0 : (_options$locale = options.locale) === null || _options$locale === void 0 ? void 0 : (_options$locale$optio = _options$locale.options) === null || _options$locale$optio === void 0 ? void 0 : _options$locale$optio.weekStartsOn) !== null && _ref3 !== void 0 ? _ref3 : defaultOptions.weekStartsOn) !== null && _ref2 !== void 0 ? _ref2 : (_defaultOptions$local = defaultOptions.locale) === null || _defaultOptions$local === void 0 ? void 0 : (_defaultOptions$local2 = _defaultOptions$local.options) === null || _defaultOptions$local2 === void 0 ? void 0 : _defaultOptions$local2.weekStartsOn) !== null && _ref !== void 0 ? _ref : 0);

    // Test if weekStartsOn is between 0 and 6 _and_ is not NaN
    if (!(weekStartsOn >= 0 && weekStartsOn <= 6)) {
      throw new RangeError('weekStartsOn must be between 0 and 6 inclusively');
    }
    var date = toDate(dirtyDate);
    var day = toInteger(dirtyDay);
    var currentDay = date.getUTCDay();
    var remainder = day % 7;
    var dayIndex = (remainder + 7) % 7;
    var diff = (dayIndex < weekStartsOn ? 7 : 0) + day - currentDay;
    date.setUTCDate(date.getUTCDate() + diff);
    return date;
  }

  var DayParser = /*#__PURE__*/function (_Parser) {
    _inherits(DayParser, _Parser);
    var _super = _createSuper(DayParser);
    function DayParser() {
      var _this;
      _classCallCheck(this, DayParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 90);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['D', 'i', 'e', 'c', 't', 'T']);
      return _this;
    }
    _createClass(DayParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          // Tue
          case 'E':
          case 'EE':
          case 'EEE':
            return match.day(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'short',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          // T
          case 'EEEEE':
            return match.day(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          // Tu
          case 'EEEEEE':
            return match.day(dateString, {
              width: 'short',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          // Tuesday
          case 'EEEE':
          default:
            return match.day(dateString, {
              width: 'wide',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'short',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 0 && value <= 6;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value, options) {
        date = setUTCDay(date, value, options);
        date.setUTCHours(0, 0, 0, 0);
        return date;
      }
    }]);
    return DayParser;
  }(Parser);

  var LocalDayParser = /*#__PURE__*/function (_Parser) {
    _inherits(LocalDayParser, _Parser);
    var _super = _createSuper(LocalDayParser);
    function LocalDayParser() {
      var _this;
      _classCallCheck(this, LocalDayParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 90);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['y', 'R', 'u', 'q', 'Q', 'M', 'L', 'I', 'd', 'D', 'E', 'i', 'c', 't', 'T']);
      return _this;
    }
    _createClass(LocalDayParser, [{
      key: "parse",
      value: function parse(dateString, token, match, options) {
        var valueCallback = function valueCallback(value) {
          var wholeWeekDays = Math.floor((value - 1) / 7) * 7;
          return (value + options.weekStartsOn + 6) % 7 + wholeWeekDays;
        };
        switch (token) {
          // 3
          case 'e':
          case 'ee':
            // 03
            return mapValue(parseNDigits(token.length, dateString), valueCallback);
          // 3rd
          case 'eo':
            return mapValue(match.ordinalNumber(dateString, {
              unit: 'day'
            }), valueCallback);
          // Tue
          case 'eee':
            return match.day(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'short',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          // T
          case 'eeeee':
            return match.day(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          // Tu
          case 'eeeeee':
            return match.day(dateString, {
              width: 'short',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          // Tuesday
          case 'eeee':
          default:
            return match.day(dateString, {
              width: 'wide',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'short',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 0 && value <= 6;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value, options) {
        date = setUTCDay(date, value, options);
        date.setUTCHours(0, 0, 0, 0);
        return date;
      }
    }]);
    return LocalDayParser;
  }(Parser);

  var StandAloneLocalDayParser = /*#__PURE__*/function (_Parser) {
    _inherits(StandAloneLocalDayParser, _Parser);
    var _super = _createSuper(StandAloneLocalDayParser);
    function StandAloneLocalDayParser() {
      var _this;
      _classCallCheck(this, StandAloneLocalDayParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 90);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['y', 'R', 'u', 'q', 'Q', 'M', 'L', 'I', 'd', 'D', 'E', 'i', 'e', 't', 'T']);
      return _this;
    }
    _createClass(StandAloneLocalDayParser, [{
      key: "parse",
      value: function parse(dateString, token, match, options) {
        var valueCallback = function valueCallback(value) {
          var wholeWeekDays = Math.floor((value - 1) / 7) * 7;
          return (value + options.weekStartsOn + 6) % 7 + wholeWeekDays;
        };
        switch (token) {
          // 3
          case 'c':
          case 'cc':
            // 03
            return mapValue(parseNDigits(token.length, dateString), valueCallback);
          // 3rd
          case 'co':
            return mapValue(match.ordinalNumber(dateString, {
              unit: 'day'
            }), valueCallback);
          // Tue
          case 'ccc':
            return match.day(dateString, {
              width: 'abbreviated',
              context: 'standalone'
            }) || match.day(dateString, {
              width: 'short',
              context: 'standalone'
            }) || match.day(dateString, {
              width: 'narrow',
              context: 'standalone'
            });
          // T
          case 'ccccc':
            return match.day(dateString, {
              width: 'narrow',
              context: 'standalone'
            });
          // Tu
          case 'cccccc':
            return match.day(dateString, {
              width: 'short',
              context: 'standalone'
            }) || match.day(dateString, {
              width: 'narrow',
              context: 'standalone'
            });
          // Tuesday
          case 'cccc':
          default:
            return match.day(dateString, {
              width: 'wide',
              context: 'standalone'
            }) || match.day(dateString, {
              width: 'abbreviated',
              context: 'standalone'
            }) || match.day(dateString, {
              width: 'short',
              context: 'standalone'
            }) || match.day(dateString, {
              width: 'narrow',
              context: 'standalone'
            });
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 0 && value <= 6;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value, options) {
        date = setUTCDay(date, value, options);
        date.setUTCHours(0, 0, 0, 0);
        return date;
      }
    }]);
    return StandAloneLocalDayParser;
  }(Parser);

  function setUTCISODay(dirtyDate, dirtyDay) {
    requiredArgs(2, arguments);
    var day = toInteger(dirtyDay);
    if (day % 7 === 0) {
      day = day - 7;
    }
    var weekStartsOn = 1;
    var date = toDate(dirtyDate);
    var currentDay = date.getUTCDay();
    var remainder = day % 7;
    var dayIndex = (remainder + 7) % 7;
    var diff = (dayIndex < weekStartsOn ? 7 : 0) + day - currentDay;
    date.setUTCDate(date.getUTCDate() + diff);
    return date;
  }

  var ISODayParser = /*#__PURE__*/function (_Parser) {
    _inherits(ISODayParser, _Parser);
    var _super = _createSuper(ISODayParser);
    function ISODayParser() {
      var _this;
      _classCallCheck(this, ISODayParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 90);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['y', 'Y', 'u', 'q', 'Q', 'M', 'L', 'w', 'd', 'D', 'E', 'e', 'c', 't', 'T']);
      return _this;
    }
    _createClass(ISODayParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        var valueCallback = function valueCallback(value) {
          if (value === 0) {
            return 7;
          }
          return value;
        };
        switch (token) {
          // 2
          case 'i':
          case 'ii':
            // 02
            return parseNDigits(token.length, dateString);
          // 2nd
          case 'io':
            return match.ordinalNumber(dateString, {
              unit: 'day'
            });
          // Tue
          case 'iii':
            return mapValue(match.day(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'short',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'narrow',
              context: 'formatting'
            }), valueCallback);
          // T
          case 'iiiii':
            return mapValue(match.day(dateString, {
              width: 'narrow',
              context: 'formatting'
            }), valueCallback);
          // Tu
          case 'iiiiii':
            return mapValue(match.day(dateString, {
              width: 'short',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'narrow',
              context: 'formatting'
            }), valueCallback);
          // Tuesday
          case 'iiii':
          default:
            return mapValue(match.day(dateString, {
              width: 'wide',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'short',
              context: 'formatting'
            }) || match.day(dateString, {
              width: 'narrow',
              context: 'formatting'
            }), valueCallback);
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 1 && value <= 7;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date = setUTCISODay(date, value);
        date.setUTCHours(0, 0, 0, 0);
        return date;
      }
    }]);
    return ISODayParser;
  }(Parser);

  var AMPMParser = /*#__PURE__*/function (_Parser) {
    _inherits(AMPMParser, _Parser);
    var _super = _createSuper(AMPMParser);
    function AMPMParser() {
      var _this;
      _classCallCheck(this, AMPMParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 80);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['b', 'B', 'H', 'k', 't', 'T']);
      return _this;
    }
    _createClass(AMPMParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          case 'a':
          case 'aa':
          case 'aaa':
            return match.dayPeriod(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.dayPeriod(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          case 'aaaaa':
            return match.dayPeriod(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          case 'aaaa':
          default:
            return match.dayPeriod(dateString, {
              width: 'wide',
              context: 'formatting'
            }) || match.dayPeriod(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.dayPeriod(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
        }
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date.setUTCHours(dayPeriodEnumToHours(value), 0, 0, 0);
        return date;
      }
    }]);
    return AMPMParser;
  }(Parser);

  var AMPMMidnightParser = /*#__PURE__*/function (_Parser) {
    _inherits(AMPMMidnightParser, _Parser);
    var _super = _createSuper(AMPMMidnightParser);
    function AMPMMidnightParser() {
      var _this;
      _classCallCheck(this, AMPMMidnightParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 80);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['a', 'B', 'H', 'k', 't', 'T']);
      return _this;
    }
    _createClass(AMPMMidnightParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          case 'b':
          case 'bb':
          case 'bbb':
            return match.dayPeriod(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.dayPeriod(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          case 'bbbbb':
            return match.dayPeriod(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          case 'bbbb':
          default:
            return match.dayPeriod(dateString, {
              width: 'wide',
              context: 'formatting'
            }) || match.dayPeriod(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.dayPeriod(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
        }
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date.setUTCHours(dayPeriodEnumToHours(value), 0, 0, 0);
        return date;
      }
    }]);
    return AMPMMidnightParser;
  }(Parser);

  var DayPeriodParser = /*#__PURE__*/function (_Parser) {
    _inherits(DayPeriodParser, _Parser);
    var _super = _createSuper(DayPeriodParser);
    function DayPeriodParser() {
      var _this;
      _classCallCheck(this, DayPeriodParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 80);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['a', 'b', 't', 'T']);
      return _this;
    }
    _createClass(DayPeriodParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          case 'B':
          case 'BB':
          case 'BBB':
            return match.dayPeriod(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.dayPeriod(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          case 'BBBBB':
            return match.dayPeriod(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
          case 'BBBB':
          default:
            return match.dayPeriod(dateString, {
              width: 'wide',
              context: 'formatting'
            }) || match.dayPeriod(dateString, {
              width: 'abbreviated',
              context: 'formatting'
            }) || match.dayPeriod(dateString, {
              width: 'narrow',
              context: 'formatting'
            });
        }
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date.setUTCHours(dayPeriodEnumToHours(value), 0, 0, 0);
        return date;
      }
    }]);
    return DayPeriodParser;
  }(Parser);

  var Hour1to12Parser = /*#__PURE__*/function (_Parser) {
    _inherits(Hour1to12Parser, _Parser);
    var _super = _createSuper(Hour1to12Parser);
    function Hour1to12Parser() {
      var _this;
      _classCallCheck(this, Hour1to12Parser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 70);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['H', 'K', 'k', 't', 'T']);
      return _this;
    }
    _createClass(Hour1to12Parser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          case 'h':
            return parseNumericPattern(numericPatterns.hour12h, dateString);
          case 'ho':
            return match.ordinalNumber(dateString, {
              unit: 'hour'
            });
          default:
            return parseNDigits(token.length, dateString);
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 1 && value <= 12;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        var isPM = date.getUTCHours() >= 12;
        if (isPM && value < 12) {
          date.setUTCHours(value + 12, 0, 0, 0);
        } else if (!isPM && value === 12) {
          date.setUTCHours(0, 0, 0, 0);
        } else {
          date.setUTCHours(value, 0, 0, 0);
        }
        return date;
      }
    }]);
    return Hour1to12Parser;
  }(Parser);

  var Hour0to23Parser = /*#__PURE__*/function (_Parser) {
    _inherits(Hour0to23Parser, _Parser);
    var _super = _createSuper(Hour0to23Parser);
    function Hour0to23Parser() {
      var _this;
      _classCallCheck(this, Hour0to23Parser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 70);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['a', 'b', 'h', 'K', 'k', 't', 'T']);
      return _this;
    }
    _createClass(Hour0to23Parser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          case 'H':
            return parseNumericPattern(numericPatterns.hour23h, dateString);
          case 'Ho':
            return match.ordinalNumber(dateString, {
              unit: 'hour'
            });
          default:
            return parseNDigits(token.length, dateString);
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 0 && value <= 23;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date.setUTCHours(value, 0, 0, 0);
        return date;
      }
    }]);
    return Hour0to23Parser;
  }(Parser);

  var Hour0To11Parser = /*#__PURE__*/function (_Parser) {
    _inherits(Hour0To11Parser, _Parser);
    var _super = _createSuper(Hour0To11Parser);
    function Hour0To11Parser() {
      var _this;
      _classCallCheck(this, Hour0To11Parser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 70);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['h', 'H', 'k', 't', 'T']);
      return _this;
    }
    _createClass(Hour0To11Parser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          case 'K':
            return parseNumericPattern(numericPatterns.hour11h, dateString);
          case 'Ko':
            return match.ordinalNumber(dateString, {
              unit: 'hour'
            });
          default:
            return parseNDigits(token.length, dateString);
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 0 && value <= 11;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        var isPM = date.getUTCHours() >= 12;
        if (isPM && value < 12) {
          date.setUTCHours(value + 12, 0, 0, 0);
        } else {
          date.setUTCHours(value, 0, 0, 0);
        }
        return date;
      }
    }]);
    return Hour0To11Parser;
  }(Parser);

  var Hour1To24Parser = /*#__PURE__*/function (_Parser) {
    _inherits(Hour1To24Parser, _Parser);
    var _super = _createSuper(Hour1To24Parser);
    function Hour1To24Parser() {
      var _this;
      _classCallCheck(this, Hour1To24Parser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 70);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['a', 'b', 'h', 'H', 'K', 't', 'T']);
      return _this;
    }
    _createClass(Hour1To24Parser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          case 'k':
            return parseNumericPattern(numericPatterns.hour24h, dateString);
          case 'ko':
            return match.ordinalNumber(dateString, {
              unit: 'hour'
            });
          default:
            return parseNDigits(token.length, dateString);
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 1 && value <= 24;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        var hours = value <= 24 ? value % 24 : value;
        date.setUTCHours(hours, 0, 0, 0);
        return date;
      }
    }]);
    return Hour1To24Parser;
  }(Parser);

  var MinuteParser = /*#__PURE__*/function (_Parser) {
    _inherits(MinuteParser, _Parser);
    var _super = _createSuper(MinuteParser);
    function MinuteParser() {
      var _this;
      _classCallCheck(this, MinuteParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 60);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['t', 'T']);
      return _this;
    }
    _createClass(MinuteParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          case 'm':
            return parseNumericPattern(numericPatterns.minute, dateString);
          case 'mo':
            return match.ordinalNumber(dateString, {
              unit: 'minute'
            });
          default:
            return parseNDigits(token.length, dateString);
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 0 && value <= 59;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date.setUTCMinutes(value, 0, 0);
        return date;
      }
    }]);
    return MinuteParser;
  }(Parser);

  var SecondParser = /*#__PURE__*/function (_Parser) {
    _inherits(SecondParser, _Parser);
    var _super = _createSuper(SecondParser);
    function SecondParser() {
      var _this;
      _classCallCheck(this, SecondParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 50);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['t', 'T']);
      return _this;
    }
    _createClass(SecondParser, [{
      key: "parse",
      value: function parse(dateString, token, match) {
        switch (token) {
          case 's':
            return parseNumericPattern(numericPatterns.second, dateString);
          case 'so':
            return match.ordinalNumber(dateString, {
              unit: 'second'
            });
          default:
            return parseNDigits(token.length, dateString);
        }
      }
    }, {
      key: "validate",
      value: function validate(_date, value) {
        return value >= 0 && value <= 59;
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date.setUTCSeconds(value, 0);
        return date;
      }
    }]);
    return SecondParser;
  }(Parser);

  var FractionOfSecondParser = /*#__PURE__*/function (_Parser) {
    _inherits(FractionOfSecondParser, _Parser);
    var _super = _createSuper(FractionOfSecondParser);
    function FractionOfSecondParser() {
      var _this;
      _classCallCheck(this, FractionOfSecondParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 30);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['t', 'T']);
      return _this;
    }
    _createClass(FractionOfSecondParser, [{
      key: "parse",
      value: function parse(dateString, token) {
        var valueCallback = function valueCallback(value) {
          return Math.floor(value * Math.pow(10, -token.length + 3));
        };
        return mapValue(parseNDigits(token.length, dateString), valueCallback);
      }
    }, {
      key: "set",
      value: function set(date, _flags, value) {
        date.setUTCMilliseconds(value);
        return date;
      }
    }]);
    return FractionOfSecondParser;
  }(Parser);

  var ISOTimezoneWithZParser = /*#__PURE__*/function (_Parser) {
    _inherits(ISOTimezoneWithZParser, _Parser);
    var _super = _createSuper(ISOTimezoneWithZParser);
    function ISOTimezoneWithZParser() {
      var _this;
      _classCallCheck(this, ISOTimezoneWithZParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 10);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['t', 'T', 'x']);
      return _this;
    }
    _createClass(ISOTimezoneWithZParser, [{
      key: "parse",
      value: function parse(dateString, token) {
        switch (token) {
          case 'X':
            return parseTimezonePattern(timezonePatterns.basicOptionalMinutes, dateString);
          case 'XX':
            return parseTimezonePattern(timezonePatterns.basic, dateString);
          case 'XXXX':
            return parseTimezonePattern(timezonePatterns.basicOptionalSeconds, dateString);
          case 'XXXXX':
            return parseTimezonePattern(timezonePatterns.extendedOptionalSeconds, dateString);
          case 'XXX':
          default:
            return parseTimezonePattern(timezonePatterns.extended, dateString);
        }
      }
    }, {
      key: "set",
      value: function set(date, flags, value) {
        if (flags.timestampIsSet) {
          return date;
        }
        return new Date(date.getTime() - value);
      }
    }]);
    return ISOTimezoneWithZParser;
  }(Parser);

  var ISOTimezoneParser = /*#__PURE__*/function (_Parser) {
    _inherits(ISOTimezoneParser, _Parser);
    var _super = _createSuper(ISOTimezoneParser);
    function ISOTimezoneParser() {
      var _this;
      _classCallCheck(this, ISOTimezoneParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 10);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", ['t', 'T', 'X']);
      return _this;
    }
    _createClass(ISOTimezoneParser, [{
      key: "parse",
      value: function parse(dateString, token) {
        switch (token) {
          case 'x':
            return parseTimezonePattern(timezonePatterns.basicOptionalMinutes, dateString);
          case 'xx':
            return parseTimezonePattern(timezonePatterns.basic, dateString);
          case 'xxxx':
            return parseTimezonePattern(timezonePatterns.basicOptionalSeconds, dateString);
          case 'xxxxx':
            return parseTimezonePattern(timezonePatterns.extendedOptionalSeconds, dateString);
          case 'xxx':
          default:
            return parseTimezonePattern(timezonePatterns.extended, dateString);
        }
      }
    }, {
      key: "set",
      value: function set(date, flags, value) {
        if (flags.timestampIsSet) {
          return date;
        }
        return new Date(date.getTime() - value);
      }
    }]);
    return ISOTimezoneParser;
  }(Parser);

  var TimestampSecondsParser = /*#__PURE__*/function (_Parser) {
    _inherits(TimestampSecondsParser, _Parser);
    var _super = _createSuper(TimestampSecondsParser);
    function TimestampSecondsParser() {
      var _this;
      _classCallCheck(this, TimestampSecondsParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 40);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", '*');
      return _this;
    }
    _createClass(TimestampSecondsParser, [{
      key: "parse",
      value: function parse(dateString) {
        return parseAnyDigitsSigned(dateString);
      }
    }, {
      key: "set",
      value: function set(_date, _flags, value) {
        return [new Date(value * 1000), {
          timestampIsSet: true
        }];
      }
    }]);
    return TimestampSecondsParser;
  }(Parser);

  var TimestampMillisecondsParser = /*#__PURE__*/function (_Parser) {
    _inherits(TimestampMillisecondsParser, _Parser);
    var _super = _createSuper(TimestampMillisecondsParser);
    function TimestampMillisecondsParser() {
      var _this;
      _classCallCheck(this, TimestampMillisecondsParser);
      for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
        args[_key] = arguments[_key];
      }
      _this = _super.call.apply(_super, [this].concat(args));
      _defineProperty(_assertThisInitialized(_this), "priority", 20);
      _defineProperty(_assertThisInitialized(_this), "incompatibleTokens", '*');
      return _this;
    }
    _createClass(TimestampMillisecondsParser, [{
      key: "parse",
      value: function parse(dateString) {
        return parseAnyDigitsSigned(dateString);
      }
    }, {
      key: "set",
      value: function set(_date, _flags, value) {
        return [new Date(value), {
          timestampIsSet: true
        }];
      }
    }]);
    return TimestampMillisecondsParser;
  }(Parser);

  /*
   * |     | Unit                           |     | Unit                           |
   * |-----|--------------------------------|-----|--------------------------------|
   * |  a  | AM, PM                         |  A* | Milliseconds in day            |
   * |  b  | AM, PM, noon, midnight         |  B  | Flexible day period            |
   * |  c  | Stand-alone local day of week  |  C* | Localized hour w/ day period   |
   * |  d  | Day of month                   |  D  | Day of year                    |
   * |  e  | Local day of week              |  E  | Day of week                    |
   * |  f  |                                |  F* | Day of week in month           |
   * |  g* | Modified Julian day            |  G  | Era                            |
   * |  h  | Hour [1-12]                    |  H  | Hour [0-23]                    |
   * |  i! | ISO day of week                |  I! | ISO week of year               |
   * |  j* | Localized hour w/ day period   |  J* | Localized hour w/o day period  |
   * |  k  | Hour [1-24]                    |  K  | Hour [0-11]                    |
   * |  l* | (deprecated)                   |  L  | Stand-alone month              |
   * |  m  | Minute                         |  M  | Month                          |
   * |  n  |                                |  N  |                                |
   * |  o! | Ordinal number modifier        |  O* | Timezone (GMT)                 |
   * |  p  |                                |  P  |                                |
   * |  q  | Stand-alone quarter            |  Q  | Quarter                        |
   * |  r* | Related Gregorian year         |  R! | ISO week-numbering year        |
   * |  s  | Second                         |  S  | Fraction of second             |
   * |  t! | Seconds timestamp              |  T! | Milliseconds timestamp         |
   * |  u  | Extended year                  |  U* | Cyclic year                    |
   * |  v* | Timezone (generic non-locat.)  |  V* | Timezone (location)            |
   * |  w  | Local week of year             |  W* | Week of month                  |
   * |  x  | Timezone (ISO-8601 w/o Z)      |  X  | Timezone (ISO-8601)            |
   * |  y  | Year (abs)                     |  Y  | Local week-numbering year      |
   * |  z* | Timezone (specific non-locat.) |  Z* | Timezone (aliases)             |
   *
   * Letters marked by * are not implemented but reserved by Unicode standard.
   *
   * Letters marked by ! are non-standard, but implemented by date-fns:
   * - `o` modifies the previous token to turn it into an ordinal (see `parse` docs)
   * - `i` is ISO day of week. For `i` and `ii` is returns numeric ISO week days,
   *   i.e. 7 for Sunday, 1 for Monday, etc.
   * - `I` is ISO week of year, as opposed to `w` which is local week of year.
   * - `R` is ISO week-numbering year, as opposed to `Y` which is local week-numbering year.
   *   `R` is supposed to be used in conjunction with `I` and `i`
   *   for universal ISO week-numbering date, whereas
   *   `Y` is supposed to be used in conjunction with `w` and `e`
   *   for week-numbering date specific to the locale.
   */
  var parsers = {
    G: new EraParser(),
    y: new YearParser(),
    Y: new LocalWeekYearParser(),
    R: new ISOWeekYearParser(),
    u: new ExtendedYearParser(),
    Q: new QuarterParser(),
    q: new StandAloneQuarterParser(),
    M: new MonthParser(),
    L: new StandAloneMonthParser(),
    w: new LocalWeekParser(),
    I: new ISOWeekParser(),
    d: new DateParser(),
    D: new DayOfYearParser(),
    E: new DayParser(),
    e: new LocalDayParser(),
    c: new StandAloneLocalDayParser(),
    i: new ISODayParser(),
    a: new AMPMParser(),
    b: new AMPMMidnightParser(),
    B: new DayPeriodParser(),
    h: new Hour1to12Parser(),
    H: new Hour0to23Parser(),
    K: new Hour0To11Parser(),
    k: new Hour1To24Parser(),
    m: new MinuteParser(),
    s: new SecondParser(),
    S: new FractionOfSecondParser(),
    X: new ISOTimezoneWithZParser(),
    x: new ISOTimezoneParser(),
    t: new TimestampSecondsParser(),
    T: new TimestampMillisecondsParser()
  };

  // - [yYQqMLwIdDecihHKkms]o matches any available ordinal number token
  //   (one of the certain letters followed by `o`)
  // - (\w)\1* matches any sequences of the same letter
  // - '' matches two quote characters in a row
  // - '(''|[^'])+('|$) matches anything surrounded by two quote characters ('),
  //   except a single quote symbol, which ends the sequence.
  //   Two quote characters do not end the sequence.
  //   If there is no matching single quote
  //   then the sequence will continue until the end of the string.
  // - . matches any single character unmatched by previous parts of the RegExps
  var formattingTokensRegExp = /[yYQqMLwIdDecihHKkms]o|(\w)\1*|''|'(''|[^'])+('|$)|./g;

  // This RegExp catches symbols escaped by quotes, and also
  // sequences of symbols P, p, and the combinations like `PPPPPPPppppp`
  var longFormattingTokensRegExp = /P+p+|P+|p+|''|'(''|[^'])+('|$)|./g;
  var escapedStringRegExp = /^'([^]*?)'?$/;
  var doubleQuoteRegExp = /''/g;
  var notWhitespaceRegExp = /\S/;
  var unescapedLatinCharacterRegExp = /[a-zA-Z]/;

  /**
   * @name parse
   * @category Common Helpers
   * @summary Parse the date.
   *
   * @description
   * Return the date parsed from string using the given format string.
   *
   * > ⚠️ Please note that the `format` tokens differ from Moment.js and other libraries.
   * > See: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   *
   * The characters in the format string wrapped between two single quotes characters (') are escaped.
   * Two single quotes in a row, whether inside or outside a quoted sequence, represent a 'real' single quote.
   *
   * Format of the format string is based on Unicode Technical Standard #35:
   * https://www.unicode.org/reports/tr35/tr35-dates.html#Date_Field_Symbol_Table
   * with a few additions (see note 5 below the table).
   *
   * Not all tokens are compatible. Combinations that don't make sense or could lead to bugs are prohibited
   * and will throw `RangeError`. For example usage of 24-hour format token with AM/PM token will throw an exception:
   *
   * ```javascript
   * parse('23 AM', 'HH a', new Date())
   * //=> RangeError: The format string mustn't contain `HH` and `a` at the same time
   * ```
   *
   * See the compatibility table: https://docs.google.com/spreadsheets/d/e/2PACX-1vQOPU3xUhplll6dyoMmVUXHKl_8CRDs6_ueLmex3SoqwhuolkuN3O05l4rqx5h1dKX8eb46Ul-CCSrq/pubhtml?gid=0&single=true
   *
   * Accepted format string patterns:
   * | Unit                            |Prior| Pattern | Result examples                   | Notes |
   * |---------------------------------|-----|---------|-----------------------------------|-------|
   * | Era                             | 140 | G..GGG  | AD, BC                            |       |
   * |                                 |     | GGGG    | Anno Domini, Before Christ        | 2     |
   * |                                 |     | GGGGG   | A, B                              |       |
   * | Calendar year                   | 130 | y       | 44, 1, 1900, 2017, 9999           | 4     |
   * |                                 |     | yo      | 44th, 1st, 1900th, 9999999th      | 4,5   |
   * |                                 |     | yy      | 44, 01, 00, 17                    | 4     |
   * |                                 |     | yyy     | 044, 001, 123, 999                | 4     |
   * |                                 |     | yyyy    | 0044, 0001, 1900, 2017            | 4     |
   * |                                 |     | yyyyy   | ...                               | 2,4   |
   * | Local week-numbering year       | 130 | Y       | 44, 1, 1900, 2017, 9000           | 4     |
   * |                                 |     | Yo      | 44th, 1st, 1900th, 9999999th      | 4,5   |
   * |                                 |     | YY      | 44, 01, 00, 17                    | 4,6   |
   * |                                 |     | YYY     | 044, 001, 123, 999                | 4     |
   * |                                 |     | YYYY    | 0044, 0001, 1900, 2017            | 4,6   |
   * |                                 |     | YYYYY   | ...                               | 2,4   |
   * | ISO week-numbering year         | 130 | R       | -43, 1, 1900, 2017, 9999, -9999   | 4,5   |
   * |                                 |     | RR      | -43, 01, 00, 17                   | 4,5   |
   * |                                 |     | RRR     | -043, 001, 123, 999, -999         | 4,5   |
   * |                                 |     | RRRR    | -0043, 0001, 2017, 9999, -9999    | 4,5   |
   * |                                 |     | RRRRR   | ...                               | 2,4,5 |
   * | Extended year                   | 130 | u       | -43, 1, 1900, 2017, 9999, -999    | 4     |
   * |                                 |     | uu      | -43, 01, 99, -99                  | 4     |
   * |                                 |     | uuu     | -043, 001, 123, 999, -999         | 4     |
   * |                                 |     | uuuu    | -0043, 0001, 2017, 9999, -9999    | 4     |
   * |                                 |     | uuuuu   | ...                               | 2,4   |
   * | Quarter (formatting)            | 120 | Q       | 1, 2, 3, 4                        |       |
   * |                                 |     | Qo      | 1st, 2nd, 3rd, 4th                | 5     |
   * |                                 |     | QQ      | 01, 02, 03, 04                    |       |
   * |                                 |     | QQQ     | Q1, Q2, Q3, Q4                    |       |
   * |                                 |     | QQQQ    | 1st quarter, 2nd quarter, ...     | 2     |
   * |                                 |     | QQQQQ   | 1, 2, 3, 4                        | 4     |
   * | Quarter (stand-alone)           | 120 | q       | 1, 2, 3, 4                        |       |
   * |                                 |     | qo      | 1st, 2nd, 3rd, 4th                | 5     |
   * |                                 |     | qq      | 01, 02, 03, 04                    |       |
   * |                                 |     | qqq     | Q1, Q2, Q3, Q4                    |       |
   * |                                 |     | qqqq    | 1st quarter, 2nd quarter, ...     | 2     |
   * |                                 |     | qqqqq   | 1, 2, 3, 4                        | 3     |
   * | Month (formatting)              | 110 | M       | 1, 2, ..., 12                     |       |
   * |                                 |     | Mo      | 1st, 2nd, ..., 12th               | 5     |
   * |                                 |     | MM      | 01, 02, ..., 12                   |       |
   * |                                 |     | MMM     | Jan, Feb, ..., Dec                |       |
   * |                                 |     | MMMM    | January, February, ..., December  | 2     |
   * |                                 |     | MMMMM   | J, F, ..., D                      |       |
   * | Month (stand-alone)             | 110 | L       | 1, 2, ..., 12                     |       |
   * |                                 |     | Lo      | 1st, 2nd, ..., 12th               | 5     |
   * |                                 |     | LL      | 01, 02, ..., 12                   |       |
   * |                                 |     | LLL     | Jan, Feb, ..., Dec                |       |
   * |                                 |     | LLLL    | January, February, ..., December  | 2     |
   * |                                 |     | LLLLL   | J, F, ..., D                      |       |
   * | Local week of year              | 100 | w       | 1, 2, ..., 53                     |       |
   * |                                 |     | wo      | 1st, 2nd, ..., 53th               | 5     |
   * |                                 |     | ww      | 01, 02, ..., 53                   |       |
   * | ISO week of year                | 100 | I       | 1, 2, ..., 53                     | 5     |
   * |                                 |     | Io      | 1st, 2nd, ..., 53th               | 5     |
   * |                                 |     | II      | 01, 02, ..., 53                   | 5     |
   * | Day of month                    |  90 | d       | 1, 2, ..., 31                     |       |
   * |                                 |     | do      | 1st, 2nd, ..., 31st               | 5     |
   * |                                 |     | dd      | 01, 02, ..., 31                   |       |
   * | Day of year                     |  90 | D       | 1, 2, ..., 365, 366               | 7     |
   * |                                 |     | Do      | 1st, 2nd, ..., 365th, 366th       | 5     |
   * |                                 |     | DD      | 01, 02, ..., 365, 366             | 7     |
   * |                                 |     | DDD     | 001, 002, ..., 365, 366           |       |
   * |                                 |     | DDDD    | ...                               | 2     |
   * | Day of week (formatting)        |  90 | E..EEE  | Mon, Tue, Wed, ..., Sun           |       |
   * |                                 |     | EEEE    | Monday, Tuesday, ..., Sunday      | 2     |
   * |                                 |     | EEEEE   | M, T, W, T, F, S, S               |       |
   * |                                 |     | EEEEEE  | Mo, Tu, We, Th, Fr, Sa, Su        |       |
   * | ISO day of week (formatting)    |  90 | i       | 1, 2, 3, ..., 7                   | 5     |
   * |                                 |     | io      | 1st, 2nd, ..., 7th                | 5     |
   * |                                 |     | ii      | 01, 02, ..., 07                   | 5     |
   * |                                 |     | iii     | Mon, Tue, Wed, ..., Sun           | 5     |
   * |                                 |     | iiii    | Monday, Tuesday, ..., Sunday      | 2,5   |
   * |                                 |     | iiiii   | M, T, W, T, F, S, S               | 5     |
   * |                                 |     | iiiiii  | Mo, Tu, We, Th, Fr, Sa, Su        | 5     |
   * | Local day of week (formatting)  |  90 | e       | 2, 3, 4, ..., 1                   |       |
   * |                                 |     | eo      | 2nd, 3rd, ..., 1st                | 5     |
   * |                                 |     | ee      | 02, 03, ..., 01                   |       |
   * |                                 |     | eee     | Mon, Tue, Wed, ..., Sun           |       |
   * |                                 |     | eeee    | Monday, Tuesday, ..., Sunday      | 2     |
   * |                                 |     | eeeee   | M, T, W, T, F, S, S               |       |
   * |                                 |     | eeeeee  | Mo, Tu, We, Th, Fr, Sa, Su        |       |
   * | Local day of week (stand-alone) |  90 | c       | 2, 3, 4, ..., 1                   |       |
   * |                                 |     | co      | 2nd, 3rd, ..., 1st                | 5     |
   * |                                 |     | cc      | 02, 03, ..., 01                   |       |
   * |                                 |     | ccc     | Mon, Tue, Wed, ..., Sun           |       |
   * |                                 |     | cccc    | Monday, Tuesday, ..., Sunday      | 2     |
   * |                                 |     | ccccc   | M, T, W, T, F, S, S               |       |
   * |                                 |     | cccccc  | Mo, Tu, We, Th, Fr, Sa, Su        |       |
   * | AM, PM                          |  80 | a..aaa  | AM, PM                            |       |
   * |                                 |     | aaaa    | a.m., p.m.                        | 2     |
   * |                                 |     | aaaaa   | a, p                              |       |
   * | AM, PM, noon, midnight          |  80 | b..bbb  | AM, PM, noon, midnight            |       |
   * |                                 |     | bbbb    | a.m., p.m., noon, midnight        | 2     |
   * |                                 |     | bbbbb   | a, p, n, mi                       |       |
   * | Flexible day period             |  80 | B..BBB  | at night, in the morning, ...     |       |
   * |                                 |     | BBBB    | at night, in the morning, ...     | 2     |
   * |                                 |     | BBBBB   | at night, in the morning, ...     |       |
   * | Hour [1-12]                     |  70 | h       | 1, 2, ..., 11, 12                 |       |
   * |                                 |     | ho      | 1st, 2nd, ..., 11th, 12th         | 5     |
   * |                                 |     | hh      | 01, 02, ..., 11, 12               |       |
   * | Hour [0-23]                     |  70 | H       | 0, 1, 2, ..., 23                  |       |
   * |                                 |     | Ho      | 0th, 1st, 2nd, ..., 23rd          | 5     |
   * |                                 |     | HH      | 00, 01, 02, ..., 23               |       |
   * | Hour [0-11]                     |  70 | K       | 1, 2, ..., 11, 0                  |       |
   * |                                 |     | Ko      | 1st, 2nd, ..., 11th, 0th          | 5     |
   * |                                 |     | KK      | 01, 02, ..., 11, 00               |       |
   * | Hour [1-24]                     |  70 | k       | 24, 1, 2, ..., 23                 |       |
   * |                                 |     | ko      | 24th, 1st, 2nd, ..., 23rd         | 5     |
   * |                                 |     | kk      | 24, 01, 02, ..., 23               |       |
   * | Minute                          |  60 | m       | 0, 1, ..., 59                     |       |
   * |                                 |     | mo      | 0th, 1st, ..., 59th               | 5     |
   * |                                 |     | mm      | 00, 01, ..., 59                   |       |
   * | Second                          |  50 | s       | 0, 1, ..., 59                     |       |
   * |                                 |     | so      | 0th, 1st, ..., 59th               | 5     |
   * |                                 |     | ss      | 00, 01, ..., 59                   |       |
   * | Seconds timestamp               |  40 | t       | 512969520                         |       |
   * |                                 |     | tt      | ...                               | 2     |
   * | Fraction of second              |  30 | S       | 0, 1, ..., 9                      |       |
   * |                                 |     | SS      | 00, 01, ..., 99                   |       |
   * |                                 |     | SSS     | 000, 001, ..., 999                |       |
   * |                                 |     | SSSS    | ...                               | 2     |
   * | Milliseconds timestamp          |  20 | T       | 512969520900                      |       |
   * |                                 |     | TT      | ...                               | 2     |
   * | Timezone (ISO-8601 w/ Z)        |  10 | X       | -08, +0530, Z                     |       |
   * |                                 |     | XX      | -0800, +0530, Z                   |       |
   * |                                 |     | XXX     | -08:00, +05:30, Z                 |       |
   * |                                 |     | XXXX    | -0800, +0530, Z, +123456          | 2     |
   * |                                 |     | XXXXX   | -08:00, +05:30, Z, +12:34:56      |       |
   * | Timezone (ISO-8601 w/o Z)       |  10 | x       | -08, +0530, +00                   |       |
   * |                                 |     | xx      | -0800, +0530, +0000               |       |
   * |                                 |     | xxx     | -08:00, +05:30, +00:00            | 2     |
   * |                                 |     | xxxx    | -0800, +0530, +0000, +123456      |       |
   * |                                 |     | xxxxx   | -08:00, +05:30, +00:00, +12:34:56 |       |
   * | Long localized date             |  NA | P       | 05/29/1453                        | 5,8   |
   * |                                 |     | PP      | May 29, 1453                      |       |
   * |                                 |     | PPP     | May 29th, 1453                    |       |
   * |                                 |     | PPPP    | Sunday, May 29th, 1453            | 2,5,8 |
   * | Long localized time             |  NA | p       | 12:00 AM                          | 5,8   |
   * |                                 |     | pp      | 12:00:00 AM                       |       |
   * | Combination of date and time    |  NA | Pp      | 05/29/1453, 12:00 AM              |       |
   * |                                 |     | PPpp    | May 29, 1453, 12:00:00 AM         |       |
   * |                                 |     | PPPpp   | May 29th, 1453 at ...             |       |
   * |                                 |     | PPPPpp  | Sunday, May 29th, 1453 at ...     | 2,5,8 |
   * Notes:
   * 1. "Formatting" units (e.g. formatting quarter) in the default en-US locale
   *    are the same as "stand-alone" units, but are different in some languages.
   *    "Formatting" units are declined according to the rules of the language
   *    in the context of a date. "Stand-alone" units are always nominative singular.
   *    In `format` function, they will produce different result:
   *
   *    `format(new Date(2017, 10, 6), 'do LLLL', {locale: cs}) //=> '6. listopad'`
   *
   *    `format(new Date(2017, 10, 6), 'do MMMM', {locale: cs}) //=> '6. listopadu'`
   *
   *    `parse` will try to match both formatting and stand-alone units interchangably.
   *
   * 2. Any sequence of the identical letters is a pattern, unless it is escaped by
   *    the single quote characters (see below).
   *    If the sequence is longer than listed in table:
   *    - for numerical units (`yyyyyyyy`) `parse` will try to match a number
   *      as wide as the sequence
   *    - for text units (`MMMMMMMM`) `parse` will try to match the widest variation of the unit.
   *      These variations are marked with "2" in the last column of the table.
   *
   * 3. `QQQQQ` and `qqqqq` could be not strictly numerical in some locales.
   *    These tokens represent the shortest form of the quarter.
   *
   * 4. The main difference between `y` and `u` patterns are B.C. years:
   *
   *    | Year | `y` | `u` |
   *    |------|-----|-----|
   *    | AC 1 |   1 |   1 |
   *    | BC 1 |   1 |   0 |
   *    | BC 2 |   2 |  -1 |
   *
   *    Also `yy` will try to guess the century of two digit year by proximity with `referenceDate`:
   *
   *    `parse('50', 'yy', new Date(2018, 0, 1)) //=> Sat Jan 01 2050 00:00:00`
   *
   *    `parse('75', 'yy', new Date(2018, 0, 1)) //=> Wed Jan 01 1975 00:00:00`
   *
   *    while `uu` will just assign the year as is:
   *
   *    `parse('50', 'uu', new Date(2018, 0, 1)) //=> Sat Jan 01 0050 00:00:00`
   *
   *    `parse('75', 'uu', new Date(2018, 0, 1)) //=> Tue Jan 01 0075 00:00:00`
   *
   *    The same difference is true for local and ISO week-numbering years (`Y` and `R`),
   *    except local week-numbering years are dependent on `options.weekStartsOn`
   *    and `options.firstWeekContainsDate` (compare [setISOWeekYear]{@link https://date-fns.org/docs/setISOWeekYear}
   *    and [setWeekYear]{@link https://date-fns.org/docs/setWeekYear}).
   *
   * 5. These patterns are not in the Unicode Technical Standard #35:
   *    - `i`: ISO day of week
   *    - `I`: ISO week of year
   *    - `R`: ISO week-numbering year
   *    - `o`: ordinal number modifier
   *    - `P`: long localized date
   *    - `p`: long localized time
   *
   * 6. `YY` and `YYYY` tokens represent week-numbering years but they are often confused with years.
   *    You should enable `options.useAdditionalWeekYearTokens` to use them. See: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   *
   * 7. `D` and `DD` tokens represent days of the year but they are ofthen confused with days of the month.
   *    You should enable `options.useAdditionalDayOfYearTokens` to use them. See: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   *
   * 8. `P+` tokens do not have a defined priority since they are merely aliases to other tokens based
   *    on the given locale.
   *
   *    using `en-US` locale: `P` => `MM/dd/yyyy`
   *    using `en-US` locale: `p` => `hh:mm a`
   *    using `pt-BR` locale: `P` => `dd/MM/yyyy`
   *    using `pt-BR` locale: `p` => `HH:mm`
   *
   * Values will be assigned to the date in the descending order of its unit's priority.
   * Units of an equal priority overwrite each other in the order of appearance.
   *
   * If no values of higher priority are parsed (e.g. when parsing string 'January 1st' without a year),
   * the values will be taken from 3rd argument `referenceDate` which works as a context of parsing.
   *
   * `referenceDate` must be passed for correct work of the function.
   * If you're not sure which `referenceDate` to supply, create a new instance of Date:
   * `parse('02/11/2014', 'MM/dd/yyyy', new Date())`
   * In this case parsing will be done in the context of the current date.
   * If `referenceDate` is `Invalid Date` or a value not convertible to valid `Date`,
   * then `Invalid Date` will be returned.
   *
   * The result may vary by locale.
   *
   * If `formatString` matches with `dateString` but does not provides tokens, `referenceDate` will be returned.
   *
   * If parsing failed, `Invalid Date` will be returned.
   * Invalid Date is a Date, whose time value is NaN.
   * Time value of Date: http://es5.github.io/#x15.9.1.1
   *
   * @param {String} dateString - the string to parse
   * @param {String} formatString - the string of tokens
   * @param {Date|Number} referenceDate - defines values missing from the parsed dateString
   * @param {Object} [options] - an object with options.
   * @param {Locale} [options.locale=defaultLocale] - the locale object. See [Locale]{@link https://date-fns.org/docs/Locale}
   * @param {0|1|2|3|4|5|6} [options.weekStartsOn=0] - the index of the first day of the week (0 - Sunday)
   * @param {1|2|3|4|5|6|7} [options.firstWeekContainsDate=1] - the day of January, which is always in the first week of the year
   * @param {Boolean} [options.useAdditionalWeekYearTokens=false] - if true, allows usage of the week-numbering year tokens `YY` and `YYYY`;
   *   see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   * @param {Boolean} [options.useAdditionalDayOfYearTokens=false] - if true, allows usage of the day of year tokens `D` and `DD`;
   *   see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   * @returns {Date} the parsed date
   * @throws {TypeError} 3 arguments required
   * @throws {RangeError} `options.weekStartsOn` must be between 0 and 6
   * @throws {RangeError} `options.firstWeekContainsDate` must be between 1 and 7
   * @throws {RangeError} `options.locale` must contain `match` property
   * @throws {RangeError} use `yyyy` instead of `YYYY` for formatting years using [format provided] to the input [input provided]; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   * @throws {RangeError} use `yy` instead of `YY` for formatting years using [format provided] to the input [input provided]; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   * @throws {RangeError} use `d` instead of `D` for formatting days of the month using [format provided] to the input [input provided]; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   * @throws {RangeError} use `dd` instead of `DD` for formatting days of the month using [format provided] to the input [input provided]; see: https://github.com/date-fns/date-fns/blob/master/docs/unicodeTokens.md
   * @throws {RangeError} format string contains an unescaped latin alphabet character
   *
   * @example
   * // Parse 11 February 2014 from middle-endian format:
   * var result = parse('02/11/2014', 'MM/dd/yyyy', new Date())
   * //=> Tue Feb 11 2014 00:00:00
   *
   * @example
   * // Parse 28th of February in Esperanto locale in the context of 2010 year:
   * import eo from 'date-fns/locale/eo'
   * var result = parse('28-a de februaro', "do 'de' MMMM", new Date(2010, 0, 1), {
   *   locale: eo
   * })
   * //=> Sun Feb 28 2010 00:00:00
   */
  function parse(dirtyDateString, dirtyFormatString, dirtyReferenceDate, options) {
    var _ref, _options$locale, _ref2, _ref3, _ref4, _options$firstWeekCon, _options$locale2, _options$locale2$opti, _defaultOptions$local, _defaultOptions$local2, _ref5, _ref6, _ref7, _options$weekStartsOn, _options$locale3, _options$locale3$opti, _defaultOptions$local3, _defaultOptions$local4;
    requiredArgs(3, arguments);
    var dateString = String(dirtyDateString);
    var formatString = String(dirtyFormatString);
    var defaultOptions = getDefaultOptions();
    var locale = (_ref = (_options$locale = options === null || options === void 0 ? void 0 : options.locale) !== null && _options$locale !== void 0 ? _options$locale : defaultOptions.locale) !== null && _ref !== void 0 ? _ref : defaultLocale;
    if (!locale.match) {
      throw new RangeError('locale must contain match property');
    }
    var firstWeekContainsDate = toInteger((_ref2 = (_ref3 = (_ref4 = (_options$firstWeekCon = options === null || options === void 0 ? void 0 : options.firstWeekContainsDate) !== null && _options$firstWeekCon !== void 0 ? _options$firstWeekCon : options === null || options === void 0 ? void 0 : (_options$locale2 = options.locale) === null || _options$locale2 === void 0 ? void 0 : (_options$locale2$opti = _options$locale2.options) === null || _options$locale2$opti === void 0 ? void 0 : _options$locale2$opti.firstWeekContainsDate) !== null && _ref4 !== void 0 ? _ref4 : defaultOptions.firstWeekContainsDate) !== null && _ref3 !== void 0 ? _ref3 : (_defaultOptions$local = defaultOptions.locale) === null || _defaultOptions$local === void 0 ? void 0 : (_defaultOptions$local2 = _defaultOptions$local.options) === null || _defaultOptions$local2 === void 0 ? void 0 : _defaultOptions$local2.firstWeekContainsDate) !== null && _ref2 !== void 0 ? _ref2 : 1);

    // Test if weekStartsOn is between 1 and 7 _and_ is not NaN
    if (!(firstWeekContainsDate >= 1 && firstWeekContainsDate <= 7)) {
      throw new RangeError('firstWeekContainsDate must be between 1 and 7 inclusively');
    }
    var weekStartsOn = toInteger((_ref5 = (_ref6 = (_ref7 = (_options$weekStartsOn = options === null || options === void 0 ? void 0 : options.weekStartsOn) !== null && _options$weekStartsOn !== void 0 ? _options$weekStartsOn : options === null || options === void 0 ? void 0 : (_options$locale3 = options.locale) === null || _options$locale3 === void 0 ? void 0 : (_options$locale3$opti = _options$locale3.options) === null || _options$locale3$opti === void 0 ? void 0 : _options$locale3$opti.weekStartsOn) !== null && _ref7 !== void 0 ? _ref7 : defaultOptions.weekStartsOn) !== null && _ref6 !== void 0 ? _ref6 : (_defaultOptions$local3 = defaultOptions.locale) === null || _defaultOptions$local3 === void 0 ? void 0 : (_defaultOptions$local4 = _defaultOptions$local3.options) === null || _defaultOptions$local4 === void 0 ? void 0 : _defaultOptions$local4.weekStartsOn) !== null && _ref5 !== void 0 ? _ref5 : 0);

    // Test if weekStartsOn is between 0 and 6 _and_ is not NaN
    if (!(weekStartsOn >= 0 && weekStartsOn <= 6)) {
      throw new RangeError('weekStartsOn must be between 0 and 6 inclusively');
    }
    if (formatString === '') {
      if (dateString === '') {
        return toDate(dirtyReferenceDate);
      } else {
        return new Date(NaN);
      }
    }
    var subFnOptions = {
      firstWeekContainsDate: firstWeekContainsDate,
      weekStartsOn: weekStartsOn,
      locale: locale
    };

    // If timezone isn't specified, it will be set to the system timezone
    var setters = [new DateToSystemTimezoneSetter()];
    var tokens = formatString.match(longFormattingTokensRegExp).map(function (substring) {
      var firstCharacter = substring[0];
      if (firstCharacter in longFormatters$1) {
        var longFormatter = longFormatters$1[firstCharacter];
        return longFormatter(substring, locale.formatLong);
      }
      return substring;
    }).join('').match(formattingTokensRegExp);
    var usedTokens = [];
    var _iterator = _createForOfIteratorHelper(tokens),
      _step;
    try {
      var _loop = function _loop() {
        var token = _step.value;
        if (!(options !== null && options !== void 0 && options.useAdditionalWeekYearTokens) && isProtectedWeekYearToken(token)) {
          throwProtectedError(token, formatString, dirtyDateString);
        }
        if (!(options !== null && options !== void 0 && options.useAdditionalDayOfYearTokens) && isProtectedDayOfYearToken(token)) {
          throwProtectedError(token, formatString, dirtyDateString);
        }
        var firstCharacter = token[0];
        var parser = parsers[firstCharacter];
        if (parser) {
          var incompatibleTokens = parser.incompatibleTokens;
          if (Array.isArray(incompatibleTokens)) {
            var incompatibleToken = usedTokens.find(function (usedToken) {
              return incompatibleTokens.includes(usedToken.token) || usedToken.token === firstCharacter;
            });
            if (incompatibleToken) {
              throw new RangeError("The format string mustn't contain `".concat(incompatibleToken.fullToken, "` and `").concat(token, "` at the same time"));
            }
          } else if (parser.incompatibleTokens === '*' && usedTokens.length > 0) {
            throw new RangeError("The format string mustn't contain `".concat(token, "` and any other token at the same time"));
          }
          usedTokens.push({
            token: firstCharacter,
            fullToken: token
          });
          var parseResult = parser.run(dateString, token, locale.match, subFnOptions);
          if (!parseResult) {
            return {
              v: new Date(NaN)
            };
          }
          setters.push(parseResult.setter);
          dateString = parseResult.rest;
        } else {
          if (firstCharacter.match(unescapedLatinCharacterRegExp)) {
            throw new RangeError('Format string contains an unescaped latin alphabet character `' + firstCharacter + '`');
          }

          // Replace two single quote characters with one single quote character
          if (token === "''") {
            token = "'";
          } else if (firstCharacter === "'") {
            token = cleanEscapedString(token);
          }

          // Cut token from string, or, if string doesn't match the token, return Invalid Date
          if (dateString.indexOf(token) === 0) {
            dateString = dateString.slice(token.length);
          } else {
            return {
              v: new Date(NaN)
            };
          }
        }
      };
      for (_iterator.s(); !(_step = _iterator.n()).done;) {
        var _ret = _loop();
        if (_typeof(_ret) === "object") return _ret.v;
      }

      // Check if the remaining input contains something other than whitespace
    } catch (err) {
      _iterator.e(err);
    } finally {
      _iterator.f();
    }
    if (dateString.length > 0 && notWhitespaceRegExp.test(dateString)) {
      return new Date(NaN);
    }
    var uniquePrioritySetters = setters.map(function (setter) {
      return setter.priority;
    }).sort(function (a, b) {
      return b - a;
    }).filter(function (priority, index, array) {
      return array.indexOf(priority) === index;
    }).map(function (priority) {
      return setters.filter(function (setter) {
        return setter.priority === priority;
      }).sort(function (a, b) {
        return b.subPriority - a.subPriority;
      });
    }).map(function (setterArray) {
      return setterArray[0];
    });
    var date = toDate(dirtyReferenceDate);
    if (isNaN(date.getTime())) {
      return new Date(NaN);
    }

    // Convert the date in system timezone to the same date in UTC+00:00 timezone.
    var utcDate = subMilliseconds(date, getTimezoneOffsetInMilliseconds(date));
    var flags = {};
    var _iterator2 = _createForOfIteratorHelper(uniquePrioritySetters),
      _step2;
    try {
      for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
        var setter = _step2.value;
        if (!setter.validate(utcDate, subFnOptions)) {
          return new Date(NaN);
        }
        var result = setter.set(utcDate, flags, subFnOptions);
        // Result is tuple (date, flags)
        if (Array.isArray(result)) {
          utcDate = result[0];
          assign(flags, result[1]);
          // Result is date
        } else {
          utcDate = result;
        }
      }
    } catch (err) {
      _iterator2.e(err);
    } finally {
      _iterator2.f();
    }
    return utcDate;
  }
  function cleanEscapedString(input) {
    return input.match(escapedStringRegExp)[1].replace(doubleQuoteRegExp, "'");
  }

  /**
   * @name startOfHour
   * @category Hour Helpers
   * @summary Return the start of an hour for the given date.
   *
   * @description
   * Return the start of an hour for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @returns {Date} the start of an hour
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // The start of an hour for 2 September 2014 11:55:00:
   * const result = startOfHour(new Date(2014, 8, 2, 11, 55))
   * //=> Tue Sep 02 2014 11:00:00
   */
  function startOfHour(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    date.setMinutes(0, 0, 0);
    return date;
  }

  /**
   * @name startOfSecond
   * @category Second Helpers
   * @summary Return the start of a second for the given date.
   *
   * @description
   * Return the start of a second for the given date.
   * The result will be in the local timezone.
   *
   * @param {Date|Number} date - the original date
   * @returns {Date} the start of a second
   * @throws {TypeError} 1 argument required
   *
   * @example
   * // The start of a second for 1 December 2014 22:15:45.400:
   * const result = startOfSecond(new Date(2014, 11, 1, 22, 15, 45, 400))
   * //=> Mon Dec 01 2014 22:15:45.000
   */
  function startOfSecond(dirtyDate) {
    requiredArgs(1, arguments);
    var date = toDate(dirtyDate);
    date.setMilliseconds(0);
    return date;
  }

  /**
   * @name parseISO
   * @category Common Helpers
   * @summary Parse ISO string
   *
   * @description
   * Parse the given string in ISO 8601 format and return an instance of Date.
   *
   * Function accepts complete ISO 8601 formats as well as partial implementations.
   * ISO 8601: http://en.wikipedia.org/wiki/ISO_8601
   *
   * If the argument isn't a string, the function cannot parse the string or
   * the values are invalid, it returns Invalid Date.
   *
   * @param {String} argument - the value to convert
   * @param {Object} [options] - an object with options.
   * @param {0|1|2} [options.additionalDigits=2] - the additional number of digits in the extended year format
   * @returns {Date} the parsed date in the local time zone
   * @throws {TypeError} 1 argument required
   * @throws {RangeError} `options.additionalDigits` must be 0, 1 or 2
   *
   * @example
   * // Convert string '2014-02-11T11:30:30' to date:
   * const result = parseISO('2014-02-11T11:30:30')
   * //=> Tue Feb 11 2014 11:30:30
   *
   * @example
   * // Convert string '+02014101' to date,
   * // if the additional number of digits in the extended year format is 1:
   * const result = parseISO('+02014101', { additionalDigits: 1 })
   * //=> Fri Apr 11 2014 00:00:00
   */
  function parseISO(argument, options) {
    var _options$additionalDi;
    requiredArgs(1, arguments);
    var additionalDigits = toInteger((_options$additionalDi = options === null || options === void 0 ? void 0 : options.additionalDigits) !== null && _options$additionalDi !== void 0 ? _options$additionalDi : 2);
    if (additionalDigits !== 2 && additionalDigits !== 1 && additionalDigits !== 0) {
      throw new RangeError('additionalDigits must be 0, 1 or 2');
    }
    if (!(typeof argument === 'string' || Object.prototype.toString.call(argument) === '[object String]')) {
      return new Date(NaN);
    }
    var dateStrings = splitDateString(argument);
    var date;
    if (dateStrings.date) {
      var parseYearResult = parseYear(dateStrings.date, additionalDigits);
      date = parseDate(parseYearResult.restDateString, parseYearResult.year);
    }
    if (!date || isNaN(date.getTime())) {
      return new Date(NaN);
    }
    var timestamp = date.getTime();
    var time = 0;
    var offset;
    if (dateStrings.time) {
      time = parseTime(dateStrings.time);
      if (isNaN(time)) {
        return new Date(NaN);
      }
    }
    if (dateStrings.timezone) {
      offset = parseTimezone(dateStrings.timezone);
      if (isNaN(offset)) {
        return new Date(NaN);
      }
    } else {
      var dirtyDate = new Date(timestamp + time);
      // js parsed string assuming it's in UTC timezone
      // but we need it to be parsed in our timezone
      // so we use utc values to build date in our timezone.
      // Year values from 0 to 99 map to the years 1900 to 1999
      // so set year explicitly with setFullYear.
      var result = new Date(0);
      result.setFullYear(dirtyDate.getUTCFullYear(), dirtyDate.getUTCMonth(), dirtyDate.getUTCDate());
      result.setHours(dirtyDate.getUTCHours(), dirtyDate.getUTCMinutes(), dirtyDate.getUTCSeconds(), dirtyDate.getUTCMilliseconds());
      return result;
    }
    return new Date(timestamp + time + offset);
  }
  var patterns = {
    dateTimeDelimiter: /[T ]/,
    timeZoneDelimiter: /[Z ]/i,
    timezone: /([Z+-].*)$/
  };
  var dateRegex = /^-?(?:(\d{3})|(\d{2})(?:-?(\d{2}))?|W(\d{2})(?:-?(\d{1}))?|)$/;
  var timeRegex = /^(\d{2}(?:[.,]\d*)?)(?::?(\d{2}(?:[.,]\d*)?))?(?::?(\d{2}(?:[.,]\d*)?))?$/;
  var timezoneRegex = /^([+-])(\d{2})(?::?(\d{2}))?$/;
  function splitDateString(dateString) {
    var dateStrings = {};
    var array = dateString.split(patterns.dateTimeDelimiter);
    var timeString;

    // The regex match should only return at maximum two array elements.
    // [date], [time], or [date, time].
    if (array.length > 2) {
      return dateStrings;
    }
    if (/:/.test(array[0])) {
      timeString = array[0];
    } else {
      dateStrings.date = array[0];
      timeString = array[1];
      if (patterns.timeZoneDelimiter.test(dateStrings.date)) {
        dateStrings.date = dateString.split(patterns.timeZoneDelimiter)[0];
        timeString = dateString.substr(dateStrings.date.length, dateString.length);
      }
    }
    if (timeString) {
      var token = patterns.timezone.exec(timeString);
      if (token) {
        dateStrings.time = timeString.replace(token[1], '');
        dateStrings.timezone = token[1];
      } else {
        dateStrings.time = timeString;
      }
    }
    return dateStrings;
  }
  function parseYear(dateString, additionalDigits) {
    var regex = new RegExp('^(?:(\\d{4}|[+-]\\d{' + (4 + additionalDigits) + '})|(\\d{2}|[+-]\\d{' + (2 + additionalDigits) + '})$)');
    var captures = dateString.match(regex);
    // Invalid ISO-formatted year
    if (!captures) return {
      year: NaN,
      restDateString: ''
    };
    var year = captures[1] ? parseInt(captures[1]) : null;
    var century = captures[2] ? parseInt(captures[2]) : null;

    // either year or century is null, not both
    return {
      year: century === null ? year : century * 100,
      restDateString: dateString.slice((captures[1] || captures[2]).length)
    };
  }
  function parseDate(dateString, year) {
    // Invalid ISO-formatted year
    if (year === null) return new Date(NaN);
    var captures = dateString.match(dateRegex);
    // Invalid ISO-formatted string
    if (!captures) return new Date(NaN);
    var isWeekDate = !!captures[4];
    var dayOfYear = parseDateUnit(captures[1]);
    var month = parseDateUnit(captures[2]) - 1;
    var day = parseDateUnit(captures[3]);
    var week = parseDateUnit(captures[4]);
    var dayOfWeek = parseDateUnit(captures[5]) - 1;
    if (isWeekDate) {
      if (!validateWeekDate(year, week, dayOfWeek)) {
        return new Date(NaN);
      }
      return dayOfISOWeekYear(year, week, dayOfWeek);
    } else {
      var date = new Date(0);
      if (!validateDate(year, month, day) || !validateDayOfYearDate(year, dayOfYear)) {
        return new Date(NaN);
      }
      date.setUTCFullYear(year, month, Math.max(dayOfYear, day));
      return date;
    }
  }
  function parseDateUnit(value) {
    return value ? parseInt(value) : 1;
  }
  function parseTime(timeString) {
    var captures = timeString.match(timeRegex);
    if (!captures) return NaN; // Invalid ISO-formatted time

    var hours = parseTimeUnit(captures[1]);
    var minutes = parseTimeUnit(captures[2]);
    var seconds = parseTimeUnit(captures[3]);
    if (!validateTime(hours, minutes, seconds)) {
      return NaN;
    }
    return hours * millisecondsInHour + minutes * millisecondsInMinute + seconds * 1000;
  }
  function parseTimeUnit(value) {
    return value && parseFloat(value.replace(',', '.')) || 0;
  }
  function parseTimezone(timezoneString) {
    if (timezoneString === 'Z') return 0;
    var captures = timezoneString.match(timezoneRegex);
    if (!captures) return 0;
    var sign = captures[1] === '+' ? -1 : 1;
    var hours = parseInt(captures[2]);
    var minutes = captures[3] && parseInt(captures[3]) || 0;
    if (!validateTimezone(hours, minutes)) {
      return NaN;
    }
    return sign * (hours * millisecondsInHour + minutes * millisecondsInMinute);
  }
  function dayOfISOWeekYear(isoWeekYear, week, day) {
    var date = new Date(0);
    date.setUTCFullYear(isoWeekYear, 0, 4);
    var fourthOfJanuaryDay = date.getUTCDay() || 7;
    var diff = (week - 1) * 7 + day + 1 - fourthOfJanuaryDay;
    date.setUTCDate(date.getUTCDate() + diff);
    return date;
  }

  // Validation functions

  // February is null to handle the leap year (using ||)
  var daysInMonths = [31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
  function isLeapYearIndex(year) {
    return year % 400 === 0 || year % 4 === 0 && year % 100 !== 0;
  }
  function validateDate(year, month, date) {
    return month >= 0 && month <= 11 && date >= 1 && date <= (daysInMonths[month] || (isLeapYearIndex(year) ? 29 : 28));
  }
  function validateDayOfYearDate(year, dayOfYear) {
    return dayOfYear >= 1 && dayOfYear <= (isLeapYearIndex(year) ? 366 : 365);
  }
  function validateWeekDate(_year, week, day) {
    return week >= 1 && week <= 53 && day >= 0 && day <= 6;
  }
  function validateTime(hours, minutes, seconds) {
    if (hours === 24) {
      return minutes === 0 && seconds === 0;
    }
    return seconds >= 0 && seconds < 60 && minutes >= 0 && minutes < 60 && hours >= 0 && hours < 25;
  }
  function validateTimezone(_hours, minutes) {
    return minutes >= 0 && minutes <= 59;
  }

  var FORMATS = {
    datetime: 'MMM d, yyyy, h:mm:ss aaaa',
    millisecond: 'h:mm:ss.SSS aaaa',
    second: 'h:mm:ss aaaa',
    minute: 'h:mm aaaa',
    hour: 'ha',
    day: 'MMM d',
    week: 'PP',
    month: 'MMM yyyy',
    quarter: 'qqq - yyyy',
    year: 'yyyy'
  };
  adapters._date.override({
    _id: 'date-fns',
    // DEBUG

    formats: function formats() {
      return FORMATS;
    },
    parse: function parse$1(value, fmt) {
      if (value === null || typeof value === 'undefined') {
        return null;
      }
      var type = _typeof$1(value);
      if (type === 'number' || value instanceof Date) {
        value = toDate(value);
      } else if (type === 'string') {
        if (typeof fmt === 'string') {
          value = parse(value, fmt, new Date(), this.options);
        } else {
          value = parseISO(value, this.options);
        }
      }
      return isValid(value) ? value.getTime() : null;
    },
    format: function format$1(time, fmt) {
      return format(time, fmt, this.options);
    },
    add: function add(time, amount, unit) {
      switch (unit) {
        case 'millisecond':
          return addMilliseconds(time, amount);
        case 'second':
          return addSeconds(time, amount);
        case 'minute':
          return addMinutes(time, amount);
        case 'hour':
          return addHours(time, amount);
        case 'day':
          return addDays(time, amount);
        case 'week':
          return addWeeks(time, amount);
        case 'month':
          return addMonths(time, amount);
        case 'quarter':
          return addQuarters(time, amount);
        case 'year':
          return addYears(time, amount);
        default:
          return time;
      }
    },
    diff: function diff(max, min, unit) {
      switch (unit) {
        case 'millisecond':
          return differenceInMilliseconds(max, min);
        case 'second':
          return differenceInSeconds(max, min);
        case 'minute':
          return differenceInMinutes(max, min);
        case 'hour':
          return differenceInHours(max, min);
        case 'day':
          return differenceInDays(max, min);
        case 'week':
          return differenceInWeeks(max, min);
        case 'month':
          return differenceInMonths(max, min);
        case 'quarter':
          return differenceInQuarters(max, min);
        case 'year':
          return differenceInYears(max, min);
        default:
          return 0;
      }
    },
    startOf: function startOf(time, unit, weekday) {
      switch (unit) {
        case 'second':
          return startOfSecond(time);
        case 'minute':
          return startOfMinute(time);
        case 'hour':
          return startOfHour(time);
        case 'day':
          return startOfDay(time);
        case 'week':
          return startOfWeek(time);
        case 'isoWeek':
          return startOfWeek(time, {
            weekStartsOn: +weekday
          });
        case 'month':
          return startOfMonth(time);
        case 'quarter':
          return startOfQuarter(time);
        case 'year':
          return startOfYear(time);
        default:
          return time;
      }
    },
    endOf: function endOf(time, unit) {
      switch (unit) {
        case 'second':
          return endOfSecond(time);
        case 'minute':
          return endOfMinute(time);
        case 'hour':
          return endOfHour(time);
        case 'day':
          return endOfDay(time);
        case 'week':
          return endOfWeek(time);
        case 'month':
          return endOfMonth(time);
        case 'quarter':
          return endOfQuarter(time);
        case 'year':
          return endOfYear(time);
        default:
          return time;
      }
    }
  });

  // for plugins
  // match src/index.umd.ts in Chart.js
  // except for platforms (not exported)
  Chart.helpers = _objectSpread2({}, helpers);
  Chart._adapters = adapters;
  Chart.Animation = Animation;
  Chart.Animations = Animations;
  Chart.animator = animator;
  Chart.controllers = registry.controllers.items;
  Chart.DatasetController = DatasetController;
  Chart.Element = Element;
  Chart.elements = elements;
  Chart.Interaction = Interaction;
  Chart.layouts = layouts;
  Chart.Scale = Scale;
  Chart.Ticks = Ticks;
  Object.assign(Chart, controllers, scales, elements, plugins);
  Chart.Chart = Chart;

  return Chart;

}));
/**
 * @license Highcharts JS v6.0.3 (2017-11-14)
 *
 * (c) 2009-2016 Torstein Honsi
 *
 * License: www.highcharts.com/license
 */

'use strict';
(function(root, factory) {
    if (typeof module === 'object' && module.exports) {
        module.exports = root.document ?
            factory(root) :
            factory;
    } else {
        root.Highcharts = factory(root);
    }
}(typeof window !== 'undefined' ? window : this, function(win) {
    var Highcharts = (function() {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        /* global win, window */

        // glob is a temporary fix to allow our es-modules to work.
        var glob = typeof win === 'undefined' ? window : win,
            doc = glob.document,
            SVG_NS = 'http://www.w3.org/2000/svg',
            userAgent = (glob.navigator && glob.navigator.userAgent) || '',
            svg = doc && doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,
            isMS = /(edge|msie|trident)/i.test(userAgent) && !glob.opera,
            isFirefox = /Firefox/.test(userAgent),
            hasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4; // issue #38;

        var Highcharts = glob.Highcharts ? glob.Highcharts.error(16, true) : {
            product: 'Highcharts',
            version: '6.0.3',
            deg2rad: Math.PI * 2 / 360,
            doc: doc,
            hasBidiBug: hasBidiBug,
            hasTouch: doc && doc.documentElement.ontouchstart !== undefined,
            isMS: isMS,
            isWebKit: /AppleWebKit/.test(userAgent),
            isFirefox: isFirefox,
            isTouchDevice: /(Mobile|Android|Windows Phone)/.test(userAgent),
            SVG_NS: SVG_NS,
            chartCount: 0,
            seriesTypes: {},
            symbolSizes: {},
            svg: svg,
            win: glob,
            marginNames: ['plotTop', 'marginRight', 'marginBottom', 'plotLeft'],
            noop: function() {
                return undefined;
            },
            /**
             * An array containing the current chart objects in the page. A chart's
             * position in the array is preserved throughout the page's lifetime. When
             * a chart is destroyed, the array item becomes `undefined`.
             * @type {Array.<Highcharts.Chart>}
             * @memberOf Highcharts
             */
            charts: []
        };
        return Highcharts;
    }());
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        /* eslint max-len: ["warn", 80, 4] */

        /**
         * The Highcharts object is the placeholder for all other members, and various
         * utility functions. The most important member of the namespace would be the
         * chart constructor.
         *
         * @example
         * var chart = Highcharts.chart('container', { ... });
         * 
         * @namespace Highcharts
         */

        H.timers = [];

        var charts = H.charts,
            doc = H.doc,
            win = H.win;

        /**
         * Provide error messages for debugging, with links to online explanation. This
         * function can be overridden to provide custom error handling.
         *
         * @function #error
         * @memberOf Highcharts
         * @param {Number|String} code - The error code. See [errors.xml]{@link 
         *     https://github.com/highcharts/highcharts/blob/master/errors/errors.xml}
         *     for available codes. If it is a string, the error message is printed
         *     directly in the console.
         * @param {Boolean} [stop=false] - Whether to throw an error or just log a 
         *     warning in the console.
         *
         * @sample highcharts/chart/highcharts-error/ Custom error handler
         */
        H.error = function(code, stop) {
            var msg = H.isNumber(code) ?
                'Highcharts error #' + code + ': www.highcharts.com/errors/' + code :
                code;
            if (stop) {
                throw new Error(msg);
            }
            // else ...
            if (win.console) {
                console.log(msg); // eslint-disable-line no-console
            }
        };

        /**
         * An animator object used internally. One instance applies to one property
         * (attribute or style prop) on one element. Animation is always initiated
         * through {@link SVGElement#animate}.
         *
         * @constructor Fx
         * @memberOf Highcharts
         * @param {HTMLDOMElement|SVGElement} elem - The element to animate.
         * @param {AnimationOptions} options - Animation options.
         * @param {string} prop - The single attribute or CSS property to animate.
         * @private
         *
         * @example
         * var rect = renderer.rect(0, 0, 10, 10).add();
         * rect.animate({ width: 100 });
         */
        H.Fx = function(elem, options, prop) {
            this.options = options;
            this.elem = elem;
            this.prop = prop;
        };
        H.Fx.prototype = {

            /**
             * Set the current step of a path definition on SVGElement.
             *
             * @function #dSetter
             * @memberOf Highcharts.Fx
             */
            dSetter: function() {
                var start = this.paths[0],
                    end = this.paths[1],
                    ret = [],
                    now = this.now,
                    i = start.length,
                    startVal;

                // Land on the final path without adjustment points appended in the ends
                if (now === 1) {
                    ret = this.toD;

                } else if (i === end.length && now < 1) {
                    while (i--) {
                        startVal = parseFloat(start[i]);
                        ret[i] =
                            isNaN(startVal) ? // a letter instruction like M or L
                            end[i] :
                            now * (parseFloat(end[i] - startVal)) + startVal;

                    }
                    // If animation is finished or length not matching, land on right value
                } else {
                    ret = end;
                }
                this.elem.attr('d', ret, null, true);
            },

            /**
             * Update the element with the current animation step.
             *
             * @function #update
             * @memberOf Highcharts.Fx
             */
            update: function() {
                var elem = this.elem,
                    prop = this.prop, // if destroyed, it is null
                    now = this.now,
                    step = this.options.step;

                // Animation setter defined from outside
                if (this[prop + 'Setter']) {
                    this[prop + 'Setter']();

                    // Other animations on SVGElement
                } else if (elem.attr) {
                    if (elem.element) {
                        elem.attr(prop, now, null, true);
                    }

                    // HTML styles, raw HTML content like container size
                } else {
                    elem.style[prop] = now + this.unit;
                }

                if (step) {
                    step.call(elem, now, this);
                }

            },

            /**
             * Run an animation.
             *
             * @function #run
             * @memberOf Highcharts.Fx
             * @param {Number} from - The current value, value to start from.
             * @param {Number} to - The end value, value to land on.
             * @param {String} [unit] - The property unit, for example `px`.
             * 
             */
            run: function(from, to, unit) {
                var self = this,
                    options = self.options,
                    timer = function(gotoEnd) {
                        return timer.stopped ? false : self.step(gotoEnd);
                    },
                    requestAnimationFrame =
                    win.requestAnimationFrame ||
                    function(step) {
                        setTimeout(step, 13);
                    },
                    step = function() {
                        H.timers = H.grep(H.timers, function(timer) {
                            return timer();
                        });

                        if (H.timers.length) {
                            requestAnimationFrame(step);
                        }
                    };

                if (from === to) {
                    delete options.curAnim[this.prop];
                    if (options.complete && H.keys(options.curAnim).length === 0) {
                        options.complete();
                    }
                } else { // #7166
                    this.startTime = +new Date();
                    this.start = from;
                    this.end = to;
                    this.unit = unit;
                    this.now = this.start;
                    this.pos = 0;

                    timer.elem = this.elem;
                    timer.prop = this.prop;

                    if (timer() && H.timers.push(timer) === 1) {
                        requestAnimationFrame(step);
                    }
                }
            },

            /**
             * Run a single step in the animation.
             *
             * @function #step
             * @memberOf Highcharts.Fx
             * @param   {Boolean} [gotoEnd] - Whether to go to the endpoint of the
             *     animation after abort.
             * @returns {Boolean} Returns `true` if animation continues.
             */
            step: function(gotoEnd) {
                var t = +new Date(),
                    ret,
                    done,
                    options = this.options,
                    elem = this.elem,
                    complete = options.complete,
                    duration = options.duration,
                    curAnim = options.curAnim;

                if (elem.attr && !elem.element) { // #2616, element is destroyed
                    ret = false;

                } else if (gotoEnd || t >= duration + this.startTime) {
                    this.now = this.end;
                    this.pos = 1;
                    this.update();

                    curAnim[this.prop] = true;

                    done = true;

                    H.objectEach(curAnim, function(val) {
                        if (val !== true) {
                            done = false;
                        }
                    });

                    if (done && complete) {
                        complete.call(elem);
                    }
                    ret = false;

                } else {
                    this.pos = options.easing((t - this.startTime) / duration);
                    this.now = this.start + ((this.end - this.start) * this.pos);
                    this.update();
                    ret = true;
                }
                return ret;
            },

            /**
             * Prepare start and end values so that the path can be animated one to one.
             *
             * @function #initPath
             * @memberOf Highcharts.Fx
             * @param {SVGElement} elem - The SVGElement item.
             * @param {String} fromD - Starting path definition.
             * @param {Array} toD - Ending path definition.
             * @returns {Array} An array containing start and end paths in array form
             * so that they can be animated in parallel.
             */
            initPath: function(elem, fromD, toD) {
                fromD = fromD || '';
                var shift,
                    startX = elem.startX,
                    endX = elem.endX,
                    bezier = fromD.indexOf('C') > -1,
                    numParams = bezier ? 7 : 3,
                    fullLength,
                    slice,
                    i,
                    start = fromD.split(' '),
                    end = toD.slice(), // copy
                    isArea = elem.isArea,
                    positionFactor = isArea ? 2 : 1,
                    reverse;

                /**
                 * In splines make moveTo and lineTo points have six parameters like
                 * bezier curves, to allow animation one-to-one.
                 */
                function sixify(arr) {
                    var isOperator,
                        nextIsOperator;
                    i = arr.length;
                    while (i--) {

                        // Fill in dummy coordinates only if the next operator comes
                        // three places behind (#5788)
                        isOperator = arr[i] === 'M' || arr[i] === 'L';
                        nextIsOperator = /[a-zA-Z]/.test(arr[i + 3]);
                        if (isOperator && nextIsOperator) {
                            arr.splice(
                                i + 1, 0,
                                arr[i + 1], arr[i + 2],
                                arr[i + 1], arr[i + 2]
                            );
                        }
                    }
                }

                /**
                 * Insert an array at the given position of another array
                 */
                function insertSlice(arr, subArr, index) {
                    [].splice.apply(
                        arr, [index, 0].concat(subArr)
                    );
                }

                /**
                 * If shifting points, prepend a dummy point to the end path. 
                 */
                function prepend(arr, other) {
                    while (arr.length < fullLength) {

                        // Move to, line to or curve to?
                        arr[0] = other[fullLength - arr.length];

                        // Prepend a copy of the first point
                        insertSlice(arr, arr.slice(0, numParams), 0);

                        // For areas, the bottom path goes back again to the left, so we
                        // need to append a copy of the last point.
                        if (isArea) {
                            insertSlice(
                                arr,
                                arr.slice(arr.length - numParams), arr.length
                            );
                            i--;
                        }
                    }
                    arr[0] = 'M';
                }

                /**
                 * Copy and append last point until the length matches the end length
                 */
                function append(arr, other) {
                    var i = (fullLength - arr.length) / numParams;
                    while (i > 0 && i--) {

                        // Pull out the slice that is going to be appended or inserted.
                        // In a line graph, the positionFactor is 1, and the last point
                        // is sliced out. In an area graph, the positionFactor is 2,
                        // causing the middle two points to be sliced out, since an area
                        // path starts at left, follows the upper path then turns and
                        // follows the bottom back. 
                        slice = arr.slice().splice(
                            (arr.length / positionFactor) - numParams,
                            numParams * positionFactor
                        );

                        // Move to, line to or curve to?
                        slice[0] = other[fullLength - numParams - (i * numParams)];

                        // Disable first control point
                        if (bezier) {
                            slice[numParams - 6] = slice[numParams - 2];
                            slice[numParams - 5] = slice[numParams - 1];
                        }

                        // Now insert the slice, either in the middle (for areas) or at
                        // the end (for lines)
                        insertSlice(arr, slice, arr.length / positionFactor);

                        if (isArea) {
                            i--;
                        }
                    }
                }

                if (bezier) {
                    sixify(start);
                    sixify(end);
                }

                // For sideways animation, find out how much we need to shift to get the
                // start path Xs to match the end path Xs.
                if (startX && endX) {
                    for (i = 0; i < startX.length; i++) {
                        // Moving left, new points coming in on right
                        if (startX[i] === endX[0]) {
                            shift = i;
                            break;
                            // Moving right
                        } else if (startX[0] ===
                            endX[endX.length - startX.length + i]) {
                            shift = i;
                            reverse = true;
                            break;
                        }
                    }
                    if (shift === undefined) {
                        start = [];
                    }
                }

                if (start.length && H.isNumber(shift)) {

                    // The common target length for the start and end array, where both 
                    // arrays are padded in opposite ends
                    fullLength = end.length + shift * positionFactor * numParams;

                    if (!reverse) {
                        prepend(end, start);
                        append(start, end);
                    } else {
                        prepend(start, end);
                        append(end, start);
                    }
                }

                return [start, end];
            }
        }; // End of Fx prototype

        /**
         * Handle animation of the color attributes directly.
         */
        H.Fx.prototype.fillSetter =
            H.Fx.prototype.strokeSetter = function() {
                this.elem.attr(
                    this.prop,
                    H.color(this.start).tweenTo(H.color(this.end), this.pos),
                    null,
                    true
                );
            };

        /**
         * Utility function to extend an object with the members of another.
         *
         * @function #extend
         * @memberOf Highcharts
         * @param {Object} a - The object to be extended.
         * @param {Object} b - The object to add to the first one.
         * @returns {Object} Object a, the original object.
         */
        H.extend = function(a, b) {
            var n;
            if (!a) {
                a = {};
            }
            for (n in b) {
                a[n] = b[n];
            }
            return a;
        };

        /**
         * Utility function to deep merge two or more objects and return a third object.
         * If the first argument is true, the contents of the second object is copied
         * into the first object. The merge function can also be used with a single 
         * object argument to create a deep copy of an object.
         *
         * @function #merge
         * @memberOf Highcharts
         * @param {Boolean} [extend] - Whether to extend the left-side object (a) or
                  return a whole new object.
         * @param {Object} a - The first object to extend. When only this is given, the
                  function returns a deep copy.
         * @param {...Object} [n] - An object to merge into the previous one.
         * @returns {Object} - The merged object. If the first argument is true, the 
         * return is the same as the second argument.
         */
        H.merge = function() {
            var i,
                args = arguments,
                len,
                ret = {},
                doCopy = function(copy, original) {
                    // An object is replacing a primitive
                    if (typeof copy !== 'object') {
                        copy = {};
                    }

                    H.objectEach(original, function(value, key) {

                        // Copy the contents of objects, but not arrays or DOM nodes
                        if (
                            H.isObject(value, true) &&
                            !H.isClass(value) &&
                            !H.isDOMElement(value)
                        ) {
                            copy[key] = doCopy(copy[key] || {}, value);

                            // Primitives and arrays are copied over directly
                        } else {
                            copy[key] = original[key];
                        }
                    });
                    return copy;
                };

            // If first argument is true, copy into the existing object. Used in
            // setOptions.
            if (args[0] === true) {
                ret = args[1];
                args = Array.prototype.slice.call(args, 2);
            }

            // For each argument, extend the return
            len = args.length;
            for (i = 0; i < len; i++) {
                ret = doCopy(ret, args[i]);
            }

            return ret;
        };

        /**
         * Shortcut for parseInt
         * @ignore
         * @param {Object} s
         * @param {Number} mag Magnitude
         */
        H.pInt = function(s, mag) {
            return parseInt(s, mag || 10);
        };

        /**
         * Utility function to check for string type.
         *
         * @function #isString
         * @memberOf Highcharts
         * @param {Object} s - The item to check.
         * @returns {Boolean} - True if the argument is a string.
         */
        H.isString = function(s) {
            return typeof s === 'string';
        };

        /**
         * Utility function to check if an item is an array.
         *
         * @function #isArray
         * @memberOf Highcharts
         * @param {Object} obj - The item to check.
         * @returns {Boolean} - True if the argument is an array.
         */
        H.isArray = function(obj) {
            var str = Object.prototype.toString.call(obj);
            return str === '[object Array]' || str === '[object Array Iterator]';
        };

        /**
         * Utility function to check if an item is of type object.
         *
         * @function #isObject
         * @memberOf Highcharts
         * @param {Object} obj - The item to check.
         * @param {Boolean} [strict=false] - Also checks that the object is not an
         *    array.
         * @returns {Boolean} - True if the argument is an object.
         */
        H.isObject = function(obj, strict) {
            return !!obj && typeof obj === 'object' && (!strict || !H.isArray(obj));
        };

        /**
         * Utility function to check if an Object is a HTML Element.
         *
         * @function #isDOMElement
         * @memberOf Highcharts
         * @param {Object} obj - The item to check.
         * @returns {Boolean} - True if the argument is a HTML Element.
         */
        H.isDOMElement = function(obj) {
            return H.isObject(obj) && typeof obj.nodeType === 'number';
        };

        /**
         * Utility function to check if an Object is an class.
         *
         * @function #isClass
         * @memberOf Highcharts
         * @param {Object} obj - The item to check.
         * @returns {Boolean} - True if the argument is an class.
         */
        H.isClass = function(obj) {
            var c = obj && obj.constructor;
            return !!(
                H.isObject(obj, true) &&
                !H.isDOMElement(obj) &&
                (c && c.name && c.name !== 'Object')
            );
        };

        /**
         * Utility function to check if an item is of type number.
         *
         * @function #isNumber
         * @memberOf Highcharts
         * @param {Object} n - The item to check.
         * @returns {Boolean} - True if the item is a number and is not NaN.
         */
        H.isNumber = function(n) {
            return typeof n === 'number' && !isNaN(n);
        };

        /**
         * Remove the last occurence of an item from an array.
         *
         * @function #erase
         * @memberOf Highcharts
         * @param {Array} arr - The array.
         * @param {*} item - The item to remove.
         */
        H.erase = function(arr, item) {
            var i = arr.length;
            while (i--) {
                if (arr[i] === item) {
                    arr.splice(i, 1);
                    break;
                }
            }
        };

        /**
         * Check if an object is null or undefined.
         *
         * @function #defined
         * @memberOf Highcharts
         * @param {Object} obj - The object to check.
         * @returns {Boolean} - False if the object is null or undefined, otherwise
         *        true.
         */
        H.defined = function(obj) {
            return obj !== undefined && obj !== null;
        };

        /**
         * Set or get an attribute or an object of attributes. To use as a setter, pass
         * a key and a value, or let the second argument be a collection of keys and
         * values. To use as a getter, pass only a string as the second argument.
         *
         * @function #attr
         * @memberOf Highcharts
         * @param {Object} elem - The DOM element to receive the attribute(s).
         * @param {String|Object} [prop] - The property or an object of key-value pairs.
         * @param {String} [value] - The value if a single property is set.
         * @returns {*} When used as a getter, return the value.
         */
        H.attr = function(elem, prop, value) {
            var ret;

            // if the prop is a string
            if (H.isString(prop)) {
                // set the value
                if (H.defined(value)) {
                    elem.setAttribute(prop, value);

                    // get the value
                } else if (elem && elem.getAttribute) {
                    ret = elem.getAttribute(prop);
                }

                // else if prop is defined, it is a hash of key/value pairs
            } else if (H.defined(prop) && H.isObject(prop)) {
                H.objectEach(prop, function(val, key) {
                    elem.setAttribute(key, val);
                });
            }
            return ret;
        };

        /**
         * Check if an element is an array, and if not, make it into an array.
         *
         * @function #splat
         * @memberOf Highcharts
         * @param obj {*} - The object to splat.
         * @returns {Array} The produced or original array.
         */
        H.splat = function(obj) {
            return H.isArray(obj) ? obj : [obj];
        };

        /**
         * Set a timeout if the delay is given, otherwise perform the function
         * synchronously.
         *
         * @function #syncTimeout
         * @memberOf Highcharts
         * @param   {Function} fn - The function callback.
         * @param   {Number}   delay - Delay in milliseconds.
         * @param   {Object}   [context] - The context.
         * @returns {Number} An identifier for the timeout that can later be cleared
         * with clearTimeout.
         */
        H.syncTimeout = function(fn, delay, context) {
            if (delay) {
                return setTimeout(fn, delay, context);
            }
            fn.call(0, context);
        };


        /**
         * Return the first value that is not null or undefined.
         *
         * @function #pick
         * @memberOf Highcharts
         * @param {...*} items - Variable number of arguments to inspect.
         * @returns {*} The value of the first argument that is not null or undefined.
         */
        H.pick = function() {
            var args = arguments,
                i,
                arg,
                length = args.length;
            for (i = 0; i < length; i++) {
                arg = args[i];
                if (arg !== undefined && arg !== null) {
                    return arg;
                }
            }
        };

        /**
         * @typedef {Object} CSSObject - A style object with camel case property names.
         * The properties can be whatever styles are supported on the given SVG or HTML
         * element.
         * @example
         * {
         *    fontFamily: 'monospace',
         *    fontSize: '1.2em'
         * }
         */
        /**
         * Set CSS on a given element.
         *
         * @function #css
         * @memberOf Highcharts
         * @param {HTMLDOMElement} el - A HTML DOM element.
         * @param {CSSObject} styles - Style object with camel case property names.
         * 
         */
        H.css = function(el, styles) {
            if (H.isMS && !H.svg) { // #2686
                if (styles && styles.opacity !== undefined) {
                    styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';
                }
            }
            H.extend(el.style, styles);
        };

        /**
         * A HTML DOM element.
         * @typedef {Object} HTMLDOMElement
         */

        /**
         * Utility function to create an HTML element with attributes and styles.
         *
         * @function #createElement
         * @memberOf Highcharts
         * @param {String} tag - The HTML tag.
         * @param {Object} [attribs] - Attributes as an object of key-value pairs.
         * @param {CSSObject} [styles] - Styles as an object of key-value pairs.
         * @param {Object} [parent] - The parent HTML object.
         * @param {Boolean} [nopad=false] - If true, remove all padding, border and
         *    margin.
         * @returns {HTMLDOMElement} The created DOM element.
         */
        H.createElement = function(tag, attribs, styles, parent, nopad) {
            var el = doc.createElement(tag),
                css = H.css;
            if (attribs) {
                H.extend(el, attribs);
            }
            if (nopad) {
                css(el, {
                    padding: 0,
                    border: 'none',
                    margin: 0
                });
            }
            if (styles) {
                css(el, styles);
            }
            if (parent) {
                parent.appendChild(el);
            }
            return el;
        };

        /**
         * Extend a prototyped class by new members.
         *
         * @function #extendClass
         * @memberOf Highcharts
         * @param {Object} parent - The parent prototype to inherit.
         * @param {Object} members - A collection of prototype members to add or
         *        override compared to the parent prototype.
         * @returns {Object} A new prototype.
         */
        H.extendClass = function(parent, members) {
            var object = function() {};
            object.prototype = new parent(); // eslint-disable-line new-cap
            H.extend(object.prototype, members);
            return object;
        };

        /**
         * Left-pad a string to a given length by adding a character repetetively.
         *
         * @function #pad
         * @memberOf Highcharts
         * @param {Number} number - The input string or number.
         * @param {Number} length - The desired string length.
         * @param {String} [padder=0] - The character to pad with.
         * @returns {String} The padded string.
         */
        H.pad = function(number, length, padder) {
            return new Array((length || 2) + 1 -
                String(number).length).join(padder || 0) + number;
        };

        /**
         * @typedef {Number|String} RelativeSize - If a number is given, it defines the
         *    pixel length. If a percentage string is given, like for example `'50%'`,
         *    the setting defines a length relative to a base size, for example the size
         *    of a container.
         */
        /**
         * Return a length based on either the integer value, or a percentage of a base.
         *
         * @function #relativeLength
         * @memberOf Highcharts
         * @param  {RelativeSize} value
         *         A percentage string or a number.
         * @param  {number} base
         *         The full length that represents 100%.
         * @param  {number} [offset=0]
         *         A pixel offset to apply for percentage values. Used internally in 
         *         axis positioning.
         * @return {number}
         *         The computed length.
         */
        H.relativeLength = function(value, base, offset) {
            return (/%$/).test(value) ?
                (base * parseFloat(value) / 100) + (offset || 0) :
                parseFloat(value);
        };

        /**
         * Wrap a method with extended functionality, preserving the original function.
         *
         * @function #wrap
         * @memberOf Highcharts
         * @param {Object} obj - The context object that the method belongs to. In real
         *        cases, this is often a prototype.
         * @param {String} method - The name of the method to extend.
         * @param {Function} func - A wrapper function callback. This function is called
         *        with the same arguments as the original function, except that the
         *        original function is unshifted and passed as the first argument.
         * 
         */
        H.wrap = function(obj, method, func) {
            var proceed = obj[method];
            obj[method] = function() {
                var args = Array.prototype.slice.call(arguments),
                    outerArgs = arguments,
                    ctx = this,
                    ret;
                ctx.proceed = function() {
                    proceed.apply(ctx, arguments.length ? arguments : outerArgs);
                };
                args.unshift(proceed);
                ret = func.apply(this, args);
                ctx.proceed = null;
                return ret;
            };
        };

        /**
         * Get the time zone offset based on the current timezone information as set in
         * the global options.
         *
         * @function #getTZOffset
         * @memberOf Highcharts
         * @param  {Number} timestamp - The JavaScript timestamp to inspect.
         * @return {Number} - The timezone offset in minutes compared to UTC.
         */
        H.getTZOffset = function(timestamp) {
            var d = H.Date;
            return ((d.hcGetTimezoneOffset && d.hcGetTimezoneOffset(timestamp)) ||
                d.hcTimezoneOffset || 0) * 60000;
        };

        /**
         * Formats a JavaScript date timestamp (milliseconds since Jan 1st 1970) into a
         * human readable date string. The format is a subset of the formats for PHP's
         * [strftime]{@link
         * http://www.php.net/manual/en/function.strftime.php} function. Additional
         * formats can be given in the {@link Highcharts.dateFormats} hook.
         *
         * @function #dateFormat
         * @memberOf Highcharts
         * @param {String} format - The desired format where various time
         *        representations are prefixed with %.
         * @param {Number} timestamp - The JavaScript timestamp.
         * @param {Boolean} [capitalize=false] - Upper case first letter in the return.
         * @returns {String} The formatted date.
         */
        H.dateFormat = function(format, timestamp, capitalize) {
            if (!H.defined(timestamp) || isNaN(timestamp)) {
                return H.defaultOptions.lang.invalidDate || '';
            }
            format = H.pick(format, '%Y-%m-%d %H:%M:%S');

            var D = H.Date,
                date = new D(timestamp - H.getTZOffset(timestamp)),
                // get the basic time values
                hours = date[D.hcGetHours](),
                day = date[D.hcGetDay](),
                dayOfMonth = date[D.hcGetDate](),
                month = date[D.hcGetMonth](),
                fullYear = date[D.hcGetFullYear](),
                lang = H.defaultOptions.lang,
                langWeekdays = lang.weekdays,
                shortWeekdays = lang.shortWeekdays,
                pad = H.pad,

                // List all format keys. Custom formats can be added from the outside. 
                replacements = H.extend({

                        // Day
                        // Short weekday, like 'Mon'
                        'a': shortWeekdays ?
                            shortWeekdays[day] : langWeekdays[day].substr(0, 3),
                        // Long weekday, like 'Monday'
                        'A': langWeekdays[day],
                        // Two digit day of the month, 01 to 31
                        'd': pad(dayOfMonth),
                        // Day of the month, 1 through 31
                        'e': pad(dayOfMonth, 2, ' '),
                        'w': day,

                        // Week (none implemented)
                        // 'W': weekNumber(),

                        // Month
                        // Short month, like 'Jan'
                        'b': lang.shortMonths[month],
                        // Long month, like 'January'
                        'B': lang.months[month],
                        // Two digit month number, 01 through 12
                        'm': pad(month + 1),

                        // Year
                        // Two digits year, like 09 for 2009
                        'y': fullYear.toString().substr(2, 2),
                        // Four digits year, like 2009
                        'Y': fullYear,

                        // Time
                        // Two digits hours in 24h format, 00 through 23
                        'H': pad(hours),
                        // Hours in 24h format, 0 through 23
                        'k': hours,
                        // Two digits hours in 12h format, 00 through 11
                        'I': pad((hours % 12) || 12),
                        // Hours in 12h format, 1 through 12
                        'l': (hours % 12) || 12,
                        // Two digits minutes, 00 through 59
                        'M': pad(date[D.hcGetMinutes]()),
                        // Upper case AM or PM
                        'p': hours < 12 ? 'AM' : 'PM',
                        // Lower case AM or PM
                        'P': hours < 12 ? 'am' : 'pm',
                        // Two digits seconds, 00 through  59
                        'S': pad(date.getSeconds()),
                        // Milliseconds (naming from Ruby)
                        'L': pad(Math.round(timestamp % 1000), 3)
                    },

                    /**
                     * A hook for defining additional date format specifiers. New
                     * specifiers are defined as key-value pairs by using the specifier
                     * as key, and a function which takes the timestamp as value. This
                     * function returns the formatted portion of the date.
                     *
                     * @type {Object}
                     * @name dateFormats
                     * @memberOf Highcharts
                     * @sample highcharts/global/dateformats/ Adding support for week
                     * number
                     */
                    H.dateFormats
                );


            // Do the replaces
            H.objectEach(replacements, function(val, key) {
                // Regex would do it in one line, but this is faster
                while (format.indexOf('%' + key) !== -1) {
                    format = format.replace(
                        '%' + key,
                        typeof val === 'function' ? val(timestamp) : val
                    );
                }

            });

            // Optionally capitalize the string and return
            return capitalize ?
                format.substr(0, 1).toUpperCase() + format.substr(1) :
                format;
        };

        /**
         * Format a single variable. Similar to sprintf, without the % prefix.
         *
         * @example
         * formatSingle('.2f', 5); // => '5.00'.
         *
         * @function #formatSingle
         * @memberOf Highcharts
         * @param {String} format The format string.
         * @param {*} val The value.
         * @returns {String} The formatted representation of the value.
         */
        H.formatSingle = function(format, val) {
            var floatRegex = /f$/,
                decRegex = /\.([0-9])/,
                lang = H.defaultOptions.lang,
                decimals;

            if (floatRegex.test(format)) { // float
                decimals = format.match(decRegex);
                decimals = decimals ? decimals[1] : -1;
                if (val !== null) {
                    val = H.numberFormat(
                        val,
                        decimals,
                        lang.decimalPoint,
                        format.indexOf(',') > -1 ? lang.thousandsSep : ''
                    );
                }
            } else {
                val = H.dateFormat(format, val);
            }
            return val;
        };

        /**
         * Format a string according to a subset of the rules of Python's String.format
         * method.
         *
         * @function #format
         * @memberOf Highcharts
         * @param {String} str The string to format.
         * @param {Object} ctx The context, a collection of key-value pairs where each
         *        key is replaced by its value.
         * @returns {String} The formatted string.
         *
         * @example
         * var s = Highcharts.format(
         *     'The {color} fox was {len:.2f} feet long',
         *     { color: 'red', len: Math.PI }
         * );
         * // => The red fox was 3.14 feet long
         */
        H.format = function(str, ctx) {
            var splitter = '{',
                isInside = false,
                segment,
                valueAndFormat,
                path,
                i,
                len,
                ret = [],
                val,
                index;

            while (str) {
                index = str.indexOf(splitter);
                if (index === -1) {
                    break;
                }

                segment = str.slice(0, index);
                if (isInside) { // we're on the closing bracket looking back

                    valueAndFormat = segment.split(':');
                    path = valueAndFormat.shift().split('.'); // get first and leave
                    len = path.length;
                    val = ctx;

                    // Assign deeper paths
                    for (i = 0; i < len; i++) {
                        if (val) {
                            val = val[path[i]];
                        }
                    }

                    // Format the replacement
                    if (valueAndFormat.length) {
                        val = H.formatSingle(valueAndFormat.join(':'), val);
                    }

                    // Push the result and advance the cursor
                    ret.push(val);

                } else {
                    ret.push(segment);

                }
                str = str.slice(index + 1); // the rest
                isInside = !isInside; // toggle
                splitter = isInside ? '}' : '{'; // now look for next matching bracket
            }
            ret.push(str);
            return ret.join('');
        };

        /**
         * Get the magnitude of a number.
         *
         * @function #getMagnitude
         * @memberOf Highcharts
         * @param {Number} number The number.
         * @returns {Number} The magnitude, where 1-9 are magnitude 1, 10-99 magnitude 2
         *        etc.
         */
        H.getMagnitude = function(num) {
            return Math.pow(10, Math.floor(Math.log(num) / Math.LN10));
        };

        /**
         * Take an interval and normalize it to multiples of round numbers.
         *
         * @todo  Move this function to the Axis prototype. It is here only for
         *        historical reasons.
         * @function #normalizeTickInterval
         * @memberOf Highcharts
         * @param {Number} interval - The raw, un-rounded interval.
         * @param {Array} [multiples] - Allowed multiples.
         * @param {Number} [magnitude] - The magnitude of the number.
         * @param {Boolean} [allowDecimals] - Whether to allow decimals.
         * @param {Boolean} [hasTickAmount] - If it has tickAmount, avoid landing
         *        on tick intervals lower than original.
         * @returns {Number} The normalized interval.
         */
        H.normalizeTickInterval = function(interval, multiples, magnitude,
            allowDecimals, hasTickAmount) {
            var normalized,
                i,
                retInterval = interval;

            // round to a tenfold of 1, 2, 2.5 or 5
            magnitude = H.pick(magnitude, 1);
            normalized = interval / magnitude;

            // multiples for a linear scale
            if (!multiples) {
                multiples = hasTickAmount ?
                    // Finer grained ticks when the tick amount is hard set, including
                    // when alignTicks is true on multiple axes (#4580).
                    [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10] :

                    // Else, let ticks fall on rounder numbers
                    [1, 2, 2.5, 5, 10];


                // the allowDecimals option
                if (allowDecimals === false) {
                    if (magnitude === 1) {
                        multiples = H.grep(multiples, function(num) {
                            return num % 1 === 0;
                        });
                    } else if (magnitude <= 0.1) {
                        multiples = [1 / magnitude];
                    }
                }
            }

            // normalize the interval to the nearest multiple
            for (i = 0; i < multiples.length; i++) {
                retInterval = multiples[i];
                // only allow tick amounts smaller than natural
                if ((hasTickAmount && retInterval * magnitude >= interval) ||
                    (!hasTickAmount && (normalized <= (multiples[i] +
                        (multiples[i + 1] || multiples[i])) / 2))) {
                    break;
                }
            }

            // Multiply back to the correct magnitude. Correct floats to appropriate 
            // precision (#6085).
            retInterval = H.correctFloat(
                retInterval * magnitude, -Math.round(Math.log(0.001) / Math.LN10)
            );

            return retInterval;
        };


        /**
         * Sort an object array and keep the order of equal items. The ECMAScript
         * standard does not specify the behaviour when items are equal.
         *
         * @function #stableSort
         * @memberOf Highcharts
         * @param {Array} arr - The array to sort.
         * @param {Function} sortFunction - The function to sort it with, like with 
         *        regular Array.prototype.sort.
         * 
         */
        H.stableSort = function(arr, sortFunction) {
            var length = arr.length,
                sortValue,
                i;

            // Add index to each item
            for (i = 0; i < length; i++) {
                arr[i].safeI = i; // stable sort index
            }

            arr.sort(function(a, b) {
                sortValue = sortFunction(a, b);
                return sortValue === 0 ? a.safeI - b.safeI : sortValue;
            });

            // Remove index from items
            for (i = 0; i < length; i++) {
                delete arr[i].safeI; // stable sort index
            }
        };

        /**
         * Non-recursive method to find the lowest member of an array. `Math.min` raises
         * a maximum call stack size exceeded error in Chrome when trying to apply more
         * than 150.000 points. This method is slightly slower, but safe.
         *
         * @function #arrayMin
         * @memberOf  Highcharts
         * @param {Array} data An array of numbers.
         * @returns {Number} The lowest number.
         */
        H.arrayMin = function(data) {
            var i = data.length,
                min = data[0];

            while (i--) {
                if (data[i] < min) {
                    min = data[i];
                }
            }
            return min;
        };

        /**
         * Non-recursive method to find the lowest member of an array. `Math.max` raises
         * a maximum call stack size exceeded error in Chrome when trying to apply more
         * than 150.000 points. This method is slightly slower, but safe.
         *
         * @function #arrayMax
         * @memberOf  Highcharts
         * @param {Array} data - An array of numbers.
         * @returns {Number} The highest number.
         */
        H.arrayMax = function(data) {
            var i = data.length,
                max = data[0];

            while (i--) {
                if (data[i] > max) {
                    max = data[i];
                }
            }
            return max;
        };

        /**
         * Utility method that destroys any SVGElement instances that are properties on
         * the given object. It loops all properties and invokes destroy if there is a
         * destroy method. The property is then delete.
         *
         * @function #destroyObjectProperties
         * @memberOf Highcharts
         * @param {Object} obj - The object to destroy properties on.
         * @param {Object} [except] - Exception, do not destroy this property, only
         *    delete it.
         * 
         */
        H.destroyObjectProperties = function(obj, except) {
            H.objectEach(obj, function(val, n) {
                // If the object is non-null and destroy is defined
                if (val && val !== except && val.destroy) {
                    // Invoke the destroy
                    val.destroy();
                }

                // Delete the property from the object.
                delete obj[n];
            });
        };


        /**
         * Discard a HTML element by moving it to the bin and delete.
         *
         * @function #discardElement
         * @memberOf Highcharts
         * @param {HTMLDOMElement} element - The HTML node to discard.
         * 
         */
        H.discardElement = function(element) {
            var garbageBin = H.garbageBin;
            // create a garbage bin element, not part of the DOM
            if (!garbageBin) {
                garbageBin = H.createElement('div');
            }

            // move the node and empty bin
            if (element) {
                garbageBin.appendChild(element);
            }
            garbageBin.innerHTML = '';
        };

        /**
         * Fix JS round off float errors.
         *
         * @function #correctFloat
         * @memberOf Highcharts
         * @param {Number} num - A float number to fix.
         * @param {Number} [prec=14] - The precision.
         * @returns {Number} The corrected float number.
         */
        H.correctFloat = function(num, prec) {
            return parseFloat(
                num.toPrecision(prec || 14)
            );
        };

        /**
         * Set the global animation to either a given value, or fall back to the given
         * chart's animation option.
         *
         * @function #setAnimation
         * @memberOf Highcharts
         * @param {Boolean|Animation} animation - The animation object.
         * @param {Object} chart - The chart instance.
         * 
         * @todo This function always relates to a chart, and sets a property on the
         *        renderer, so it should be moved to the SVGRenderer.
         */
        H.setAnimation = function(animation, chart) {
            chart.renderer.globalAnimation = H.pick(
                animation,
                chart.options.chart.animation,
                true
            );
        };

        /**
         * Get the animation in object form, where a disabled animation is always
         * returned as `{ duration: 0 }`.
         *
         * @function #animObject
         * @memberOf Highcharts
         * @param {Boolean|AnimationOptions} animation - An animation setting. Can be an
         *        object with duration, complete and easing properties, or a boolean to
         *        enable or disable.
         * @returns {AnimationOptions} An object with at least a duration property.
         */
        H.animObject = function(animation) {
            return H.isObject(animation) ?
                H.merge(animation) : {
                    duration: animation ? 500 : 0
                };
        };

        /**
         * The time unit lookup
         */
        H.timeUnits = {
            millisecond: 1,
            second: 1000,
            minute: 60000,
            hour: 3600000,
            day: 24 * 3600000,
            week: 7 * 24 * 3600000,
            month: 28 * 24 * 3600000,
            year: 364 * 24 * 3600000
        };

        /**
         * Format a number and return a string based on input settings.
         *
         * @function #numberFormat
         * @memberOf Highcharts
         * @param {Number} number - The input number to format.
         * @param {Number} decimals - The amount of decimals. A value of -1 preserves
         *        the amount in the input number.
         * @param {String} [decimalPoint] - The decimal point, defaults to the one given
         *        in the lang options, or a dot.
         * @param {String} [thousandsSep] - The thousands separator, defaults to the one
         *        given in the lang options, or a space character.
         * @returns {String} The formatted number.
         *
         * @sample highcharts/members/highcharts-numberformat/ Custom number format
         */
        H.numberFormat = function(number, decimals, decimalPoint, thousandsSep) {
            number = +number || 0;
            decimals = +decimals;

            var lang = H.defaultOptions.lang,
                origDec = (number.toString().split('.')[1] || '').split('e')[0].length,
                strinteger,
                thousands,
                ret,
                roundedNumber,
                exponent = number.toString().split('e');

            if (decimals === -1) {
                // Preserve decimals. Not huge numbers (#3793).
                decimals = Math.min(origDec, 20);
            } else if (!H.isNumber(decimals)) {
                decimals = 2;
            }

            // Add another decimal to avoid rounding errors of float numbers. (#4573)
            // Then use toFixed to handle rounding.
            roundedNumber = (
                Math.abs(exponent[1] ? exponent[0] : number) +
                Math.pow(10, -Math.max(decimals, origDec) - 1)
            ).toFixed(decimals);

            // A string containing the positive integer component of the number
            strinteger = String(H.pInt(roundedNumber));

            // Leftover after grouping into thousands. Can be 0, 1 or 3.
            thousands = strinteger.length > 3 ? strinteger.length % 3 : 0;

            // Language
            decimalPoint = H.pick(decimalPoint, lang.decimalPoint);
            thousandsSep = H.pick(thousandsSep, lang.thousandsSep);

            // Start building the return
            ret = number < 0 ? '-' : '';

            // Add the leftover after grouping into thousands. For example, in the
            // number 42 000 000, this line adds 42.
            ret += thousands ? strinteger.substr(0, thousands) + thousandsSep : '';

            // Add the remaining thousands groups, joined by the thousands separator
            ret += strinteger
                .substr(thousands)
                .replace(/(\d{3})(?=\d)/g, '$1' + thousandsSep);

            // Add the decimal point and the decimal component
            if (decimals) {
                // Get the decimal component
                ret += decimalPoint + roundedNumber.slice(-decimals);
            }

            if (exponent[1]) {
                ret += 'e' + exponent[1];
            }

            return ret;
        };

        /**
         * Easing definition
         * @ignore
         * @param   {Number} pos Current position, ranging from 0 to 1.
         */
        Math.easeInOutSine = function(pos) {
            return -0.5 * (Math.cos(Math.PI * pos) - 1);
        };

        /**
         * Get the computed CSS value for given element and property, only for numerical
         * properties. For width and height, the dimension of the inner box (excluding
         * padding) is returned. Used for fitting the chart within the container.
         *
         * @function #getStyle
         * @memberOf Highcharts
         * @param {HTMLDOMElement} el - A HTML element.
         * @param {String} prop - The property name.
         * @param {Boolean} [toInt=true] - Parse to integer.
         * @returns {Number} - The numeric value.
         */
        H.getStyle = function(el, prop, toInt) {

            var style;

            // For width and height, return the actual inner pixel size (#4913)
            if (prop === 'width') {
                return Math.min(el.offsetWidth, el.scrollWidth) -
                    H.getStyle(el, 'padding-left') -
                    H.getStyle(el, 'padding-right');
            } else if (prop === 'height') {
                return Math.min(el.offsetHeight, el.scrollHeight) -
                    H.getStyle(el, 'padding-top') -
                    H.getStyle(el, 'padding-bottom');
            }

            if (!win.getComputedStyle) {
                // SVG not supported, forgot to load oldie.js?
                H.error(27, true);
            }

            // Otherwise, get the computed style
            style = win.getComputedStyle(el, undefined);
            if (style) {
                style = style.getPropertyValue(prop);
                if (H.pick(toInt, prop !== 'opacity')) {
                    style = H.pInt(style);
                }
            }
            return style;
        };

        /**
         * Search for an item in an array.
         *
         * @function #inArray
         * @memberOf Highcharts
         * @param {*} item - The item to search for.
         * @param {arr} arr - The array or node collection to search in.
         * @returns {Number} - The index within the array, or -1 if not found.
         */
        H.inArray = function(item, arr) {
            return (H.indexOfPolyfill || Array.prototype.indexOf).call(arr, item);
        };

        /**
         * Filter an array by a callback.
         *
         * @function #grep
         * @memberOf Highcharts
         * @param {Array} arr - The array to filter.
         * @param {Function} callback - The callback function. The function receives the
         *        item as the first argument. Return `true` if the item is to be
         *        preserved.
         * @returns {Array} - A new, filtered array.
         */
        H.grep = function(arr, callback) {
            return (H.filterPolyfill || Array.prototype.filter).call(arr, callback);
        };

        /**
         * Return the value of the first element in the array that satisfies the 
         * provided testing function.
         *
         * @function #find
         * @memberOf Highcharts
         * @param {Array} arr - The array to test.
         * @param {Function} callback - The callback function. The function receives the
         *        item as the first argument. Return `true` if this item satisfies the
         *        condition.
         * @returns {Mixed} - The value of the element.
         */
        H.find = Array.prototype.find ?
            function(arr, callback) {
                return arr.find(callback);
            } :
            // Legacy implementation. PhantomJS, IE <= 11 etc. #7223.
            function(arr, fn) {
                var i,
                    length = arr.length;

                for (i = 0; i < length; i++) {
                    if (fn(arr[i], i)) {
                        return arr[i];
                    }
                }
            };

        /**
         * Map an array by a callback.
         *
         * @function #map
         * @memberOf Highcharts
         * @param {Array} arr - The array to map.
         * @param {Function} fn - The callback function. Return the new value for the 
         *        new array.
         * @returns {Array} - A new array item with modified items.
         */
        H.map = function(arr, fn) {
            var results = [],
                i = 0,
                len = arr.length;

            for (; i < len; i++) {
                results[i] = fn.call(arr[i], arr[i], i, arr);
            }

            return results;
        };

        /**
         * Returns an array of a given object's own properties.
         *
         * @function #keys
         * @memberOf highcharts
         * @param {Object} obj - The object of which the properties are to be returned.
         * @returns {Array} - An array of strings that represents all the properties.
         */
        H.keys = function(obj) {
            return (H.keysPolyfill || Object.keys).call(undefined, obj);
        };

        /**
         * Reduce an array to a single value.
         *
         * @function #reduce
         * @memberOf Highcharts
         * @param {Array} arr - The array to reduce.
         * @param {Function} fn - The callback function. Return the reduced value. 
         *  Receives 4 arguments: Accumulated/reduced value, current value, current 
         *  array index, and the array.
         * @param {Mixed} initialValue - The initial value of the accumulator.
         * @returns {Mixed} - The reduced value.
         */
        H.reduce = function(arr, func, initialValue) {
            return (H.reducePolyfill || Array.prototype.reduce).call(
                arr,
                func,
                initialValue
            );
        };

        /**
         * Get the element's offset position, corrected for `overflow: auto`.
         *
         * @function #offset
         * @memberOf Highcharts
         * @param {HTMLDOMElement} el - The HTML element.
         * @returns {Object} An object containing `left` and `top` properties for the
         * position in the page.
         */
        H.offset = function(el) {
            var docElem = doc.documentElement,
                box = el.parentElement ? // IE11 throws Unspecified error in test suite
                el.getBoundingClientRect() : {
                    top: 0,
                    left: 0
                };

            return {
                top: box.top + (win.pageYOffset || docElem.scrollTop) -
                    (docElem.clientTop || 0),
                left: box.left + (win.pageXOffset || docElem.scrollLeft) -
                    (docElem.clientLeft || 0)
            };
        };

        /**
         * Stop running animation.
         *
         * @todo A possible extension to this would be to stop a single property, when
         * we want to continue animating others. Then assign the prop to the timer
         * in the Fx.run method, and check for the prop here. This would be an
         * improvement in all cases where we stop the animation from .attr. Instead of
         * stopping everything, we can just stop the actual attributes we're setting.
         *
         * @function #stop
         * @memberOf Highcharts
         * @param {SVGElement} el - The SVGElement to stop animation on.
         * @param {string} [prop] - The property to stop animating. If given, the stop
         *    method will stop a single property from animating, while others continue.
         * 
         */
        H.stop = function(el, prop) {

            var i = H.timers.length;

            // Remove timers related to this element (#4519)
            while (i--) {
                if (H.timers[i].elem === el && (!prop || prop === H.timers[i].prop)) {
                    H.timers[i].stopped = true; // #4667
                }
            }
        };

        /**
         * Iterate over an array.
         *
         * @function #each
         * @memberOf Highcharts
         * @param {Array} arr - The array to iterate over.
         * @param {Function} fn - The iterator callback. It passes three arguments:
         * * item - The array item.
         * * index - The item's index in the array.
         * * arr - The array that each is being applied to.
         * @param {Object} [ctx] The context.
         */
        H.each = function(arr, fn, ctx) { // modern browsers
            return (H.forEachPolyfill || Array.prototype.forEach).call(arr, fn, ctx);
        };

        /**
         * Iterate over object key pairs in an object.
         *
         * @function #objectEach
         * @memberOf Highcharts
         * @param  {Object}   obj - The object to iterate over.
         * @param  {Function} fn  - The iterator callback. It passes three arguments:
         * * value - The property value.
         * * key - The property key.
         * * obj - The object that objectEach is being applied to.
         * @param  {Object}   ctx The context
         */
        H.objectEach = function(obj, fn, ctx) {
            for (var key in obj) {
                if (obj.hasOwnProperty(key)) {
                    fn.call(ctx, obj[key], key, obj);
                }
            }
        };

        /**
         * Add an event listener.
         *
         * @function #addEvent
         * @memberOf Highcharts
         * @param {Object} el - The element or object to add a listener to. It can be a
         *        {@link HTMLDOMElement}, an {@link SVGElement} or any other object.
         * @param {String} type - The event type.
         * @param {Function} fn - The function callback to execute when the event is 
         *        fired.
         * @returns {Function} A callback function to remove the added event.
         */
        H.addEvent = function(el, type, fn) {

            var events,
                itemEvents,
                addEventListener = el.addEventListener || H.addEventListenerPolyfill;

            // If events are previously set directly on the prototype, pick them up 
            // and copy them over to the instance. Otherwise instance handlers would
            // be set on the prototype and apply to multiple charts in the page.
            if (el.hcEvents && !el.hasOwnProperty('hcEvents')) {
                itemEvents = {};
                H.objectEach(el.hcEvents, function(handlers, eventType) {
                    itemEvents[eventType] = handlers.slice(0);
                });
                el.hcEvents = itemEvents;
            }

            events = el.hcEvents = el.hcEvents || {};

            // Handle DOM events
            if (addEventListener) {
                addEventListener.call(el, type, fn, false);
            }

            if (!events[type]) {
                events[type] = [];
            }

            events[type].push(fn);

            // Return a function that can be called to remove this event.
            return function() {
                H.removeEvent(el, type, fn);
            };
        };

        /**
         * Remove an event that was added with {@link Highcharts#addEvent}.
         *
         * @function #removeEvent
         * @memberOf Highcharts
         * @param {Object} el - The element to remove events on.
         * @param {String} [type] - The type of events to remove. If undefined, all
         *        events are removed from the element.
         * @param {Function} [fn] - The specific callback to remove. If undefined, all
         *        events that match the element and optionally the type are removed.
         * 
         */
        H.removeEvent = function(el, type, fn) {

            var events,
                hcEvents = el.hcEvents,
                index;

            function removeOneEvent(type, fn) {
                var removeEventListener =
                    el.removeEventListener || H.removeEventListenerPolyfill;

                if (removeEventListener) {
                    removeEventListener.call(el, type, fn, false);
                }
            }

            function removeAllEvents() {
                var types,
                    len;

                if (!el.nodeName) {
                    return; // break on non-DOM events
                }

                if (type) {
                    types = {};
                    types[type] = true;
                } else {
                    types = hcEvents;
                }

                H.objectEach(types, function(val, n) {
                    if (hcEvents[n]) {
                        len = hcEvents[n].length;
                        while (len--) {
                            removeOneEvent(n, hcEvents[n][len]);
                        }
                    }
                });
            }

            if (hcEvents) {
                if (type) {
                    events = hcEvents[type] || [];
                    if (fn) {
                        index = H.inArray(fn, events);
                        if (index > -1) {
                            events.splice(index, 1);
                            hcEvents[type] = events;
                        }
                        removeOneEvent(type, fn);

                    } else {
                        removeAllEvents();
                        hcEvents[type] = [];
                    }
                } else {
                    removeAllEvents();
                    el.hcEvents = {};
                }
            }
        };

        /**
         * Fire an event that was registered with {@link Highcharts#addEvent}.
         *
         * @function #fireEvent
         * @memberOf Highcharts
         * @param {Object} el - The object to fire the event on. It can be a
         *        {@link HTMLDOMElement}, an {@link SVGElement} or any other object.
         * @param {String} type - The type of event.
         * @param {Object} [eventArguments] - Custom event arguments that are passed on
         *        as an argument to the event handler.
         * @param {Function} [defaultFunction] - The default function to execute if the 
         *        other listeners haven't returned false.
         * 
         */
        H.fireEvent = function(el, type, eventArguments, defaultFunction) {
            var e,
                hcEvents = el.hcEvents,
                events,
                len,
                i,
                fn;

            eventArguments = eventArguments || {};

            if (doc.createEvent && (el.dispatchEvent || el.fireEvent)) {
                e = doc.createEvent('Events');
                e.initEvent(type, true, true);

                H.extend(e, eventArguments);

                if (el.dispatchEvent) {
                    el.dispatchEvent(e);
                } else {
                    el.fireEvent(type, e);
                }

            } else if (hcEvents) {

                events = hcEvents[type] || [];
                len = events.length;

                if (!eventArguments.target) { // We're running a custom event

                    H.extend(eventArguments, {
                        // Attach a simple preventDefault function to skip default
                        // handler if called. The built-in defaultPrevented property is
                        // not overwritable (#5112)
                        preventDefault: function() {
                            eventArguments.defaultPrevented = true;
                        },
                        // Setting target to native events fails with clicking the
                        // zoom-out button in Chrome.
                        target: el,
                        // If the type is not set, we're running a custom event (#2297).
                        // If it is set, we're running a browser event, and setting it
                        // will cause en error in IE8 (#2465).		
                        type: type
                    });
                }


                for (i = 0; i < len; i++) {
                    fn = events[i];

                    // If the event handler return false, prevent the default handler
                    // from executing
                    if (fn && fn.call(el, eventArguments) === false) {
                        eventArguments.preventDefault();
                    }
                }
            }

            // Run the default if not prevented
            if (defaultFunction && !eventArguments.defaultPrevented) {
                defaultFunction(eventArguments);
            }
        };

        /**
         * An animation configuration. Animation configurations can also be defined as
         * booleans, where `false` turns off animation and `true` defaults to a duration
         * of 500ms.
         * @typedef {Object} AnimationOptions
         * @property {Number} duration - The animation duration in milliseconds.
         * @property {String} [easing] - The name of an easing function as defined on
         *     the `Math` object.
         * @property {Function} [complete] - A callback function to exectute when the
         *     animation finishes.
         * @property {Function} [step] - A callback function to execute on each step of
         *     each attribute or CSS property that's being animated. The first argument
         *     contains information about the animation and progress.
         */


        /**
         * The global animate method, which uses Fx to create individual animators.
         *
         * @function #animate
         * @memberOf Highcharts
         * @param {HTMLDOMElement|SVGElement} el - The element to animate.
         * @param {Object} params - An object containing key-value pairs of the
         *        properties to animate. Supports numeric as pixel-based CSS properties
         *        for HTML objects and attributes for SVGElements.
         * @param {AnimationOptions} [opt] - Animation options.
         */
        H.animate = function(el, params, opt) {
            var start,
                unit = '',
                end,
                fx,
                args;

            if (!H.isObject(opt)) { // Number or undefined/null
                args = arguments;
                opt = {
                    duration: args[2],
                    easing: args[3],
                    complete: args[4]
                };
            }
            if (!H.isNumber(opt.duration)) {
                opt.duration = 400;
            }
            opt.easing = typeof opt.easing === 'function' ?
                opt.easing :
                (Math[opt.easing] || Math.easeInOutSine);
            opt.curAnim = H.merge(params);

            H.objectEach(params, function(val, prop) {
                // Stop current running animation of this property
                H.stop(el, prop);

                fx = new H.Fx(el, opt, prop);
                end = null;

                if (prop === 'd') {
                    fx.paths = fx.initPath(
                        el,
                        el.d,
                        params.d
                    );
                    fx.toD = params.d;
                    start = 0;
                    end = 1;
                } else if (el.attr) {
                    start = el.attr(prop);
                } else {
                    start = parseFloat(H.getStyle(el, prop)) || 0;
                    if (prop !== 'opacity') {
                        unit = 'px';
                    }
                }

                if (!end) {
                    end = val;
                }
                if (end && end.match && end.match('px')) {
                    end = end.replace(/px/g, ''); // #4351
                }
                fx.run(start, end, unit);
            });
        };

        /**
         * Factory to create new series prototypes.
         *
         * @function #seriesType
         * @memberOf Highcharts
         *
         * @param {String} type - The series type name.
         * @param {String} parent - The parent series type name. Use `line` to inherit
         *        from the basic {@link Series} object.
         * @param {Object} options - The additional default options that is merged with
         *        the parent's options.
         * @param {Object} props - The properties (functions and primitives) to set on
         *        the new prototype.
         * @param {Object} [pointProps] - Members for a series-specific extension of the
         *        {@link Point} prototype if needed.
         * @returns {*} - The newly created prototype as extended from {@link Series}
         * or its derivatives.
         */
        // docs: add to API + extending Highcharts
        H.seriesType = function(type, parent, options, props, pointProps) {
            var defaultOptions = H.getOptions(),
                seriesTypes = H.seriesTypes;

            // Merge the options
            defaultOptions.plotOptions[type] = H.merge(
                defaultOptions.plotOptions[parent],
                options
            );

            // Create the class
            seriesTypes[type] = H.extendClass(seriesTypes[parent] ||
                function() {}, props);
            seriesTypes[type].prototype.type = type;

            // Create the point class if needed
            if (pointProps) {
                seriesTypes[type].prototype.pointClass =
                    H.extendClass(H.Point, pointProps);
            }

            return seriesTypes[type];
        };

        /**
         * Get a unique key for using in internal element id's and pointers. The key
         * is composed of a random hash specific to this Highcharts instance, and a 
         * counter.
         * @function #uniqueKey
         * @memberOf Highcharts
         * @return {string} The key.
         * @example
         * var id = H.uniqueKey(); // => 'highcharts-x45f6hp-0'
         */
        H.uniqueKey = (function() {

            var uniqueKeyHash = Math.random().toString(36).substring(2, 9),
                idCounter = 0;

            return function() {
                return 'highcharts-' + uniqueKeyHash + '-' + idCounter++;
            };
        }());

        /**
         * Register Highcharts as a plugin in jQuery
         */
        if (win.jQuery) {
            win.jQuery.fn.highcharts = function() {
                var args = [].slice.call(arguments);

                if (this[0]) { // this[0] is the renderTo div

                    // Create the chart
                    if (args[0]) {
                        new H[ // eslint-disable-line no-new
                            // Constructor defaults to Chart
                            H.isString(args[0]) ? args.shift() : 'Chart'
                        ](this[0], args[0], args[1]);
                        return this;
                    }

                    // When called without parameters or with the return argument,
                    // return an existing chart
                    return charts[H.attr(this[0], 'data-highcharts-chart')];
                }
            };
        }

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var each = H.each,
            isNumber = H.isNumber,
            map = H.map,
            merge = H.merge,
            pInt = H.pInt;

        /**
         * @typedef {string} ColorString
         * A valid color to be parsed and handled by Highcharts. Highcharts internally 
         * supports hex colors like `#ffffff`, rgb colors like `rgb(255,255,255)` and
         * rgba colors like `rgba(255,255,255,1)`. Other colors may be supported by the
         * browsers and displayed correctly, but Highcharts is not able to process them
         * and apply concepts like opacity and brightening.
         */
        /**
         * Handle color operations. The object methods are chainable.
         * @param {String} input The input color in either rbga or hex format
         */
        H.Color = function(input) {
            // Backwards compatibility, allow instanciation without new
            if (!(this instanceof H.Color)) {
                return new H.Color(input);
            }
            // Initialize
            this.init(input);
        };
        H.Color.prototype = {

            // Collection of parsers. This can be extended from the outside by pushing parsers
            // to Highcharts.Color.prototype.parsers.
            parsers: [{
                // RGBA color
                regex: /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/,
                parse: function(result) {
                    return [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];
                }
            }, {
                // RGB color
                regex: /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/,
                parse: function(result) {
                    return [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1];
                }
            }],

            // Collection of named colors. Can be extended from the outside by adding
            // colors to Highcharts.Color.prototype.names.
            names: {
                none: 'rgba(255,255,255,0)',
                white: '#ffffff',
                black: '#000000'
            },

            /**
             * Parse the input color to rgba array
             * @param {String} input
             */
            init: function(input) {
                var result,
                    rgba,
                    i,
                    parser,
                    len;

                this.input = input = this.names[
                    input && input.toLowerCase ?
                    input.toLowerCase() :
                    ''
                ] || input;

                // Gradients
                if (input && input.stops) {
                    this.stops = map(input.stops, function(stop) {
                        return new H.Color(stop[1]);
                    });

                    // Solid colors
                } else {

                    // Bitmasking as input[0] is not working for legacy IE.
                    if (input && input.charAt && input.charAt() === '#') {

                        len = input.length;
                        input = parseInt(input.substr(1), 16);

                        // Handle long-form, e.g. #AABBCC
                        if (len === 7) {

                            rgba = [
                                (input & 0xFF0000) >> 16,
                                (input & 0xFF00) >> 8,
                                (input & 0xFF),
                                1
                            ];

                            // Handle short-form, e.g. #ABC
                            // In short form, the value is assumed to be the same 
                            // for both nibbles for each component. e.g. #ABC = #AABBCC
                        } else if (len === 4) {

                            rgba = [
                                ((input & 0xF00) >> 4) | (input & 0xF00) >> 8,
                                ((input & 0xF0) >> 4) | (input & 0xF0),
                                ((input & 0xF) << 4) | (input & 0xF),
                                1
                            ];
                        }
                    }

                    // Otherwise, check regex parsers
                    if (!rgba) {
                        i = this.parsers.length;
                        while (i-- && !rgba) {
                            parser = this.parsers[i];
                            result = parser.regex.exec(input);
                            if (result) {
                                rgba = parser.parse(result);
                            }
                        }
                    }
                }
                this.rgba = rgba || [];
            },

            /**
             * Return the color a specified format
             * @param {String} format
             */
            get: function(format) {
                var input = this.input,
                    rgba = this.rgba,
                    ret;

                if (this.stops) {
                    ret = merge(input);
                    ret.stops = [].concat(ret.stops);
                    each(this.stops, function(stop, i) {
                        ret.stops[i] = [ret.stops[i][0], stop.get(format)];
                    });

                    // it's NaN if gradient colors on a column chart
                } else if (rgba && isNumber(rgba[0])) {
                    if (format === 'rgb' || (!format && rgba[3] === 1)) {
                        ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
                    } else if (format === 'a') {
                        ret = rgba[3];
                    } else {
                        ret = 'rgba(' + rgba.join(',') + ')';
                    }
                } else {
                    ret = input;
                }
                return ret;
            },

            /**
             * Brighten the color
             * @param {Number} alpha
             */
            brighten: function(alpha) {
                var i,
                    rgba = this.rgba;

                if (this.stops) {
                    each(this.stops, function(stop) {
                        stop.brighten(alpha);
                    });

                } else if (isNumber(alpha) && alpha !== 0) {
                    for (i = 0; i < 3; i++) {
                        rgba[i] += pInt(alpha * 255);

                        if (rgba[i] < 0) {
                            rgba[i] = 0;
                        }
                        if (rgba[i] > 255) {
                            rgba[i] = 255;
                        }
                    }
                }
                return this;
            },

            /**
             * Set the color's opacity to a given alpha value
             * @param {Number} alpha
             */
            setOpacity: function(alpha) {
                this.rgba[3] = alpha;
                return this;
            },

            /*
             * Return an intermediate color between two colors.
             *
             * @param  {Highcharts.Color} to
             *         The color object to tween to.
             * @param  {Number} pos
             *         The intermediate position, where 0 is the from color (current
             *         color item), and 1 is the `to` color.
             *
             * @return {String}
             *         The intermediate color in rgba notation.
             */
            tweenTo: function(to, pos) {
                // Check for has alpha, because rgba colors perform worse due to lack of
                // support in WebKit.
                var fromRgba = this.rgba,
                    toRgba = to.rgba,
                    hasAlpha,
                    ret;

                // Unsupported color, return to-color (#3920, #7034)
                if (!toRgba.length || !fromRgba || !fromRgba.length) {
                    ret = to.input || 'none';

                    // Interpolate
                } else {
                    hasAlpha = (toRgba[3] !== 1 || fromRgba[3] !== 1);
                    ret = (hasAlpha ? 'rgba(' : 'rgb(') +
                        Math.round(toRgba[0] + (fromRgba[0] - toRgba[0]) * (1 - pos)) +
                        ',' +
                        Math.round(toRgba[1] + (fromRgba[1] - toRgba[1]) * (1 - pos)) +
                        ',' +
                        Math.round(toRgba[2] + (fromRgba[2] - toRgba[2]) * (1 - pos)) +
                        (
                            hasAlpha ?
                            (
                                ',' +
                                (toRgba[3] + (fromRgba[3] - toRgba[3]) * (1 - pos))
                            ) :
                            ''
                        ) +
                        ')';
                }
                return ret;
            }
        };
        H.color = function(input) {
            return new H.Color(input);
        };

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var SVGElement,
            SVGRenderer,

            addEvent = H.addEvent,
            animate = H.animate,
            attr = H.attr,
            charts = H.charts,
            color = H.color,
            css = H.css,
            createElement = H.createElement,
            defined = H.defined,
            deg2rad = H.deg2rad,
            destroyObjectProperties = H.destroyObjectProperties,
            doc = H.doc,
            each = H.each,
            extend = H.extend,
            erase = H.erase,
            grep = H.grep,
            hasTouch = H.hasTouch,
            inArray = H.inArray,
            isArray = H.isArray,
            isFirefox = H.isFirefox,
            isMS = H.isMS,
            isObject = H.isObject,
            isString = H.isString,
            isWebKit = H.isWebKit,
            merge = H.merge,
            noop = H.noop,
            objectEach = H.objectEach,
            pick = H.pick,
            pInt = H.pInt,
            removeEvent = H.removeEvent,
            splat = H.splat,
            stop = H.stop,
            svg = H.svg,
            SVG_NS = H.SVG_NS,
            symbolSizes = H.symbolSizes,
            win = H.win;

        /**
         * @typedef {Object} SVGDOMElement - An SVG DOM element.
         */
        /**
         * The SVGElement prototype is a JavaScript wrapper for SVG elements used in the
         * rendering layer of Highcharts. Combined with the {@link
         * Highcharts.SVGRenderer} object, these prototypes allow freeform annotation
         * in the charts or even in HTML pages without instanciating a chart. The
         * SVGElement can also wrap HTML labels, when `text` or `label` elements are
         * created with the `useHTML` parameter.
         *
         * The SVGElement instances are created through factory functions on the 
         * {@link Highcharts.SVGRenderer} object, like
         * [rect]{@link Highcharts.SVGRenderer#rect}, [path]{@link
         * Highcharts.SVGRenderer#path}, [text]{@link Highcharts.SVGRenderer#text},
         * [label]{@link Highcharts.SVGRenderer#label}, [g]{@link
         * Highcharts.SVGRenderer#g} and more.
         *
         * @class Highcharts.SVGElement
         */
        SVGElement = H.SVGElement = function() {
            return this;
        };
        extend(SVGElement.prototype, /** @lends Highcharts.SVGElement.prototype */ {

            // Default base for animation
            opacity: 1,
            SVG_NS: SVG_NS,

            /**
             * For labels, these CSS properties are applied to the `text` node directly.
             *
             * @private
             * @type {Array.<string>}
             */
            textProps: ['direction', 'fontSize', 'fontWeight', 'fontFamily',
                'fontStyle', 'color', 'lineHeight', 'width', 'textAlign',
                'textDecoration', 'textOverflow', 'textOutline'
            ],

            /**
             * Initialize the SVG renderer. This function only exists to make the
             * initiation process overridable. It should not be called directly.
             *
             * @param  {SVGRenderer} renderer
             *         The SVGRenderer instance to initialize to.
             * @param  {String} nodeName
             *         The SVG node name.
             * 
             */
            init: function(renderer, nodeName) {

                /** 
                 * The primary DOM node. Each `SVGElement` instance wraps a main DOM
                 * node, but may also represent more nodes.
                 *
                 * @name  element
                 * @memberOf SVGElement
                 * @type {SVGDOMNode|HTMLDOMNode}
                 */
                this.element = nodeName === 'span' ?
                    createElement(nodeName) :
                    doc.createElementNS(this.SVG_NS, nodeName);

                /**
                 * The renderer that the SVGElement belongs to.
                 *
                 * @name renderer
                 * @memberOf SVGElement
                 * @type {SVGRenderer}
                 */
                this.renderer = renderer;
            },

            /**
             * Animate to given attributes or CSS properties.
             * 
             * @param {SVGAttributes} params SVG attributes or CSS to animate.
             * @param {AnimationOptions} [options] Animation options.
             * @param {Function} [complete] Function to perform at the end of animation.
             *
             * @sample highcharts/members/element-on/
             *         Setting some attributes by animation
             * 
             * @returns {SVGElement} Returns the SVGElement for chaining.
             */
            animate: function(params, options, complete) {
                var animOptions = H.animObject(
                    pick(options, this.renderer.globalAnimation, true)
                );
                if (animOptions.duration !== 0) {
                    // allows using a callback with the global animation without
                    // overwriting it
                    if (complete) {
                        animOptions.complete = complete;
                    }
                    animate(this, params, animOptions);
                } else {
                    this.attr(params, null, complete);
                    if (animOptions.step) {
                        animOptions.step.call(this);
                    }
                }
                return this;
            },

            /**
             * @typedef {Object} GradientOptions
             * @property {Object} linearGradient Holds an object that defines the start
             *    position and the end position relative to the shape.
             * @property {Number} linearGradient.x1 Start horizontal position of the
             *    gradient. Ranges 0-1.
             * @property {Number} linearGradient.x2 End horizontal position of the
             *    gradient. Ranges 0-1.
             * @property {Number} linearGradient.y1 Start vertical position of the
             *    gradient. Ranges 0-1.
             * @property {Number} linearGradient.y2 End vertical position of the
             *    gradient. Ranges 0-1.
             * @property {Object} radialGradient Holds an object that defines the center
             *    position and the radius.
             * @property {Number} radialGradient.cx Center horizontal position relative
             *    to the shape. Ranges 0-1.
             * @property {Number} radialGradient.cy Center vertical position relative
             *    to the shape. Ranges 0-1.
             * @property {Number} radialGradient.r Radius relative to the shape. Ranges
             *    0-1.
             * @property {Array.<Array>} stops The first item in each tuple is the
             *    position in the gradient, where 0 is the start of the gradient and 1
             *    is the end of the gradient. Multiple stops can be applied. The second
             *    item is the color for each stop. This color can also be given in the
             *    rgba format.
             *
             * @example
             * // Linear gradient used as a color option
             * color: {
             *     linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
             *         stops: [
             *             [0, '#003399'], // start
             *             [0.5, '#ffffff'], // middle
             *             [1, '#3366AA'] // end
             *         ]
             *     }
             * }
             */
            /**
             * Build and apply an SVG gradient out of a common JavaScript configuration
             * object. This function is called from the attribute setters.
             *
             * @private
             * @param {GradientOptions} color The gradient options structure.
             * @param {string} prop The property to apply, can either be `fill` or
             * `stroke`. 
             * @param {SVGDOMElement} elem SVG DOM element to apply the gradient on.
             */
            colorGradient: function(color, prop, elem) {
                var renderer = this.renderer,
                    colorObject,
                    gradName,
                    gradAttr,
                    radAttr,
                    gradients,
                    gradientObject,
                    stops,
                    stopColor,
                    stopOpacity,
                    radialReference,
                    id,
                    key = [],
                    value;

                // Apply linear or radial gradients
                if (color.radialGradient) {
                    gradName = 'radialGradient';
                } else if (color.linearGradient) {
                    gradName = 'linearGradient';
                }

                if (gradName) {
                    gradAttr = color[gradName];
                    gradients = renderer.gradients;
                    stops = color.stops;
                    radialReference = elem.radialReference;

                    // Keep < 2.2 kompatibility
                    if (isArray(gradAttr)) {
                        color[gradName] = gradAttr = {
                            x1: gradAttr[0],
                            y1: gradAttr[1],
                            x2: gradAttr[2],
                            y2: gradAttr[3],
                            gradientUnits: 'userSpaceOnUse'
                        };
                    }

                    // Correct the radial gradient for the radial reference system
                    if (
                        gradName === 'radialGradient' &&
                        radialReference &&
                        !defined(gradAttr.gradientUnits)
                    ) {
                        radAttr = gradAttr; // Save the radial attributes for updating
                        gradAttr = merge(
                            gradAttr,
                            renderer.getRadialAttr(radialReference, radAttr), {
                                gradientUnits: 'userSpaceOnUse'
                            }
                        );
                    }

                    // Build the unique key to detect whether we need to create a new
                    // element (#1282)
                    objectEach(gradAttr, function(val, n) {
                        if (n !== 'id') {
                            key.push(n, val);
                        }
                    });
                    objectEach(stops, function(val) {
                        key.push(val);
                    });
                    key = key.join(',');

                    // Check if a gradient object with the same config object is created
                    // within this renderer
                    if (gradients[key]) {
                        id = gradients[key].attr('id');

                    } else {

                        // Set the id and create the element
                        gradAttr.id = id = H.uniqueKey();
                        gradients[key] = gradientObject =
                            renderer.createElement(gradName)
                            .attr(gradAttr)
                            .add(renderer.defs);

                        gradientObject.radAttr = radAttr;

                        // The gradient needs to keep a list of stops to be able to
                        // destroy them
                        gradientObject.stops = [];
                        each(stops, function(stop) {
                            var stopObject;
                            if (stop[1].indexOf('rgba') === 0) {
                                colorObject = H.color(stop[1]);
                                stopColor = colorObject.get('rgb');
                                stopOpacity = colorObject.get('a');
                            } else {
                                stopColor = stop[1];
                                stopOpacity = 1;
                            }
                            stopObject = renderer.createElement('stop').attr({
                                offset: stop[0],
                                'stop-color': stopColor,
                                'stop-opacity': stopOpacity
                            }).add(gradientObject);

                            // Add the stop element to the gradient
                            gradientObject.stops.push(stopObject);
                        });
                    }

                    // Set the reference to the gradient object
                    value = 'url(' + renderer.url + '#' + id + ')';
                    elem.setAttribute(prop, value);
                    elem.gradient = key;

                    // Allow the color to be concatenated into tooltips formatters etc.
                    // (#2995)
                    color.toString = function() {
                        return value;
                    };
                }
            },

            /**
             * Apply a text outline through a custom CSS property, by copying the text
             * element and apply stroke to the copy. Used internally. Contrast checks
             * at http://jsfiddle.net/highcharts/43soe9m1/2/ .
             *
             * @private
             * @param {String} textOutline A custom CSS `text-outline` setting, defined
             *    by `width color`. 
             * @example
             * // Specific color
             * text.css({
             *    textOutline: '1px black'
             * });
             * // Automatic contrast
             * text.css({
             *    color: '#000000', // black text
             *    textOutline: '1px contrast' // => white outline
             * });
             */
            applyTextOutline: function(textOutline) {
                var elem = this.element,
                    tspans,
                    tspan,
                    hasContrast = textOutline.indexOf('contrast') !== -1,
                    styles = {},
                    color,
                    strokeWidth,
                    firstRealChild,
                    i;

                // When the text shadow is set to contrast, use dark stroke for light
                // text and vice versa.
                if (hasContrast) {
                    styles.textOutline = textOutline = textOutline.replace(
                        /contrast/g,
                        this.renderer.getContrast(elem.style.fill)
                    );
                }

                // Extract the stroke width and color
                textOutline = textOutline.split(' ');
                color = textOutline[textOutline.length - 1];
                strokeWidth = textOutline[0];

                if (strokeWidth && strokeWidth !== 'none' && H.svg) {

                    this.fakeTS = true; // Fake text shadow

                    tspans = [].slice.call(elem.getElementsByTagName('tspan'));

                    // In order to get the right y position of the clone,
                    // copy over the y setter
                    this.ySetter = this.xSetter;

                    // Since the stroke is applied on center of the actual outline, we
                    // need to double it to get the correct stroke-width outside the 
                    // glyphs.
                    strokeWidth = strokeWidth.replace(
                        /(^[\d\.]+)(.*?)$/g,
                        function(match, digit, unit) {
                            return (2 * digit) + unit;
                        }
                    );

                    // Remove shadows from previous runs. Iterate from the end to
                    // support removing items inside the cycle (#6472).
                    i = tspans.length;
                    while (i--) {
                        tspan = tspans[i];
                        if (tspan.getAttribute('class') === 'highcharts-text-outline') {
                            // Remove then erase
                            erase(tspans, elem.removeChild(tspan));
                        }
                    }

                    // For each of the tspans, create a stroked copy behind it.
                    firstRealChild = elem.firstChild;
                    each(tspans, function(tspan, y) {
                        var clone;

                        // Let the first line start at the correct X position
                        if (y === 0) {
                            tspan.setAttribute('x', elem.getAttribute('x'));
                            y = elem.getAttribute('y');
                            tspan.setAttribute('y', y || 0);
                            if (y === null) {
                                elem.setAttribute('y', 0);
                            }
                        }

                        // Create the clone and apply outline properties
                        clone = tspan.cloneNode(1);
                        attr(clone, {
                            'class': 'highcharts-text-outline',
                            'fill': color,
                            'stroke': color,
                            'stroke-width': strokeWidth,
                            'stroke-linejoin': 'round'
                        });
                        elem.insertBefore(clone, firstRealChild);
                    });
                }
            },

            /**
             *
             * @typedef {Object} SVGAttributes An object of key-value pairs for SVG
             *   attributes. Attributes in Highcharts elements for the most parts
             *   correspond to SVG, but some are specific to Highcharts, like `zIndex`,
             *   `rotation`, `rotationOriginX`, `rotationOriginY`, `translateX`,
             *   `translateY`, `scaleX` and `scaleY`. SVG attributes containing a hyphen
             *   are _not_ camel-cased, they should be quoted to preserve the hyphen.
             *   
             * @example
             * {
             *     'stroke': '#ff0000', // basic
             *     'stroke-width': 2, // hyphenated
             *     'rotation': 45 // custom
             *     'd': ['M', 10, 10, 'L', 30, 30, 'z'] // path definition, note format
             * }
             */
            /**
             * Apply native and custom attributes to the SVG elements.
             * 
             * In order to set the rotation center for rotation, set x and y to 0 and
             * use `translateX` and `translateY` attributes to position the element
             * instead.
             *
             * Attributes frequently used in Highcharts are `fill`, `stroke`,
             * `stroke-width`.
             *
             * @param {SVGAttributes|String} hash - The native and custom SVG
             *    attributes. 
             * @param {string} [val] - If the type of the first argument is `string`, 
             *    the second can be a value, which will serve as a single attribute
             *    setter. If the first argument is a string and the second is undefined,
             *    the function serves as a getter and the current value of the property
             *    is returned.
             * @param {Function} [complete] - A callback function to execute after
             *    setting the attributes. This makes the function compliant and
             *    interchangeable with the {@link SVGElement#animate} function.
             * @param {boolean} [continueAnimation=true] Used internally when `.attr` is
             *    called as part of an animation step. Otherwise, calling `.attr` for an
             *    attribute will stop animation for that attribute.
             *    
             * @returns {SVGElement|string|number} If used as a setter, it returns the 
             *    current {@link SVGElement} so the calls can be chained. If used as a 
             *    getter, the current value of the attribute is returned.
             *
             * @sample highcharts/members/renderer-rect/
             *         Setting some attributes
             * 
             * @example
             * // Set multiple attributes
             * element.attr({
             *     stroke: 'red',
             *     fill: 'blue',
             *     x: 10,
             *     y: 10
             * });
             *
             * // Set a single attribute
             * element.attr('stroke', 'red');
             *
             * // Get an attribute
             * element.attr('stroke'); // => 'red'
             * 
             */
            attr: function(hash, val, complete, continueAnimation) {
                var key,
                    element = this.element,
                    hasSetSymbolSize,
                    ret = this,
                    skipAttr,
                    setter;

                // single key-value pair
                if (typeof hash === 'string' && val !== undefined) {
                    key = hash;
                    hash = {};
                    hash[key] = val;
                }

                // used as a getter: first argument is a string, second is undefined
                if (typeof hash === 'string') {
                    ret = (this[hash + 'Getter'] || this._defaultGetter).call(
                        this,
                        hash,
                        element
                    );

                    // setter
                } else {

                    objectEach(hash, function eachAttribute(val, key) {
                        skipAttr = false;

                        // Unless .attr is from the animator update, stop current
                        // running animation of this property
                        if (!continueAnimation) {
                            stop(this, key);
                        }

                        // Special handling of symbol attributes
                        if (
                            this.symbolName &&
                            /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)$/
                            .test(key)
                        ) {
                            if (!hasSetSymbolSize) {
                                this.symbolAttr(hash);
                                hasSetSymbolSize = true;
                            }
                            skipAttr = true;
                        }

                        if (this.rotation && (key === 'x' || key === 'y')) {
                            this.doTransform = true;
                        }

                        if (!skipAttr) {
                            setter = this[key + 'Setter'] || this._defaultSetter;
                            setter.call(this, val, key, element);


                            // Let the shadow follow the main element
                            if (
                                this.shadows &&
                                /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/
                                .test(key)
                            ) {
                                this.updateShadows(key, val, setter);
                            }

                        }
                    }, this);

                    this.afterSetters();
                }

                // In accordance with animate, run a complete callback
                if (complete) {
                    complete();
                }

                return ret;
            },

            /**
             * This method is executed in the end of `attr()`, after setting all
             * attributes in the hash. In can be used to efficiently consolidate
             * multiple attributes in one SVG property -- e.g., translate, rotate and
             * scale are merged in one "transform" attribute in the SVG node.
             *
             * @private
             */
            afterSetters: function() {
                // Update transform. Do this outside the loop to prevent redundant
                // updating for batch setting of attributes.
                if (this.doTransform) {
                    this.updateTransform();
                    this.doTransform = false;
                }
            },


            /**
             * Update the shadow elements with new attributes.
             *
             * @private
             * @param {String} key - The attribute name.
             * @param {String|Number} value - The value of the attribute.
             * @param {Function} setter - The setter function, inherited from the
             *   parent wrapper
             * 
             */
            updateShadows: function(key, value, setter) {
                var shadows = this.shadows,
                    i = shadows.length;

                while (i--) {
                    setter.call(
                        shadows[i],
                        key === 'height' ?
                        Math.max(value - (shadows[i].cutHeight || 0), 0) :
                        key === 'd' ? this.d : value,
                        key,
                        shadows[i]
                    );
                }
            },


            /**
             * Add a class name to an element.
             *
             * @param {string} className - The new class name to add.
             * @param {boolean} [replace=false] - When true, the existing class name(s)
             *    will be overwritten with the new one. When false, the new one is
             *    added.
             * @returns {SVGElement} Return the SVG element for chainability.
             */
            addClass: function(className, replace) {
                var currentClassName = this.attr('class') || '';
                if (currentClassName.indexOf(className) === -1) {
                    if (!replace) {
                        className =
                            (currentClassName + (currentClassName ? ' ' : '') +
                                className).replace('  ', ' ');
                    }
                    this.attr('class', className);
                }

                return this;
            },

            /**
             * Check if an element has the given class name.
             * @param  {string} className
             *         The class name to check for.
             * @return {Boolean}
             *         Whether the class name is found.
             */
            hasClass: function(className) {
                return inArray(
                    className,
                    (this.attr('class') || '').split(' ')
                ) !== -1;
            },

            /**
             * Remove a class name from the element.
             * @param  {String|RegExp} className The class name to remove.
             * @return {SVGElement} Returns the SVG element for chainability.
             */
            removeClass: function(className) {
                return this.attr(
                    'class',
                    (this.attr('class') || '').replace(className, '')
                );
            },

            /**
             * If one of the symbol size affecting parameters are changed,
             * check all the others only once for each call to an element's
             * .attr() method
             * @param {Object} hash - The attributes to set.
             * @private
             */
            symbolAttr: function(hash) {
                var wrapper = this;

                each([
                    'x',
                    'y',
                    'r',
                    'start',
                    'end',
                    'width',
                    'height',
                    'innerR',
                    'anchorX',
                    'anchorY'
                ], function(key) {
                    wrapper[key] = pick(hash[key], wrapper[key]);
                });

                wrapper.attr({
                    d: wrapper.renderer.symbols[wrapper.symbolName](
                        wrapper.x,
                        wrapper.y,
                        wrapper.width,
                        wrapper.height,
                        wrapper
                    )
                });
            },

            /**
             * Apply a clipping rectangle to this element.
             * 
             * @param {ClipRect} [clipRect] - The clipping rectangle. If skipped, the
             *    current clip is removed.
             * @returns {SVGElement} Returns the SVG element to allow chaining.
             */
            clip: function(clipRect) {
                return this.attr(
                    'clip-path',
                    clipRect ?
                    'url(' + this.renderer.url + '#' + clipRect.id + ')' :
                    'none'
                );
            },

            /**
             * Calculate the coordinates needed for drawing a rectangle crisply and
             * return the calculated attributes.
             * 
             * @param {Object} rect - A rectangle.
             * @param {number} rect.x - The x position.
             * @param {number} rect.y - The y position.
             * @param {number} rect.width - The width.
             * @param {number} rect.height - The height.
             * @param {number} [strokeWidth] - The stroke width to consider when
             *    computing crisp positioning. It can also be set directly on the rect
             *    parameter.
             *
             * @returns {{x: Number, y: Number, width: Number, height: Number}} The
             *    modified rectangle arguments.
             */
            crisp: function(rect, strokeWidth) {

                var wrapper = this,
                    attribs = {},
                    normalizer;

                strokeWidth = strokeWidth || rect.strokeWidth || 0;
                // Math.round because strokeWidth can sometimes have roundoff errors
                normalizer = Math.round(strokeWidth) % 2 / 2;

                // normalize for crisp edges
                rect.x = Math.floor(rect.x || wrapper.x || 0) + normalizer;
                rect.y = Math.floor(rect.y || wrapper.y || 0) + normalizer;
                rect.width = Math.floor(
                    (rect.width || wrapper.width || 0) - 2 * normalizer
                );
                rect.height = Math.floor(
                    (rect.height || wrapper.height || 0) - 2 * normalizer
                );
                if (defined(rect.strokeWidth)) {
                    rect.strokeWidth = strokeWidth;
                }

                objectEach(rect, function(val, key) {
                    if (wrapper[key] !== val) { // only set attribute if changed
                        wrapper[key] = attribs[key] = val;
                    }
                });

                return attribs;
            },

            /**
             * Set styles for the element. In addition to CSS styles supported by 
             * native SVG and HTML elements, there are also some custom made for 
             * Highcharts, like `width`, `ellipsis` and `textOverflow` for SVG text
             * elements.
             * @param {CSSObject} styles The new CSS styles.
             * @returns {SVGElement} Return the SVG element for chaining.
             *
             * @sample highcharts/members/renderer-text-on-chart/
             *         Styled text
             */
            css: function(styles) {
                var oldStyles = this.styles,
                    newStyles = {},
                    elem = this.element,
                    textWidth,
                    serializedCss = '',
                    hyphenate,
                    hasNew = !oldStyles,
                    // These CSS properties are interpreted internally by the SVG
                    // renderer, but are not supported by SVG and should not be added to
                    // the DOM. In styled mode, no CSS should find its way to the DOM
                    // whatsoever (#6173, #6474).
                    svgPseudoProps = ['textOutline', 'textOverflow', 'width'];

                // convert legacy
                if (styles && styles.color) {
                    styles.fill = styles.color;
                }

                // Filter out existing styles to increase performance (#2640)
                if (oldStyles) {
                    objectEach(styles, function(style, n) {
                        if (style !== oldStyles[n]) {
                            newStyles[n] = style;
                            hasNew = true;
                        }
                    });
                }
                if (hasNew) {

                    // Merge the new styles with the old ones
                    if (oldStyles) {
                        styles = extend(
                            oldStyles,
                            newStyles
                        );
                    }

                    // Get the text width from style
                    textWidth = this.textWidth = (
                        styles &&
                        styles.width &&
                        styles.width !== 'auto' &&
                        elem.nodeName.toLowerCase() === 'text' &&
                        pInt(styles.width)
                    );

                    // store object
                    this.styles = styles;

                    if (textWidth && (!svg && this.renderer.forExport)) {
                        delete styles.width;
                    }

                    // serialize and set style attribute
                    if (isMS && !svg) {
                        css(this.element, styles);
                    } else {
                        hyphenate = function(a, b) {
                            return '-' + b.toLowerCase();
                        };
                        objectEach(styles, function(style, n) {
                            if (inArray(n, svgPseudoProps) === -1) {
                                serializedCss +=
                                    n.replace(/([A-Z])/g, hyphenate) + ':' +
                                    style + ';';
                            }
                        });
                        if (serializedCss) {
                            attr(elem, 'style', serializedCss); // #1881
                        }
                    }


                    if (this.added) {

                        // Rebuild text after added. Cache mechanisms in the buildText
                        // will prevent building if there are no significant changes.
                        if (this.element.nodeName === 'text') {
                            this.renderer.buildText(this);
                        }

                        // Apply text outline after added
                        if (styles && styles.textOutline) {
                            this.applyTextOutline(styles.textOutline);
                        }
                    }
                }

                return this;
            },


            /**
             * Get the current stroke width. In classic mode, the setter registers it 
             * directly on the element.
             * @returns {number} The stroke width in pixels.
             * @ignore
             */
            strokeWidth: function() {
                return this['stroke-width'] || 0;
            },


            /**
             * Add an event listener. This is a simple setter that replaces all other
             * events of the same type, opposed to the {@link Highcharts#addEvent}
             * function.
             * @param {string} eventType - The event type. If the type is `click`, 
             *    Highcharts will internally translate it to a `touchstart` event on 
             *    touch devices, to prevent the browser from waiting for a click event
             *    from firing.
             * @param {Function} handler - The handler callback.
             * @returns {SVGElement} The SVGElement for chaining.
             *
             * @sample highcharts/members/element-on/
             *         A clickable rectangle
             */
            on: function(eventType, handler) {
                var svgElement = this,
                    element = svgElement.element;

                // touch
                if (hasTouch && eventType === 'click') {
                    element.ontouchstart = function(e) {
                        svgElement.touchEventFired = Date.now(); // #2269
                        e.preventDefault();
                        handler.call(element, e);
                    };
                    element.onclick = function(e) {
                        if (win.navigator.userAgent.indexOf('Android') === -1 ||
                            Date.now() - (svgElement.touchEventFired || 0) > 1100) {
                            handler.call(element, e);
                        }
                    };
                } else {
                    // simplest possible event model for internal use
                    element['on' + eventType] = handler;
                }
                return this;
            },

            /**
             * Set the coordinates needed to draw a consistent radial gradient across
             * a shape regardless of positioning inside the chart. Used on pie slices
             * to make all the slices have the same radial reference point.
             *
             * @param {Array} coordinates The center reference. The format is
             *    `[centerX, centerY, diameter]` in pixels.
             * @returns {SVGElement} Returns the SVGElement for chaining.
             */
            setRadialReference: function(coordinates) {
                var existingGradient = this.renderer.gradients[this.element.gradient];

                this.element.radialReference = coordinates;

                // On redrawing objects with an existing gradient, the gradient needs
                // to be repositioned (#3801)
                if (existingGradient && existingGradient.radAttr) {
                    existingGradient.animate(
                        this.renderer.getRadialAttr(
                            coordinates,
                            existingGradient.radAttr
                        )
                    );
                }

                return this;
            },

            /**
             * Move an object and its children by x and y values.
             * 
             * @param {number} x - The x value.
             * @param {number} y - The y value.
             */
            translate: function(x, y) {
                return this.attr({
                    translateX: x,
                    translateY: y
                });
            },

            /**
             * Invert a group, rotate and flip. This is used internally on inverted 
             * charts, where the points and graphs are drawn as if not inverted, then
             * the series group elements are inverted.
             *
             * @param  {boolean} inverted
             *         Whether to invert or not. An inverted shape can be un-inverted by
             *         setting it to false.
             * @return {SVGElement}
             *         Return the SVGElement for chaining.
             */
            invert: function(inverted) {
                var wrapper = this;
                wrapper.inverted = inverted;
                wrapper.updateTransform();
                return wrapper;
            },

            /**
             * Update the transform attribute based on internal properties. Deals with
             * the custom `translateX`, `translateY`, `rotation`, `scaleX` and `scaleY`
             * attributes and updates the SVG `transform` attribute.
             * @private
             * 
             */
            updateTransform: function() {
                var wrapper = this,
                    translateX = wrapper.translateX || 0,
                    translateY = wrapper.translateY || 0,
                    scaleX = wrapper.scaleX,
                    scaleY = wrapper.scaleY,
                    inverted = wrapper.inverted,
                    rotation = wrapper.rotation,
                    matrix = wrapper.matrix,
                    element = wrapper.element,
                    transform;

                // Flipping affects translate as adjustment for flipping around the
                // group's axis
                if (inverted) {
                    translateX += wrapper.width;
                    translateY += wrapper.height;
                }

                // Apply translate. Nearly all transformed elements have translation,
                // so instead of checking for translate = 0, do it always (#1767,
                // #1846).
                transform = ['translate(' + translateX + ',' + translateY + ')'];

                // apply matrix
                if (defined(matrix)) {
                    transform.push(
                        'matrix(' + matrix.join(',') + ')'
                    );
                }

                // apply rotation
                if (inverted) {
                    transform.push('rotate(90) scale(-1,1)');
                } else if (rotation) { // text rotation
                    transform.push(
                        'rotate(' + rotation + ' ' +
                        pick(this.rotationOriginX, element.getAttribute('x'), 0) +
                        ' ' +
                        pick(this.rotationOriginY, element.getAttribute('y') || 0) + ')'
                    );
                }

                // apply scale
                if (defined(scaleX) || defined(scaleY)) {
                    transform.push(
                        'scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')'
                    );
                }

                if (transform.length) {
                    element.setAttribute('transform', transform.join(' '));
                }
            },

            /**
             * Bring the element to the front. Alternatively, a new zIndex can be set.
             *
             * @returns {SVGElement} Returns the SVGElement for chaining.
             *
             * @sample highcharts/members/element-tofront/
             *         Click an element to bring it to front
             */
            toFront: function() {
                var element = this.element;
                element.parentNode.appendChild(element);
                return this;
            },


            /**
             * Align the element relative to the chart or another box.
             * 
             * @param {Object} [alignOptions] The alignment options. The function can be
             *   called without this parameter in order to re-align an element after the
             *   box has been updated.
             * @param {string} [alignOptions.align=left] Horizontal alignment. Can be
             *   one of `left`, `center` and `right`.
             * @param {string} [alignOptions.verticalAlign=top] Vertical alignment. Can
             *   be one of `top`, `middle` and `bottom`.
             * @param {number} [alignOptions.x=0] Horizontal pixel offset from
             *   alignment.
             * @param {number} [alignOptions.y=0] Vertical pixel offset from alignment.
             * @param {Boolean} [alignByTranslate=false] Use the `transform` attribute
             *   with translateX and translateY custom attributes to align this elements
             *   rather than `x` and `y` attributes.
             * @param {String|Object} box The box to align to, needs a width and height.
             *   When the box is a string, it refers to an object in the Renderer. For
             *   example, when box is `spacingBox`, it refers to `Renderer.spacingBox`
             *   which holds `width`, `height`, `x` and `y` properties.
             * @returns {SVGElement} Returns the SVGElement for chaining.
             */
            align: function(alignOptions, alignByTranslate, box) {
                var align,
                    vAlign,
                    x,
                    y,
                    attribs = {},
                    alignTo,
                    renderer = this.renderer,
                    alignedObjects = renderer.alignedObjects,
                    alignFactor,
                    vAlignFactor;

                // First call on instanciate
                if (alignOptions) {
                    this.alignOptions = alignOptions;
                    this.alignByTranslate = alignByTranslate;
                    if (!box || isString(box)) {
                        this.alignTo = alignTo = box || 'renderer';
                        // prevent duplicates, like legendGroup after resize
                        erase(alignedObjects, this);
                        alignedObjects.push(this);
                        box = null; // reassign it below
                    }

                    // When called on resize, no arguments are supplied
                } else {
                    alignOptions = this.alignOptions;
                    alignByTranslate = this.alignByTranslate;
                    alignTo = this.alignTo;
                }

                box = pick(box, renderer[alignTo], renderer);

                // Assign variables
                align = alignOptions.align;
                vAlign = alignOptions.verticalAlign;
                x = (box.x || 0) + (alignOptions.x || 0); // default: left align
                y = (box.y || 0) + (alignOptions.y || 0); // default: top align

                // Align
                if (align === 'right') {
                    alignFactor = 1;
                } else if (align === 'center') {
                    alignFactor = 2;
                }
                if (alignFactor) {
                    x += (box.width - (alignOptions.width || 0)) / alignFactor;
                }
                attribs[alignByTranslate ? 'translateX' : 'x'] = Math.round(x);


                // Vertical align
                if (vAlign === 'bottom') {
                    vAlignFactor = 1;
                } else if (vAlign === 'middle') {
                    vAlignFactor = 2;
                }
                if (vAlignFactor) {
                    y += (box.height - (alignOptions.height || 0)) / vAlignFactor;
                }
                attribs[alignByTranslate ? 'translateY' : 'y'] = Math.round(y);

                // Animate only if already placed
                this[this.placed ? 'animate' : 'attr'](attribs);
                this.placed = true;
                this.alignAttr = attribs;

                return this;
            },

            /**
             * Get the bounding box (width, height, x and y) for the element. Generally
             * used to get rendered text size. Since this is called a lot in charts,
             * the results are cached based on text properties, in order to save DOM
             * traffic. The returned bounding box includes the rotation, so for example
             * a single text line of rotation 90 will report a greater height, and a
             * width corresponding to the line-height.
             *
             * @param {boolean} [reload] Skip the cache and get the updated DOM bouding
             *   box.
             * @param {number} [rot] Override the element's rotation. This is internally
             *   used on axis labels with a value of 0 to find out what the bounding box
             *   would be have been if it were not rotated.
             * @returns {Object} The bounding box with `x`, `y`, `width` and `height`
             * properties.
             *
             * @sample highcharts/members/renderer-on-chart/
             *         Draw a rectangle based on a text's bounding box
             */
            getBBox: function(reload, rot) {
                var wrapper = this,
                    bBox, // = wrapper.bBox,
                    renderer = wrapper.renderer,
                    width,
                    height,
                    rotation,
                    rad,
                    element = wrapper.element,
                    styles = wrapper.styles,
                    fontSize,
                    textStr = wrapper.textStr,
                    toggleTextShadowShim,
                    cache = renderer.cache,
                    cacheKeys = renderer.cacheKeys,
                    cacheKey;

                rotation = pick(rot, wrapper.rotation);
                rad = rotation * deg2rad;


                fontSize = styles && styles.fontSize;


                // Avoid undefined and null (#7316)
                if (defined(textStr)) {

                    cacheKey = textStr.toString();

                    // Since numbers are monospaced, and numerical labels appear a lot
                    // in a chart, we assume that a label of n characters has the same
                    // bounding box as others of the same length. Unless there is inner
                    // HTML in the label. In that case, leave the numbers as is (#5899).
                    if (cacheKey.indexOf('<') === -1) {
                        cacheKey = cacheKey.replace(/[0-9]/g, '0');
                    }

                    // Properties that affect bounding box
                    cacheKey += [
                            '',
                            rotation || 0,
                            fontSize,
                            styles && styles.width,
                            styles && styles.textOverflow // #5968
                        ]
                        .join(',');

                }

                if (cacheKey && !reload) {
                    bBox = cache[cacheKey];
                }

                // No cache found
                if (!bBox) {

                    // SVG elements
                    if (element.namespaceURI === wrapper.SVG_NS || renderer.forExport) {
                        try { // Fails in Firefox if the container has display: none.

                            // When the text shadow shim is used, we need to hide the
                            // fake shadows to get the correct bounding box (#3872)
                            toggleTextShadowShim = this.fakeTS && function(display) {
                                each(
                                    element.querySelectorAll(
                                        '.highcharts-text-outline'
                                    ),
                                    function(tspan) {
                                        tspan.style.display = display;
                                    }
                                );
                            };

                            // Workaround for #3842, Firefox reporting wrong bounding
                            // box for shadows
                            if (toggleTextShadowShim) {
                                toggleTextShadowShim('none');
                            }

                            bBox = element.getBBox ?
                                // SVG: use extend because IE9 is not allowed to change
                                // width and height in case of rotation (below)
                                extend({}, element.getBBox()) : {

                                    // Legacy IE in export mode
                                    width: element.offsetWidth,
                                    height: element.offsetHeight
                                };

                            // #3842
                            if (toggleTextShadowShim) {
                                toggleTextShadowShim('');
                            }
                        } catch (e) {}

                        // If the bBox is not set, the try-catch block above failed. The
                        // other condition is for Opera that returns a width of
                        // -Infinity on hidden elements.
                        if (!bBox || bBox.width < 0) {
                            bBox = {
                                width: 0,
                                height: 0
                            };
                        }


                        // VML Renderer or useHTML within SVG
                    } else {

                        bBox = wrapper.htmlGetBBox();

                    }

                    // True SVG elements as well as HTML elements in modern browsers
                    // using the .useHTML option need to compensated for rotation
                    if (renderer.isSVG) {
                        width = bBox.width;
                        height = bBox.height;

                        // Workaround for wrong bounding box in IE, Edge and Chrome on
                        // Windows. With Highcharts' default font, IE and Edge report
                        // a box height of 16.899 and Chrome rounds it to 17. If this 
                        // stands uncorrected, it results in more padding added below
                        // the text than above when adding a label border or background.
                        // Also vertical positioning is affected.
                        // http://jsfiddle.net/highcharts/em37nvuj/
                        // (#1101, #1505, #1669, #2568, #6213).
                        if (
                            styles &&
                            styles.fontSize === '11px' &&
                            Math.round(height) === 17
                        ) {
                            bBox.height = height = 14;
                        }

                        // Adjust for rotated text
                        if (rotation) {
                            bBox.width = Math.abs(height * Math.sin(rad)) +
                                Math.abs(width * Math.cos(rad));
                            bBox.height = Math.abs(height * Math.cos(rad)) +
                                Math.abs(width * Math.sin(rad));
                        }
                    }

                    // Cache it. When loading a chart in a hidden iframe in Firefox and
                    // IE/Edge, the bounding box height is 0, so don't cache it (#5620).
                    if (cacheKey && bBox.height > 0) {

                        // Rotate (#4681)
                        while (cacheKeys.length > 250) {
                            delete cache[cacheKeys.shift()];
                        }

                        if (!cache[cacheKey]) {
                            cacheKeys.push(cacheKey);
                        }
                        cache[cacheKey] = bBox;
                    }
                }
                return bBox;
            },

            /**
             * Show the element after it has been hidden. 
             *
             * @param {boolean} [inherit=false] Set the visibility attribute to
             * `inherit` rather than `visible`. The difference is that an element with
             * `visibility="visible"` will be visible even if the parent is hidden.
             *
             * @returns {SVGElement} Returns the SVGElement for chaining.
             */
            show: function(inherit) {
                return this.attr({
                    visibility: inherit ? 'inherit' : 'visible'
                });
            },

            /**
             * Hide the element, equivalent to setting the `visibility` attribute to
             * `hidden`.
             *
             * @returns {SVGElement} Returns the SVGElement for chaining.
             */
            hide: function() {
                return this.attr({
                    visibility: 'hidden'
                });
            },

            /**
             * Fade out an element by animating its opacity down to 0, and hide it on
             * complete. Used internally for the tooltip.
             * 
             * @param {number} [duration=150] The fade duration in milliseconds.
             */
            fadeOut: function(duration) {
                var elemWrapper = this;
                elemWrapper.animate({
                    opacity: 0
                }, {
                    duration: duration || 150,
                    complete: function() {
                        // #3088, assuming we're only using this for tooltips
                        elemWrapper.attr({
                            y: -9999
                        });
                    }
                });
            },

            /**
             * Add the element to the DOM. All elements must be added this way.
             * 
             * @param {SVGElement|SVGDOMElement} [parent] The parent item to add it to.
             *   If undefined, the element is added to the {@link
             *   Highcharts.SVGRenderer.box}.
             *
             * @returns {SVGElement} Returns the SVGElement for chaining.
             *
             * @sample highcharts/members/renderer-g - Elements added to a group
             */
            add: function(parent) {

                var renderer = this.renderer,
                    element = this.element,
                    inserted;

                if (parent) {
                    this.parentGroup = parent;
                }

                // mark as inverted
                this.parentInverted = parent && parent.inverted;

                // build formatted text
                if (this.textStr !== undefined) {
                    renderer.buildText(this);
                }

                // Mark as added
                this.added = true;

                // If we're adding to renderer root, or other elements in the group
                // have a z index, we need to handle it
                if (!parent || parent.handleZ || this.zIndex) {
                    inserted = this.zIndexSetter();
                }

                // If zIndex is not handled, append at the end
                if (!inserted) {
                    (parent ? parent.element : renderer.box).appendChild(element);
                }

                // fire an event for internal hooks
                if (this.onAdd) {
                    this.onAdd();
                }

                return this;
            },

            /**
             * Removes an element from the DOM.
             *
             * @private
             * @param {SVGDOMElement|HTMLDOMElement} element The DOM node to remove.
             */
            safeRemoveChild: function(element) {
                var parentNode = element.parentNode;
                if (parentNode) {
                    parentNode.removeChild(element);
                }
            },

            /**
             * Destroy the element and element wrapper and clear up the DOM and event
             * hooks.
             *
             * 
             */
            destroy: function() {
                var wrapper = this,
                    element = wrapper.element || {},
                    parentToClean =
                    wrapper.renderer.isSVG &&
                    element.nodeName === 'SPAN' &&
                    wrapper.parentGroup,
                    grandParent,
                    ownerSVGElement = element.ownerSVGElement,
                    i;

                // remove events
                element.onclick = element.onmouseout = element.onmouseover =
                    element.onmousemove = element.point = null;
                stop(wrapper); // stop running animations

                if (wrapper.clipPath && ownerSVGElement) {
                    // Look for existing references to this clipPath and remove them
                    // before destroying the element (#6196).
                    each(
                        // The upper case version is for Edge
                        ownerSVGElement.querySelectorAll('[clip-path],[CLIP-PATH]'),
                        function(el) {
                            // Include the closing paranthesis in the test to rule out
                            // id's from 10 and above (#6550)
                            if (el
                                .getAttribute('clip-path')
                                .match(RegExp(
                                    // Edge puts quotes inside the url, others not
                                    '[\("]#' + wrapper.clipPath.element.id + '[\)"]'
                                ))
                            ) {
                                el.removeAttribute('clip-path');
                            }
                        }
                    );
                    wrapper.clipPath = wrapper.clipPath.destroy();
                }

                // Destroy stops in case this is a gradient object
                if (wrapper.stops) {
                    for (i = 0; i < wrapper.stops.length; i++) {
                        wrapper.stops[i] = wrapper.stops[i].destroy();
                    }
                    wrapper.stops = null;
                }

                // remove element
                wrapper.safeRemoveChild(element);


                wrapper.destroyShadows();


                // In case of useHTML, clean up empty containers emulating SVG groups
                // (#1960, #2393, #2697).
                while (
                    parentToClean &&
                    parentToClean.div &&
                    parentToClean.div.childNodes.length === 0
                ) {
                    grandParent = parentToClean.parentGroup;
                    wrapper.safeRemoveChild(parentToClean.div);
                    delete parentToClean.div;
                    parentToClean = grandParent;
                }

                // remove from alignObjects
                if (wrapper.alignTo) {
                    erase(wrapper.renderer.alignedObjects, wrapper);
                }

                objectEach(wrapper, function(val, key) {
                    delete wrapper[key];
                });

                return null;
            },


            /**
             * @typedef {Object} ShadowOptions
             * @property {string} [color=#000000] The shadow color.
             * @property {number} [offsetX=1] The horizontal offset from the element.
             * @property {number} [offsetY=1] The vertical offset from the element.
             * @property {number} [opacity=0.15] The shadow opacity.
             * @property {number} [width=3] The shadow width or distance from the
             *    element.
             */
            /**
             * Add a shadow to the element. Must be called after the element is added to
             * the DOM. In styled mode, this method is not used, instead use `defs` and
             * filters.
             * 
             * @param {boolean|ShadowOptions} shadowOptions The shadow options. If
             *    `true`, the default options are applied. If `false`, the current
             *    shadow will be removed.
             * @param {SVGElement} [group] The SVG group element where the shadows will 
             *    be applied. The default is to add it to the same parent as the current
             *    element. Internally, this is ised for pie slices, where all the
             *    shadows are added to an element behind all the slices.
             * @param {boolean} [cutOff] Used internally for column shadows.
             *
             * @returns {SVGElement} Returns the SVGElement for chaining.
             *
             * @example
             * renderer.rect(10, 100, 100, 100)
             *     .attr({ fill: 'red' })
             *     .shadow(true);
             */
            shadow: function(shadowOptions, group, cutOff) {
                var shadows = [],
                    i,
                    shadow,
                    element = this.element,
                    strokeWidth,
                    shadowWidth,
                    shadowElementOpacity,

                    // compensate for inverted plot area
                    transform;

                if (!shadowOptions) {
                    this.destroyShadows();

                } else if (!this.shadows) {
                    shadowWidth = pick(shadowOptions.width, 3);
                    shadowElementOpacity = (shadowOptions.opacity || 0.15) /
                        shadowWidth;
                    transform = this.parentInverted ?
                        '(-1,-1)' :
                        '(' + pick(shadowOptions.offsetX, 1) + ', ' +
                        pick(shadowOptions.offsetY, 1) + ')';
                    for (i = 1; i <= shadowWidth; i++) {
                        shadow = element.cloneNode(0);
                        strokeWidth = (shadowWidth * 2) + 1 - (2 * i);
                        attr(shadow, {
                            'isShadow': 'true',
                            'stroke': shadowOptions.color || '#000000',
                            'stroke-opacity': shadowElementOpacity * i,
                            'stroke-width': strokeWidth,
                            'transform': 'translate' + transform,
                            'fill': 'none'
                        });
                        if (cutOff) {
                            attr(
                                shadow,
                                'height',
                                Math.max(attr(shadow, 'height') - strokeWidth, 0)
                            );
                            shadow.cutHeight = strokeWidth;
                        }

                        if (group) {
                            group.element.appendChild(shadow);
                        } else if (element.parentNode) {
                            element.parentNode.insertBefore(shadow, element);
                        }

                        shadows.push(shadow);
                    }

                    this.shadows = shadows;
                }
                return this;

            },

            /**
             * Destroy shadows on the element.
             * @private
             */
            destroyShadows: function() {
                each(this.shadows || [], function(shadow) {
                    this.safeRemoveChild(shadow);
                }, this);
                this.shadows = undefined;
            },



            xGetter: function(key) {
                if (this.element.nodeName === 'circle') {
                    if (key === 'x') {
                        key = 'cx';
                    } else if (key === 'y') {
                        key = 'cy';
                    }
                }
                return this._defaultGetter(key);
            },

            /**
             * Get the current value of an attribute or pseudo attribute, used mainly
             * for animation. Called internally from the {@link
             * Highcharts.SVGRenderer#attr}
             * function.
             *
             * @private
             */
            _defaultGetter: function(key) {
                var ret = pick(
                    this[key],
                    this.element ? this.element.getAttribute(key) : null,
                    0
                );

                if (/^[\-0-9\.]+$/.test(ret)) { // is numerical
                    ret = parseFloat(ret);
                }
                return ret;
            },


            dSetter: function(value, key, element) {
                if (value && value.join) { // join path
                    value = value.join(' ');
                }
                if (/(NaN| {2}|^$)/.test(value)) {
                    value = 'M 0 0';
                }

                // Check for cache before resetting. Resetting causes disturbance in the
                // DOM, causing flickering in some cases in Edge/IE (#6747). Also
                // possible performance gain.
                if (this[key] !== value) {
                    element.setAttribute(key, value);
                    this[key] = value;
                }

            },

            dashstyleSetter: function(value) {
                var i,
                    strokeWidth = this['stroke-width'];

                // If "inherit", like maps in IE, assume 1 (#4981). With HC5 and the new
                // strokeWidth function, we should be able to use that instead.
                if (strokeWidth === 'inherit') {
                    strokeWidth = 1;
                }
                value = value && value.toLowerCase();
                if (value) {
                    value = value
                        .replace('shortdashdotdot', '3,1,1,1,1,1,')
                        .replace('shortdashdot', '3,1,1,1')
                        .replace('shortdot', '1,1,')
                        .replace('shortdash', '3,1,')
                        .replace('longdash', '8,3,')
                        .replace(/dot/g, '1,3,')
                        .replace('dash', '4,3,')
                        .replace(/,$/, '')
                        .split(','); // ending comma

                    i = value.length;
                    while (i--) {
                        value[i] = pInt(value[i]) * strokeWidth;
                    }
                    value = value.join(',')
                        .replace(/NaN/g, 'none'); // #3226
                    this.element.setAttribute('stroke-dasharray', value);
                }
            },

            alignSetter: function(value) {
                var convert = {
                    left: 'start',
                    center: 'middle',
                    right: 'end'
                };
                this.element.setAttribute('text-anchor', convert[value]);
            },
            opacitySetter: function(value, key, element) {
                this[key] = value;
                element.setAttribute(key, value);
            },
            titleSetter: function(value) {
                var titleNode = this.element.getElementsByTagName('title')[0];
                if (!titleNode) {
                    titleNode = doc.createElementNS(this.SVG_NS, 'title');
                    this.element.appendChild(titleNode);
                }

                // Remove text content if it exists
                if (titleNode.firstChild) {
                    titleNode.removeChild(titleNode.firstChild);
                }

                titleNode.appendChild(
                    doc.createTextNode(
                        // #3276, #3895
                        (String(pick(value), '')).replace(/<[^>]*>/g, '')
                    )
                );
            },
            textSetter: function(value) {
                if (value !== this.textStr) {
                    // Delete bBox memo when the text changes
                    delete this.bBox;

                    this.textStr = value;
                    if (this.added) {
                        this.renderer.buildText(this);
                    }
                }
            },
            fillSetter: function(value, key, element) {
                if (typeof value === 'string') {
                    element.setAttribute(key, value);
                } else if (value) {
                    this.colorGradient(value, key, element);
                }
            },
            visibilitySetter: function(value, key, element) {
                // IE9-11 doesn't handle visibilty:inherit well, so we remove the
                // attribute instead (#2881, #3909)
                if (value === 'inherit') {
                    element.removeAttribute(key);
                } else if (this[key] !== value) { // #6747
                    element.setAttribute(key, value);
                }
                this[key] = value;
            },
            zIndexSetter: function(value, key) {
                var renderer = this.renderer,
                    parentGroup = this.parentGroup,
                    parentWrapper = parentGroup || renderer,
                    parentNode = parentWrapper.element || renderer.box,
                    childNodes,
                    otherElement,
                    otherZIndex,
                    element = this.element,
                    inserted,
                    undefinedOtherZIndex,
                    svgParent = parentNode === renderer.box,
                    run = this.added,
                    i;

                if (defined(value)) {
                    // So we can read it for other elements in the group
                    element.zIndex = value;

                    value = +value;
                    if (this[key] === value) { // Only update when needed (#3865)
                        run = false;
                    }
                    this[key] = value;
                }

                // Insert according to this and other elements' zIndex. Before .add() is
                // called, nothing is done. Then on add, or by later calls to
                // zIndexSetter, the node is placed on the right place in the DOM.
                if (run) {
                    value = this.zIndex;

                    if (value && parentGroup) {
                        parentGroup.handleZ = true;
                    }

                    childNodes = parentNode.childNodes;
                    for (i = childNodes.length - 1; i >= 0 && !inserted; i--) {
                        otherElement = childNodes[i];
                        otherZIndex = otherElement.zIndex;
                        undefinedOtherZIndex = !defined(otherZIndex);

                        if (otherElement !== element) {
                            if (
                                // Negative zIndex versus no zIndex:
                                // On all levels except the highest. If the parent is <svg>,
                                // then we don't want to put items before <desc> or <defs>
                                (value < 0 && undefinedOtherZIndex && !svgParent && !i)
                            ) {
                                parentNode.insertBefore(element, childNodes[i]);
                                inserted = true;
                            } else if (
                                // Insert after the first element with a lower zIndex
                                pInt(otherZIndex) <= value ||
                                // If negative zIndex, add this before first undefined zIndex element
                                (undefinedOtherZIndex && (!defined(value) || value >= 0))
                            ) {
                                parentNode.insertBefore(
                                    element,
                                    childNodes[i + 1] || null // null for oldIE export
                                );
                                inserted = true;
                            }
                        }
                    }

                    if (!inserted) {
                        parentNode.insertBefore(
                            element,
                            childNodes[svgParent ? 3 : 0] || null // null for oldIE
                        );
                        inserted = true;
                    }
                }
                return inserted;
            },
            _defaultSetter: function(value, key, element) {
                element.setAttribute(key, value);
            }
        });

        // Some shared setters and getters
        SVGElement.prototype.yGetter =
            SVGElement.prototype.xGetter;
        SVGElement.prototype.translateXSetter =
            SVGElement.prototype.translateYSetter =
            SVGElement.prototype.rotationSetter =
            SVGElement.prototype.verticalAlignSetter =
            SVGElement.prototype.rotationOriginXSetter =
            SVGElement.prototype.rotationOriginYSetter =
            SVGElement.prototype.scaleXSetter =
            SVGElement.prototype.scaleYSetter =
            SVGElement.prototype.matrixSetter = function(value, key) {
                this[key] = value;
                this.doTransform = true;
            };


        // WebKit and Batik have problems with a stroke-width of zero, so in this case
        // we remove the stroke attribute altogether. #1270, #1369, #3065, #3072.
        SVGElement.prototype['stroke-widthSetter'] =
            SVGElement.prototype.strokeSetter = function(value, key, element) {
                this[key] = value;
                // Only apply the stroke attribute if the stroke width is defined and larger
                // than 0
                if (this.stroke && this['stroke-width']) {
                    // Use prototype as instance may be overridden
                    SVGElement.prototype.fillSetter.call(
                        this,
                        this.stroke,
                        'stroke',
                        element
                    );

                    element.setAttribute('stroke-width', this['stroke-width']);
                    this.hasStroke = true;
                } else if (key === 'stroke-width' && value === 0 && this.hasStroke) {
                    element.removeAttribute('stroke');
                    this.hasStroke = false;
                }
            };


        /**
         * Allows direct access to the Highcharts rendering layer in order to draw
         * primitive shapes like circles, rectangles, paths or text directly on a chart,
         * or independent from any chart. The SVGRenderer represents a wrapper object
         * for SVG in modern browsers. Through the VMLRenderer, part of the `oldie.js`
         * module, it also brings vector graphics to IE <= 8.
         *
         * An existing chart's renderer can be accessed through {@link Chart.renderer}.
         * The renderer can also be used completely decoupled from a chart.
         *
         * @param {HTMLDOMElement} container - Where to put the SVG in the web page.
         * @param {number} width - The width of the SVG.
         * @param {number} height - The height of the SVG.
         * @param {boolean} [forExport=false] - Whether the rendered content is intended
         *   for export.
         * @param {boolean} [allowHTML=true] - Whether the renderer is allowed to
         *   include HTML text, which will be projected on top of the SVG.
         *
         * @example
         * // Use directly without a chart object.
         * var renderer = new Highcharts.Renderer(parentNode, 600, 400);
         *
         * @sample highcharts/members/renderer-on-chart
         *         Annotating a chart programmatically.
         * @sample highcharts/members/renderer-basic
         *         Independent SVG drawing.
         *
         * @class Highcharts.SVGRenderer
         */
        SVGRenderer = H.SVGRenderer = function() {
            this.init.apply(this, arguments);
        };
        extend(SVGRenderer.prototype, /** @lends Highcharts.SVGRenderer.prototype */ {
            /**
             * A pointer to the renderer's associated Element class. The VMLRenderer
             * will have a pointer to VMLElement here.
             * @type {SVGElement}
             */
            Element: SVGElement,
            SVG_NS: SVG_NS,
            /**
             * Initialize the SVGRenderer. Overridable initiator function that takes
             * the same parameters as the constructor.
             */
            init: function(container, width, height, style, forExport, allowHTML) {
                var renderer = this,
                    boxWrapper,
                    element,
                    desc;

                boxWrapper = renderer.createElement('svg')
                    .attr({
                        'version': '1.1',
                        'class': 'highcharts-root'
                    })

                    .css(this.getStyle(style));
                element = boxWrapper.element;
                container.appendChild(element);

                // Always use ltr on the container, otherwise text-anchor will be
                // flipped and text appear outside labels, buttons, tooltip etc (#3482)
                attr(container, 'dir', 'ltr');

                // For browsers other than IE, add the namespace attribute (#1978)
                if (container.innerHTML.indexOf('xmlns') === -1) {
                    attr(element, 'xmlns', this.SVG_NS);
                }

                // object properties
                renderer.isSVG = true;

                /** 
                 * The root `svg` node of the renderer.
                 * @name box
                 * @memberOf SVGRenderer
                 * @type {SVGDOMElement}
                 */
                this.box = element;
                /** 
                 * The wrapper for the root `svg` node of the renderer.
                 *
                 * @name boxWrapper
                 * @memberOf SVGRenderer
                 * @type {SVGElement}
                 */
                this.boxWrapper = boxWrapper;
                renderer.alignedObjects = [];

                /**
                 * Page url used for internal references.
                 * @type {string}
                 */
                // #24, #672, #1070
                this.url = (
                        (isFirefox || isWebKit) &&
                        doc.getElementsByTagName('base').length
                    ) ?
                    win.location.href
                    .replace(/#.*?$/, '') // remove the hash
                    .replace(/<[^>]*>/g, '') // wing cut HTML
                    // escape parantheses and quotes
                    .replace(/([\('\)])/g, '\\$1')
                    // replace spaces (needed for Safari only)
                    .replace(/ /g, '%20') :
                    '';

                // Add description
                desc = this.createElement('desc').add();
                desc.element.appendChild(
                    doc.createTextNode('Created with Highcharts 6.0.3')
                );

                /**
                 * A pointer to the `defs` node of the root SVG.
                 * @type {SVGElement}
                 * @name defs
                 * @memberOf SVGRenderer
                 */
                renderer.defs = this.createElement('defs').add();
                renderer.allowHTML = allowHTML;
                renderer.forExport = forExport;
                renderer.gradients = {}; // Object where gradient SvgElements are stored
                renderer.cache = {}; // Cache for numerical bounding boxes
                renderer.cacheKeys = [];
                renderer.imgCount = 0;

                renderer.setSize(width, height, false);



                // Issue 110 workaround:
                // In Firefox, if a div is positioned by percentage, its pixel position
                // may land between pixels. The container itself doesn't display this,
                // but an SVG element inside this container will be drawn at subpixel
                // precision. In order to draw sharp lines, this must be compensated
                // for. This doesn't seem to work inside iframes though (like in
                // jsFiddle).
                var subPixelFix, rect;
                if (isFirefox && container.getBoundingClientRect) {
                    subPixelFix = function() {
                        css(container, {
                            left: 0,
                            top: 0
                        });
                        rect = container.getBoundingClientRect();
                        css(container, {
                            left: (Math.ceil(rect.left) - rect.left) + 'px',
                            top: (Math.ceil(rect.top) - rect.top) + 'px'
                        });
                    };

                    // run the fix now
                    subPixelFix();

                    // run it on resize
                    renderer.unSubPixelFix = addEvent(win, 'resize', subPixelFix);
                }
            },



            /**
             * Get the global style setting for the renderer.
             * @private
             * @param  {CSSObject} style - Style settings.
             * @return {CSSObject} The style settings mixed with defaults.
             */
            getStyle: function(style) {
                this.style = extend({

                    fontFamily: '"Lucida Grande", "Lucida Sans Unicode", ' +
                        'Arial, Helvetica, sans-serif',
                    fontSize: '12px'

                }, style);
                return this.style;
            },
            /**
             * Apply the global style on the renderer, mixed with the default styles.
             * 
             * @param {CSSObject} style - CSS to apply.
             */
            setStyle: function(style) {
                this.boxWrapper.css(this.getStyle(style));
            },


            /**
             * Detect whether the renderer is hidden. This happens when one of the
             * parent elements has `display: none`. Used internally to detect when we
             * needto render preliminarily in another div to get the text bounding boxes
             * right.
             *
             * @returns {boolean} True if it is hidden.
             */
            isHidden: function() { // #608
                return !this.boxWrapper.getBBox().width;
            },

            /**
             * Destroys the renderer and its allocated members.
             */
            destroy: function() {
                var renderer = this,
                    rendererDefs = renderer.defs;
                renderer.box = null;
                renderer.boxWrapper = renderer.boxWrapper.destroy();

                // Call destroy on all gradient elements
                destroyObjectProperties(renderer.gradients || {});
                renderer.gradients = null;

                // Defs are null in VMLRenderer
                // Otherwise, destroy them here.
                if (rendererDefs) {
                    renderer.defs = rendererDefs.destroy();
                }

                // Remove sub pixel fix handler (#982)
                if (renderer.unSubPixelFix) {
                    renderer.unSubPixelFix();
                }

                renderer.alignedObjects = null;

                return null;
            },

            /**
             * Create a wrapper for an SVG element. Serves as a factory for 
             * {@link SVGElement}, but this function is itself mostly called from 
             * primitive factories like {@link SVGRenderer#path}, {@link
             * SVGRenderer#rect} or {@link SVGRenderer#text}.
             * 
             * @param {string} nodeName - The node name, for example `rect`, `g` etc.
             * @returns {SVGElement} The generated SVGElement.
             */
            createElement: function(nodeName) {
                var wrapper = new this.Element();
                wrapper.init(this, nodeName);
                return wrapper;
            },

            /**
             * Dummy function for plugins, called every time the renderer is updated.
             * Prior to Highcharts 5, this was used for the canvg renderer.
             * @function
             */
            draw: noop,

            /**
             * Get converted radial gradient attributes according to the radial
             * reference. Used internally from the {@link SVGElement#colorGradient}
             * function.
             *
             * @private
             */
            getRadialAttr: function(radialReference, gradAttr) {
                return {
                    cx: (radialReference[0] - radialReference[2] / 2) +
                        gradAttr.cx * radialReference[2],
                    cy: (radialReference[1] - radialReference[2] / 2) +
                        gradAttr.cy * radialReference[2],
                    r: gradAttr.r * radialReference[2]
                };
            },

            getSpanWidth: function(wrapper, tspan) {
                var renderer = this,
                    bBox = wrapper.getBBox(true),
                    actualWidth = bBox.width;

                // Old IE cannot measure the actualWidth for SVG elements (#2314)
                if (!svg && renderer.forExport) {
                    actualWidth = renderer.measureSpanWidth(
                        tspan.firstChild.data,
                        wrapper.styles
                    );
                }
                return actualWidth;
            },

            applyEllipsis: function(wrapper, tspan, text, width) {
                var renderer = this,
                    rotation = wrapper.rotation,
                    str = text,
                    currentIndex,
                    minIndex = 0,
                    maxIndex = text.length,
                    updateTSpan = function(s) {
                        tspan.removeChild(tspan.firstChild);
                        if (s) {
                            tspan.appendChild(doc.createTextNode(s));
                        }
                    },
                    actualWidth,
                    wasTooLong;
                wrapper.rotation = 0; // discard rotation when computing box
                actualWidth = renderer.getSpanWidth(wrapper, tspan);
                wasTooLong = actualWidth > width;
                if (wasTooLong) {
                    while (minIndex <= maxIndex) {
                        currentIndex = Math.ceil((minIndex + maxIndex) / 2);
                        str = text.substring(0, currentIndex) + '\u2026';
                        updateTSpan(str);
                        actualWidth = renderer.getSpanWidth(wrapper, tspan);
                        if (minIndex === maxIndex) {
                            // Complete
                            minIndex = maxIndex + 1;
                        } else if (actualWidth > width) {
                            // Too large. Set max index to current.
                            maxIndex = currentIndex - 1;
                        } else {
                            // Within width. Set min index to current.
                            minIndex = currentIndex;
                        }
                    }
                    // If max index was 0 it means just ellipsis was also to large.
                    if (maxIndex === 0) {
                        // Remove ellipses.
                        updateTSpan('');
                    }
                }
                wrapper.rotation = rotation; // Apply rotation again.
                return wasTooLong;
            },

            /**
             * A collection of characters mapped to HTML entities. When `useHTML` on an
             * element is true, these entities will be rendered correctly by HTML. In 
             * the SVG pseudo-HTML, they need to be unescaped back to simple characters,
             * so for example `&lt;` will render as `<`.
             *
             * @example
             * // Add support for unescaping quotes
             * Highcharts.SVGRenderer.prototype.escapes['"'] = '&quot;';
             * 
             * @type {Object}
             */
            escapes: {
                '&': '&amp;',
                '<': '&lt;',
                '>': '&gt;',
                "'": '&#39;', // eslint-disable-line quotes
                '"': '&quot'
            },

            /**
             * Parse a simple HTML string into SVG tspans. Called internally when text
             *   is set on an SVGElement. The function supports a subset of HTML tags,
             *   CSS text features like `width`, `text-overflow`, `white-space`, and
             *   also attributes like `href` and `style`.
             * @private
             * @param {SVGElement} wrapper The parent SVGElement.
             */
            buildText: function(wrapper) {
                var textNode = wrapper.element,
                    renderer = this,
                    forExport = renderer.forExport,
                    textStr = pick(wrapper.textStr, '').toString(),
                    hasMarkup = textStr.indexOf('<') !== -1,
                    lines,
                    childNodes = textNode.childNodes,
                    clsRegex,
                    styleRegex,
                    hrefRegex,
                    wasTooLong,
                    parentX = attr(textNode, 'x'),
                    textStyles = wrapper.styles,
                    width = wrapper.textWidth,
                    textLineHeight = textStyles && textStyles.lineHeight,
                    textOutline = textStyles && textStyles.textOutline,
                    ellipsis = textStyles && textStyles.textOverflow === 'ellipsis',
                    noWrap = textStyles && textStyles.whiteSpace === 'nowrap',
                    fontSize = textStyles && textStyles.fontSize,
                    textCache,
                    isSubsequentLine,
                    i = childNodes.length,
                    tempParent = width && !wrapper.added && this.box,
                    getLineHeight = function(tspan) {
                        var fontSizeStyle;

                        fontSizeStyle = /(px|em)$/.test(tspan && tspan.style.fontSize) ?
                            tspan.style.fontSize :
                            (fontSize || renderer.style.fontSize || 12);


                        return textLineHeight ?
                            pInt(textLineHeight) :
                            renderer.fontMetrics(
                                fontSizeStyle,
                                // Get the computed size from parent if not explicit
                                tspan.getAttribute('style') ? tspan : textNode
                            ).h;
                    },
                    unescapeEntities = function(inputStr) {
                        objectEach(renderer.escapes, function(value, key) {
                            inputStr = inputStr.replace(
                                new RegExp(value, 'g'),
                                key
                            );
                        });
                        return inputStr;
                    };

                // The buildText code is quite heavy, so if we're not changing something
                // that affects the text, skip it (#6113).
                textCache = [
                    textStr,
                    ellipsis,
                    noWrap,
                    textLineHeight,
                    textOutline,
                    fontSize,
                    width
                ].join(',');
                if (textCache === wrapper.textCache) {
                    return;
                }
                wrapper.textCache = textCache;

                // Remove old text
                while (i--) {
                    textNode.removeChild(childNodes[i]);
                }

                // Skip tspans, add text directly to text node. The forceTSpan is a hook
                // used in text outline hack.
                if (!hasMarkup &&
                    !textOutline &&
                    !ellipsis &&
                    !width &&
                    textStr.indexOf(' ') === -1
                ) {
                    textNode.appendChild(doc.createTextNode(unescapeEntities(textStr)));

                    // Complex strings, add more logic
                } else {

                    clsRegex = /<.*class="([^"]+)".*>/;
                    styleRegex = /<.*style="([^"]+)".*>/;
                    hrefRegex = /<.*href="([^"]+)".*>/;

                    if (tempParent) {
                        // attach it to the DOM to read offset width
                        tempParent.appendChild(textNode);
                    }

                    if (hasMarkup) {
                        lines = textStr

                            .replace(/<(b|strong)>/g, '<span style="font-weight:bold">')
                            .replace(/<(i|em)>/g, '<span style="font-style:italic">')

                            .replace(/<a/g, '<span')
                            .replace(/<\/(b|strong|i|em|a)>/g, '</span>')
                            .split(/<br.*?>/g);

                    } else {
                        lines = [textStr];
                    }


                    // Trim empty lines (#5261)
                    lines = grep(lines, function(line) {
                        return line !== '';
                    });


                    // build the lines
                    each(lines, function buildTextLines(line, lineNo) {
                        var spans,
                            spanNo = 0;
                        line = line
                            // Trim to prevent useless/costly process on the spaces
                            // (#5258)
                            .replace(/^\s+|\s+$/g, '')
                            .replace(/<span/g, '|||<span')
                            .replace(/<\/span>/g, '</span>|||');
                        spans = line.split('|||');

                        each(spans, function buildTextSpans(span) {
                            if (span !== '' || spans.length === 1) {
                                var attributes = {},
                                    tspan = doc.createElementNS(
                                        renderer.SVG_NS,
                                        'tspan'
                                    ),
                                    spanCls,
                                    spanStyle; // #390
                                if (clsRegex.test(span)) {
                                    spanCls = span.match(clsRegex)[1];
                                    attr(tspan, 'class', spanCls);
                                }
                                if (styleRegex.test(span)) {
                                    spanStyle = span.match(styleRegex)[1].replace(
                                        /(;| |^)color([ :])/,
                                        '$1fill$2'
                                    );
                                    attr(tspan, 'style', spanStyle);
                                }

                                // Not for export - #1529
                                if (hrefRegex.test(span) && !forExport) {
                                    attr(
                                        tspan,
                                        'onclick',
                                        'location.href=\"' +
                                        span.match(hrefRegex)[1] + '\"'
                                    );
                                    attr(tspan, 'class', 'highcharts-anchor');

                                    css(tspan, {
                                        cursor: 'pointer'
                                    });

                                }

                                // Strip away unsupported HTML tags (#7126)
                                span = unescapeEntities(
                                    span.replace(/<[a-zA-Z\/](.|\n)*?>/g, '') || ' '
                                );

                                // Nested tags aren't supported, and cause crash in
                                // Safari (#1596)
                                if (span !== ' ') {

                                    // add the text node
                                    tspan.appendChild(doc.createTextNode(span));

                                    // First span in a line, align it to the left
                                    if (!spanNo) {
                                        if (lineNo && parentX !== null) {
                                            attributes.x = parentX;
                                        }
                                    } else {
                                        attributes.dx = 0; // #16
                                    }

                                    // add attributes
                                    attr(tspan, attributes);

                                    // Append it
                                    textNode.appendChild(tspan);

                                    // first span on subsequent line, add the line
                                    // height
                                    if (!spanNo && isSubsequentLine) {

                                        // allow getting the right offset height in
                                        // exporting in IE
                                        if (!svg && forExport) {
                                            css(tspan, {
                                                display: 'block'
                                            });
                                        }

                                        // Set the line height based on the font size of
                                        // either the text element or the tspan element
                                        attr(
                                            tspan,
                                            'dy',
                                            getLineHeight(tspan)
                                        );
                                    }

                                    /* 
                                    if (width) {
                                    	renderer.breakText(wrapper, width);
                                    }
                                    */

                                    // Check width and apply soft breaks or ellipsis
                                    if (width) {
                                        var words = span.replace(
                                                /([^\^])-/g,
                                                '$1- '
                                            ).split(' '), // #1273
                                            hasWhiteSpace = (
                                                spans.length > 1 ||
                                                lineNo ||
                                                (words.length > 1 && !noWrap)
                                            ),
                                            tooLong,
                                            rest = [],
                                            actualWidth,
                                            dy = getLineHeight(tspan),
                                            rotation = wrapper.rotation;

                                        if (ellipsis) {
                                            wasTooLong = renderer.applyEllipsis(
                                                wrapper,
                                                tspan,
                                                span,
                                                width
                                            );
                                        }

                                        while (!ellipsis &&
                                            hasWhiteSpace &&
                                            (words.length || rest.length)
                                        ) {
                                            // discard rotation when computing box
                                            wrapper.rotation = 0;
                                            actualWidth = renderer.getSpanWidth(
                                                wrapper,
                                                tspan
                                            );
                                            tooLong = actualWidth > width;

                                            // For ellipsis, do a binary search for the 
                                            // correct string length
                                            if (wasTooLong === undefined) {
                                                wasTooLong = tooLong; // First time
                                            }

                                            // Looping down, this is the first word
                                            // sequence that is not too long, so we can
                                            // move on to build the next line.
                                            if (!tooLong || words.length === 1) {
                                                words = rest;
                                                rest = [];

                                                if (words.length && !noWrap) {
                                                    tspan = doc.createElementNS(
                                                        SVG_NS,
                                                        'tspan'
                                                    );
                                                    attr(tspan, {
                                                        dy: dy,
                                                        x: parentX
                                                    });
                                                    if (spanStyle) { // #390
                                                        attr(tspan, 'style', spanStyle);
                                                    }
                                                    textNode.appendChild(tspan);
                                                }

                                                // a single word is pressing it out
                                                if (actualWidth > width) {
                                                    width = actualWidth;
                                                }
                                            } else { // append to existing line tspan
                                                tspan.removeChild(tspan.firstChild);
                                                rest.unshift(words.pop());
                                            }
                                            if (words.length) {
                                                tspan.appendChild(
                                                    doc.createTextNode(
                                                        words.join(' ')
                                                        .replace(/- /g, '-')
                                                    )
                                                );
                                            }
                                        }
                                        wrapper.rotation = rotation;
                                    }

                                    spanNo++;
                                }
                            }
                        });
                        // To avoid beginning lines that doesn't add to the textNode
                        // (#6144)
                        isSubsequentLine = (
                            isSubsequentLine ||
                            textNode.childNodes.length
                        );
                    });

                    if (wasTooLong) {
                        wrapper.attr('title', wrapper.textStr);
                    }
                    if (tempParent) {
                        tempParent.removeChild(textNode);
                    }

                    // Apply the text outline
                    if (textOutline && wrapper.applyTextOutline) {
                        wrapper.applyTextOutline(textOutline);
                    }
                }
            },



            /*
            breakText: function (wrapper, width) {
            	var bBox = wrapper.getBBox(),
            		node = wrapper.element,
            		textLength = node.textContent.length,
            		// try this position first, based on average character width
            		pos = Math.round(width * textLength / bBox.width),
            		increment = 0,
            		finalPos;

            	if (bBox.width > width) {
            		while (finalPos === undefined) {
            			textLength = node.getSubStringLength(0, pos);

            			if (textLength <= width) {
            				if (increment === -1) {
            					finalPos = pos;
            				} else {
            					increment = 1;
            				}
            			} else {
            				if (increment === 1) {
            					finalPos = pos - 1;
            				} else {
            					increment = -1;
            				}
            			}
            			pos += increment;
            		}
            	}
            	console.log(
            		'width',
            		width,
            		'stringWidth',
            		node.getSubStringLength(0, finalPos)
            	)
            },
            */

            /**
             * Returns white for dark colors and black for bright colors.
             *
             * @param {ColorString} rgba - The color to get the contrast for.
             * @returns {string} The contrast color, either `#000000` or `#FFFFFF`.
             */
            getContrast: function(rgba) {
                rgba = color(rgba).rgba;

                // The threshold may be discussed. Here's a proposal for adding
                // different weight to the color channels (#6216)
                /*
        rgba[0] *= 1; // red
        rgba[1] *= 1.2; // green
        rgba[2] *= 0.7; // blue
        */

                return rgba[0] + rgba[1] + rgba[2] > 2 * 255 ? '#000000' : '#FFFFFF';
            },

            /**
             * Create a button with preset states.
             * @param {string} text - The text or HTML to draw.
             * @param {number} x - The x position of the button's left side.
             * @param {number} y - The y position of the button's top side.
             * @param {Function} callback - The function to execute on button click or 
             *    touch.
             * @param {SVGAttributes} [normalState] - SVG attributes for the normal
             *    state.
             * @param {SVGAttributes} [hoverState] - SVG attributes for the hover state.
             * @param {SVGAttributes} [pressedState] - SVG attributes for the pressed
             *    state.
             * @param {SVGAttributes} [disabledState] - SVG attributes for the disabled
             *    state.
             * @param {Symbol} [shape=rect] - The shape type.
             * @returns {SVGRenderer} The button element.
             */
            button: function(
                text,
                x,
                y,
                callback,
                normalState,
                hoverState,
                pressedState,
                disabledState,
                shape
            ) {
                var label = this.label(
                        text,
                        x,
                        y,
                        shape,
                        null,
                        null,
                        null,
                        null,
                        'button'
                    ),
                    curState = 0;

                // Default, non-stylable attributes
                label.attr(merge({
                    'padding': 8,
                    'r': 2
                }, normalState));


                // Presentational
                var normalStyle,
                    hoverStyle,
                    pressedStyle,
                    disabledStyle;

                // Normal state - prepare the attributes
                normalState = merge({
                    fill: '#f7f7f7',
                    stroke: '#cccccc',
                    'stroke-width': 1,
                    style: {
                        color: '#333333',
                        cursor: 'pointer',
                        fontWeight: 'normal'
                    }
                }, normalState);
                normalStyle = normalState.style;
                delete normalState.style;

                // Hover state
                hoverState = merge(normalState, {
                    fill: '#e6e6e6'
                }, hoverState);
                hoverStyle = hoverState.style;
                delete hoverState.style;

                // Pressed state
                pressedState = merge(normalState, {
                    fill: '#e6ebf5',
                    style: {
                        color: '#000000',
                        fontWeight: 'bold'
                    }
                }, pressedState);
                pressedStyle = pressedState.style;
                delete pressedState.style;

                // Disabled state
                disabledState = merge(normalState, {
                    style: {
                        color: '#cccccc'
                    }
                }, disabledState);
                disabledStyle = disabledState.style;
                delete disabledState.style;


                // Add the events. IE9 and IE10 need mouseover and mouseout to funciton
                // (#667).
                addEvent(label.element, isMS ? 'mouseover' : 'mouseenter', function() {
                    if (curState !== 3) {
                        label.setState(1);
                    }
                });
                addEvent(label.element, isMS ? 'mouseout' : 'mouseleave', function() {
                    if (curState !== 3) {
                        label.setState(curState);
                    }
                });

                label.setState = function(state) {
                    // Hover state is temporary, don't record it
                    if (state !== 1) {
                        label.state = curState = state;
                    }
                    // Update visuals
                    label.removeClass(
                            /highcharts-button-(normal|hover|pressed|disabled)/
                        )
                        .addClass(
                            'highcharts-button-' + ['normal', 'hover', 'pressed', 'disabled'][state || 0]
                        );


                    label.attr([
                            normalState,
                            hoverState,
                            pressedState,
                            disabledState
                        ][state || 0])
                        .css([
                            normalStyle,
                            hoverStyle,
                            pressedStyle,
                            disabledStyle
                        ][state || 0]);

                };



                // Presentational attributes
                label
                    .attr(normalState)
                    .css(extend({
                        cursor: 'default'
                    }, normalStyle));


                return label
                    .on('click', function(e) {
                        if (curState !== 3) {
                            callback.call(label, e);
                        }
                    });
            },

            /**
             * Make a straight line crisper by not spilling out to neighbour pixels.
             * 
             * @param {Array} points - The original points on the format `['M', 0, 0,
             *    'L', 100, 0]`.
             * @param {number} width - The width of the line.
             * @returns {Array} The original points array, but modified to render
             * crisply.
             */
            crispLine: function(points, width) {
                // normalize to a crisp line
                if (points[1] === points[4]) {
                    // Substract due to #1129. Now bottom and left axis gridlines behave
                    // the same.
                    points[1] = points[4] = Math.round(points[1]) - (width % 2 / 2);
                }
                if (points[2] === points[5]) {
                    points[2] = points[5] = Math.round(points[2]) + (width % 2 / 2);
                }
                return points;
            },


            /**
             * Draw a path, wraps the SVG `path` element.
             * 
             * @param {Array} [path] An SVG path definition in array form.
             * 
             * @example
             * var path = renderer.path(['M', 10, 10, 'L', 30, 30, 'z'])
             *     .attr({ stroke: '#ff00ff' })
             *     .add();
             * @returns {SVGElement} The generated wrapper element.
             *
             * @sample highcharts/members/renderer-path-on-chart/
             *         Draw a path in a chart
             * @sample highcharts/members/renderer-path/
             *         Draw a path independent from a chart
             *
             */
            /**
             * Draw a path, wraps the SVG `path` element.
             * 
             * @param {SVGAttributes} [attribs] The initial attributes.
             * @returns {SVGElement} The generated wrapper element.
             */
            path: function(path) {
                var attribs = {

                    fill: 'none'

                };
                if (isArray(path)) {
                    attribs.d = path;
                } else if (isObject(path)) { // attributes
                    extend(attribs, path);
                }
                return this.createElement('path').attr(attribs);
            },

            /**
             * Draw a circle, wraps the SVG `circle` element.
             * 
             * @param {number} [x] The center x position.
             * @param {number} [y] The center y position.
             * @param {number} [r] The radius.
             * @returns {SVGElement} The generated wrapper element.
             *
             * @sample highcharts/members/renderer-circle/ Drawing a circle
             */
            /**
             * Draw a circle, wraps the SVG `circle` element.
             * 
             * @param {SVGAttributes} [attribs] The initial attributes.
             * @returns {SVGElement} The generated wrapper element.
             */
            circle: function(x, y, r) {
                var attribs = isObject(x) ? x : {
                        x: x,
                        y: y,
                        r: r
                    },
                    wrapper = this.createElement('circle');

                // Setting x or y translates to cx and cy
                wrapper.xSetter = wrapper.ySetter = function(value, key, element) {
                    element.setAttribute('c' + key, value);
                };

                return wrapper.attr(attribs);
            },

            /**
             * Draw and return an arc.
             * @param {number} [x=0] Center X position.
             * @param {number} [y=0] Center Y position.
             * @param {number} [r=0] The outer radius of the arc.
             * @param {number} [innerR=0] Inner radius like used in donut charts.
             * @param {number} [start=0] The starting angle of the arc in radians, where
             *    0 is to the right and `-Math.PI/2` is up.
             * @param {number} [end=0] The ending angle of the arc in radians, where 0
             *    is to the right and `-Math.PI/2` is up.
             * @returns {SVGElement} The generated wrapper element.
             *
             * @sample highcharts/members/renderer-arc/
             *         Drawing an arc
             */
            /**
             * Draw and return an arc. Overloaded function that takes arguments object.
             * @param {SVGAttributes} attribs Initial SVG attributes.
             * @returns {SVGElement} The generated wrapper element.
             */
            arc: function(x, y, r, innerR, start, end) {
                var arc,
                    options;

                if (isObject(x)) {
                    options = x;
                    y = options.y;
                    r = options.r;
                    innerR = options.innerR;
                    start = options.start;
                    end = options.end;
                    x = options.x;
                } else {
                    options = {
                        innerR: innerR,
                        start: start,
                        end: end
                    };
                }

                // Arcs are defined as symbols for the ability to set
                // attributes in attr and animate
                arc = this.symbol('arc', x, y, r, r, options);
                arc.r = r; // #959
                return arc;
            },

            /**
             * Draw and return a rectangle.
             * @param {number} [x] Left position.
             * @param {number} [y] Top position.
             * @param {number} [width] Width of the rectangle.
             * @param {number} [height] Height of the rectangle.
             * @param {number} [r] Border corner radius.
             * @param {number} [strokeWidth] A stroke width can be supplied to allow
             *    crisp drawing.
             * @returns {SVGElement} The generated wrapper element.
             */
            /**
             * Draw and return a rectangle.
             * @param  {SVGAttributes} [attributes]
             *         General SVG attributes for the rectangle.
             * @return {SVGElement}
             *         The generated wrapper element.
             *
             * @sample highcharts/members/renderer-rect-on-chart/
             *         Draw a rectangle in a chart
             * @sample highcharts/members/renderer-rect/
             *         Draw a rectangle independent from a chart
             */
            rect: function(x, y, width, height, r, strokeWidth) {

                r = isObject(x) ? x.r : r;

                var wrapper = this.createElement('rect'),
                    attribs = isObject(x) ? x : x === undefined ? {} : {
                        x: x,
                        y: y,
                        width: Math.max(width, 0),
                        height: Math.max(height, 0)
                    };


                if (strokeWidth !== undefined) {
                    attribs.strokeWidth = strokeWidth;
                    attribs = wrapper.crisp(attribs);
                }
                attribs.fill = 'none';


                if (r) {
                    attribs.r = r;
                }

                wrapper.rSetter = function(value, key, element) {
                    attr(element, {
                        rx: value,
                        ry: value
                    });
                };

                return wrapper.attr(attribs);
            },

            /**
             * Resize the {@link SVGRenderer#box} and re-align all aligned child
             * elements.
             * @param  {number} width
             *         The new pixel width.
             * @param  {number} height
             *         The new pixel height.
             * @param  {Boolean|AnimationOptions} [animate=true]
             *         Whether and how to animate.
             */
            setSize: function(width, height, animate) {
                var renderer = this,
                    alignedObjects = renderer.alignedObjects,
                    i = alignedObjects.length;

                renderer.width = width;
                renderer.height = height;

                renderer.boxWrapper.animate({
                    width: width,
                    height: height
                }, {
                    step: function() {
                        this.attr({
                            viewBox: '0 0 ' + this.attr('width') + ' ' +
                                this.attr('height')
                        });
                    },
                    duration: pick(animate, true) ? undefined : 0
                });

                while (i--) {
                    alignedObjects[i].align();
                }
            },

            /**
             * Create and return an svg group element. Child
             * {@link Highcharts.SVGElement} objects are added to the group by using the
             * group as the first parameter
             * in {@link Highcharts.SVGElement#add|add()}.
             * 
             * @param {string} [name] The group will be given a class name of
             * `highcharts-{name}`. This can be used for styling and scripting.
             * @returns {SVGElement} The generated wrapper element.
             *
             * @sample highcharts/members/renderer-g/
             *         Show and hide grouped objects
             */
            g: function(name) {
                var elem = this.createElement('g');
                return name ? elem.attr({
                    'class': 'highcharts-' + name
                }) : elem;
            },

            /**
             * Display an image.
             * @param {string} src The image source.
             * @param {number} [x] The X position.
             * @param {number} [y] The Y position.
             * @param {number} [width] The image width. If omitted, it defaults to the 
             *    image file width.
             * @param {number} [height] The image height. If omitted it defaults to the
             *    image file height.
             * @returns {SVGElement} The generated wrapper element.
             *
             * @sample highcharts/members/renderer-image-on-chart/
             *         Add an image in a chart
             * @sample highcharts/members/renderer-image/
             *         Add an image independent of a chart
             */
            image: function(src, x, y, width, height) {
                var attribs = {
                        preserveAspectRatio: 'none'
                    },
                    elemWrapper;

                // optional properties
                if (arguments.length > 1) {
                    extend(attribs, {
                        x: x,
                        y: y,
                        width: width,
                        height: height
                    });
                }

                elemWrapper = this.createElement('image').attr(attribs);

                // set the href in the xlink namespace
                if (elemWrapper.element.setAttributeNS) {
                    elemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',
                        'href', src);
                } else {
                    // could be exporting in IE
                    // using href throws "not supported" in ie7 and under, requries
                    // regex shim to fix later
                    elemWrapper.element.setAttribute('hc-svg-href', src);
                }
                return elemWrapper;
            },

            /**
             * Draw a symbol out of pre-defined shape paths from
             * {@link SVGRenderer#symbols}.
             * It is used in Highcharts for point makers, which cake a `symbol` option,
             * and label and button backgrounds like in the tooltip and stock flags.
             *
             * @param {Symbol} symbol - The symbol name.
             * @param {number} x - The X coordinate for the top left position.
             * @param {number} y - The Y coordinate for the top left position.
             * @param {number} width - The pixel width.
             * @param {number} height - The pixel height.
             * @param {Object} [options] - Additional options, depending on the actual
             *    symbol drawn. 
             * @param {number} [options.anchorX] - The anchor X position for the
             *    `callout` symbol. This is where the chevron points to.
             * @param {number} [options.anchorY] - The anchor Y position for the
             *    `callout` symbol. This is where the chevron points to.
             * @param {number} [options.end] - The end angle of an `arc` symbol.
             * @param {boolean} [options.open] - Whether to draw `arc` symbol open or
             *    closed.
             * @param {number} [options.r] - The radius of an `arc` symbol, or the
             *    border radius for the `callout` symbol.
             * @param {number} [options.start] - The start angle of an `arc` symbol.
             */
            symbol: function(symbol, x, y, width, height, options) {

                var ren = this,
                    obj,
                    imageRegex = /^url\((.*?)\)$/,
                    isImage = imageRegex.test(symbol),
                    sym = !isImage && (this.symbols[symbol] ? symbol : 'circle'),


                    // get the symbol definition function
                    symbolFn = sym && this.symbols[sym],

                    // check if there's a path defined for this symbol
                    path = defined(x) && symbolFn && symbolFn.call(
                        this.symbols,
                        Math.round(x),
                        Math.round(y),
                        width,
                        height,
                        options
                    ),
                    imageSrc,
                    centerImage;

                if (symbolFn) {
                    obj = this.path(path);


                    obj.attr('fill', 'none');


                    // expando properties for use in animate and attr
                    extend(obj, {
                        symbolName: sym,
                        x: x,
                        y: y,
                        width: width,
                        height: height
                    });
                    if (options) {
                        extend(obj, options);
                    }


                    // Image symbols
                } else if (isImage) {


                    imageSrc = symbol.match(imageRegex)[1];

                    // Create the image synchronously, add attribs async
                    obj = this.image(imageSrc);

                    // The image width is not always the same as the symbol width. The
                    // image may be centered within the symbol, as is the case when
                    // image shapes are used as label backgrounds, for example in flags.
                    obj.imgwidth = pick(
                        symbolSizes[imageSrc] && symbolSizes[imageSrc].width,
                        options && options.width
                    );
                    obj.imgheight = pick(
                        symbolSizes[imageSrc] && symbolSizes[imageSrc].height,
                        options && options.height
                    );
                    /**
                     * Set the size and position
                     */
                    centerImage = function() {
                        obj.attr({
                            width: obj.width,
                            height: obj.height
                        });
                    };

                    /**
                     * Width and height setters that take both the image's physical size
                     * and the label size into consideration, and translates the image
                     * to center within the label.
                     */
                    each(['width', 'height'], function(key) {
                        obj[key + 'Setter'] = function(value, key) {
                            var attribs = {},
                                imgSize = this['img' + key],
                                trans = key === 'width' ? 'translateX' : 'translateY';
                            this[key] = value;
                            if (defined(imgSize)) {
                                if (this.element) {
                                    this.element.setAttribute(key, imgSize);
                                }
                                if (!this.alignByTranslate) {
                                    attribs[trans] = ((this[key] || 0) - imgSize) / 2;
                                    this.attr(attribs);
                                }
                            }
                        };
                    });


                    if (defined(x)) {
                        obj.attr({
                            x: x,
                            y: y
                        });
                    }
                    obj.isImg = true;

                    if (defined(obj.imgwidth) && defined(obj.imgheight)) {
                        centerImage();
                    } else {
                        // Initialize image to be 0 size so export will still function
                        // if there's no cached sizes.
                        obj.attr({
                            width: 0,
                            height: 0
                        });

                        // Create a dummy JavaScript image to get the width and height. 
                        createElement('img', {
                            onload: function() {

                                var chart = charts[ren.chartIndex];

                                // Special case for SVGs on IE11, the width is not
                                // accessible until the image is part of the DOM
                                // (#2854).
                                if (this.width === 0) {
                                    css(this, {
                                        position: 'absolute',
                                        top: '-999em'
                                    });
                                    doc.body.appendChild(this);
                                }

                                // Center the image
                                symbolSizes[imageSrc] = { // Cache for next	
                                    width: this.width,
                                    height: this.height
                                };
                                obj.imgwidth = this.width;
                                obj.imgheight = this.height;

                                if (obj.element) {
                                    centerImage();
                                }

                                // Clean up after #2854 workaround.
                                if (this.parentNode) {
                                    this.parentNode.removeChild(this);
                                }

                                // Fire the load event when all external images are
                                // loaded
                                ren.imgCount--;
                                if (!ren.imgCount && chart && chart.onload) {
                                    chart.onload();
                                }
                            },
                            src: imageSrc
                        });
                        this.imgCount++;
                    }
                }

                return obj;
            },

            /**
             * @typedef {string} Symbol
             * 
             * Can be one of `arc`, `callout`, `circle`, `diamond`, `square`,
             * `triangle`, `triangle-down`. Symbols are used internally for point
             * markers, button and label borders and backgrounds, or custom shapes.
             * Extendable by adding to {@link SVGRenderer#symbols}.
             */
            /**
             * An extendable collection of functions for defining symbol paths.
             */
            symbols: {
                'circle': function(x, y, w, h) {
                    // Return a full arc
                    return this.arc(x + w / 2, y + h / 2, w / 2, h / 2, {
                        start: 0,
                        end: Math.PI * 2,
                        open: false
                    });
                },

                'square': function(x, y, w, h) {
                    return [
                        'M', x, y,
                        'L', x + w, y,
                        x + w, y + h,
                        x, y + h,
                        'Z'
                    ];
                },

                'triangle': function(x, y, w, h) {
                    return [
                        'M', x + w / 2, y,
                        'L', x + w, y + h,
                        x, y + h,
                        'Z'
                    ];
                },

                'triangle-down': function(x, y, w, h) {
                    return [
                        'M', x, y,
                        'L', x + w, y,
                        x + w / 2, y + h,
                        'Z'
                    ];
                },
                'diamond': function(x, y, w, h) {
                    return [
                        'M', x + w / 2, y,
                        'L', x + w, y + h / 2,
                        x + w / 2, y + h,
                        x, y + h / 2,
                        'Z'
                    ];
                },
                'arc': function(x, y, w, h, options) {
                    var start = options.start,
                        rx = options.r || w,
                        ry = options.r || h || w,
                        proximity = 0.001,
                        fullCircle =
                        Math.abs(options.end - options.start - 2 * Math.PI) <
                        proximity,
                        // Substract a small number to prevent cos and sin of start and
                        // end from becoming equal on 360 arcs (related: #1561)
                        end = options.end - proximity,
                        innerRadius = options.innerR,
                        open = pick(options.open, fullCircle),
                        cosStart = Math.cos(start),
                        sinStart = Math.sin(start),
                        cosEnd = Math.cos(end),
                        sinEnd = Math.sin(end),
                        // Proximity takes care of rounding errors around PI (#6971)
                        longArc = options.end - start - Math.PI < proximity ? 0 : 1,
                        arc;

                    arc = [
                        'M',
                        x + rx * cosStart,
                        y + ry * sinStart,
                        'A', // arcTo
                        rx, // x radius
                        ry, // y radius
                        0, // slanting
                        longArc, // long or short arc
                        1, // clockwise
                        x + rx * cosEnd,
                        y + ry * sinEnd
                    ];

                    if (defined(innerRadius)) {
                        arc.push(
                            open ? 'M' : 'L',
                            x + innerRadius * cosEnd,
                            y + innerRadius * sinEnd,
                            'A', // arcTo
                            innerRadius, // x radius
                            innerRadius, // y radius
                            0, // slanting
                            longArc, // long or short arc
                            0, // clockwise
                            x + innerRadius * cosStart,
                            y + innerRadius * sinStart
                        );
                    }

                    arc.push(open ? '' : 'Z'); // close
                    return arc;
                },

                /**
                 * Callout shape used for default tooltips, also used for rounded
                 * rectangles in VML
                 */
                callout: function(x, y, w, h, options) {
                    var arrowLength = 6,
                        halfDistance = 6,
                        r = Math.min((options && options.r) || 0, w, h),
                        safeDistance = r + halfDistance,
                        anchorX = options && options.anchorX,
                        anchorY = options && options.anchorY,
                        path;

                    path = [
                        'M', x + r, y,
                        'L', x + w - r, y, // top side
                        'C', x + w, y, x + w, y, x + w, y + r, // top-right corner
                        'L', x + w, y + h - r, // right side
                        'C', x + w, y + h, x + w, y + h, x + w - r, y + h, // bottom-rgt
                        'L', x + r, y + h, // bottom side
                        'C', x, y + h, x, y + h, x, y + h - r, // bottom-left corner
                        'L', x, y + r, // left side
                        'C', x, y, x, y, x + r, y // top-left corner
                    ];

                    // Anchor on right side
                    if (anchorX && anchorX > w) {

                        // Chevron
                        if (
                            anchorY > y + safeDistance &&
                            anchorY < y + h - safeDistance
                        ) {
                            path.splice(13, 3,
                                'L', x + w, anchorY - halfDistance,
                                x + w + arrowLength, anchorY,
                                x + w, anchorY + halfDistance,
                                x + w, y + h - r
                            );

                            // Simple connector
                        } else {
                            path.splice(13, 3,
                                'L', x + w, h / 2,
                                anchorX, anchorY,
                                x + w, h / 2,
                                x + w, y + h - r
                            );
                        }

                        // Anchor on left side
                    } else if (anchorX && anchorX < 0) {

                        // Chevron
                        if (
                            anchorY > y + safeDistance &&
                            anchorY < y + h - safeDistance
                        ) {
                            path.splice(33, 3,
                                'L', x, anchorY + halfDistance,
                                x - arrowLength, anchorY,
                                x, anchorY - halfDistance,
                                x, y + r
                            );

                            // Simple connector
                        } else {
                            path.splice(33, 3,
                                'L', x, h / 2,
                                anchorX, anchorY,
                                x, h / 2,
                                x, y + r
                            );
                        }

                    } else if ( // replace bottom
                        anchorY &&
                        anchorY > h &&
                        anchorX > x + safeDistance &&
                        anchorX < x + w - safeDistance
                    ) {
                        path.splice(23, 3,
                            'L', anchorX + halfDistance, y + h,
                            anchorX, y + h + arrowLength,
                            anchorX - halfDistance, y + h,
                            x + r, y + h
                        );

                    } else if ( // replace top
                        anchorY &&
                        anchorY < 0 &&
                        anchorX > x + safeDistance &&
                        anchorX < x + w - safeDistance
                    ) {
                        path.splice(3, 3,
                            'L', anchorX - halfDistance, y,
                            anchorX, y - arrowLength,
                            anchorX + halfDistance, y,
                            w - r, y
                        );
                    }

                    return path;
                }
            },

            /**
             * @typedef {SVGElement} ClipRect - A clipping rectangle that can be applied
             * to one or more {@link SVGElement} instances. It is instanciated with the
             * {@link SVGRenderer#clipRect} function and applied with the {@link 
             * SVGElement#clip} function.
             *
             * @example
             * var circle = renderer.circle(100, 100, 100)
             *     .attr({ fill: 'red' })
             *     .add();
             * var clipRect = renderer.clipRect(100, 100, 100, 100);
             *
             * // Leave only the lower right quarter visible
             * circle.clip(clipRect);
             */
            /**
             * Define a clipping rectangle. The clipping rectangle is later applied
             * to {@link SVGElement} objects through the {@link SVGElement#clip}
             * function.
             * 
             * @param {String} id
             * @param {number} x
             * @param {number} y
             * @param {number} width
             * @param {number} height
             * @returns {ClipRect} A clipping rectangle.
             *
             * @example
             * var circle = renderer.circle(100, 100, 100)
             *     .attr({ fill: 'red' })
             *     .add();
             * var clipRect = renderer.clipRect(100, 100, 100, 100);
             *
             * // Leave only the lower right quarter visible
             * circle.clip(clipRect);
             */
            clipRect: function(x, y, width, height) {
                var wrapper,
                    id = H.uniqueKey(),

                    clipPath = this.createElement('clipPath').attr({
                        id: id
                    }).add(this.defs);

                wrapper = this.rect(x, y, width, height, 0).add(clipPath);
                wrapper.id = id;
                wrapper.clipPath = clipPath;
                wrapper.count = 0;

                return wrapper;
            },





            /**
             * Draw text. The text can contain a subset of HTML, like spans and anchors
             * and some basic text styling of these. For more advanced features like
             * border and background, use {@link Highcharts.SVGRenderer#label} instead.
             * To update the text after render, run `text.attr({ text: 'New text' })`.
             * @param  {String} str
             *         The text of (subset) HTML to draw.
             * @param  {number} x
             *         The x position of the text's lower left corner.
             * @param  {number} y
             *         The y position of the text's lower left corner.
             * @param  {Boolean} [useHTML=false]
             *         Use HTML to render the text.
             *
             * @return {SVGElement} The text object.
             *
             * @sample highcharts/members/renderer-text-on-chart/
             *         Annotate the chart freely
             * @sample highcharts/members/renderer-on-chart/
             *         Annotate with a border and in response to the data
             * @sample highcharts/members/renderer-text/
             *         Formatted text
             */
            text: function(str, x, y, useHTML) {

                // declare variables
                var renderer = this,
                    wrapper,
                    attribs = {};

                if (useHTML && (renderer.allowHTML || !renderer.forExport)) {
                    return renderer.html(str, x, y);
                }

                attribs.x = Math.round(x || 0); // X always needed for line-wrap logic
                if (y) {
                    attribs.y = Math.round(y);
                }
                if (str || str === 0) {
                    attribs.text = str;
                }

                wrapper = renderer.createElement('text')
                    .attr(attribs);

                if (!useHTML) {
                    wrapper.xSetter = function(value, key, element) {
                        var tspans = element.getElementsByTagName('tspan'),
                            tspan,
                            parentVal = element.getAttribute(key),
                            i;
                        for (i = 0; i < tspans.length; i++) {
                            tspan = tspans[i];
                            // If the x values are equal, the tspan represents a
                            // linebreak
                            if (tspan.getAttribute(key) === parentVal) {
                                tspan.setAttribute(key, value);
                            }
                        }
                        element.setAttribute(key, value);
                    };
                }

                return wrapper;
            },

            /**
             * Utility to return the baseline offset and total line height from the font
             * size.
             *
             * @param {?string} fontSize The current font size to inspect. If not given,
             *   the font size will be found from the DOM element.
             * @param {SVGElement|SVGDOMElement} [elem] The element to inspect for a
             *   current font size.
             * @returns {Object} An object containing `h`: the line height, `b`: the
             * baseline relative to the top of the box, and `f`: the font size.
             */
            fontMetrics: function(fontSize, elem) {
                var lineHeight,
                    baseline;


                fontSize = fontSize ||
                    // When the elem is a DOM element (#5932)
                    (elem && elem.style && elem.style.fontSize) ||
                    // Fall back on the renderer style default
                    (this.style && this.style.fontSize);



                // Handle different units
                if (/px/.test(fontSize)) {
                    fontSize = pInt(fontSize);
                } else if (/em/.test(fontSize)) {
                    // The em unit depends on parent items
                    fontSize = parseFloat(fontSize) *
                        (elem ? this.fontMetrics(null, elem.parentNode).f : 16);
                } else {
                    fontSize = 12;
                }

                // Empirical values found by comparing font size and bounding box
                // height. Applies to the default font family.
                // http://jsfiddle.net/highcharts/7xvn7/
                lineHeight = fontSize < 24 ? fontSize + 3 : Math.round(fontSize * 1.2);
                baseline = Math.round(lineHeight * 0.8);

                return {
                    h: lineHeight,
                    b: baseline,
                    f: fontSize
                };
            },

            /**
             * Correct X and Y positioning of a label for rotation (#1764).
             *
             * @private
             */
            rotCorr: function(baseline, rotation, alterY) {
                var y = baseline;
                if (rotation && alterY) {
                    y = Math.max(y * Math.cos(rotation * deg2rad), 4);
                }
                return {
                    x: (-baseline / 3) * Math.sin(rotation * deg2rad),
                    y: y
                };
            },

            /**
             * Draw a label, which is an extended text element with support for border
             * and background. Highcharts creates a `g` element with a text and a `path`
             * or `rect` inside, to make it behave somewhat like a HTML div. Border and
             * background are set through `stroke`, `stroke-width` and `fill` attributes
             * using the {@link Highcharts.SVGElement#attr|attr} method. To update the
             * text after render, run `label.attr({ text: 'New text' })`.
             * 
             * @param  {string} str
             *         The initial text string or (subset) HTML to render.
             * @param  {number} x
             *         The x position of the label's left side.
             * @param  {number} y
             *         The y position of the label's top side or baseline, depending on
             *         the `baseline` parameter.
             * @param  {String} shape
             *         The shape of the label's border/background, if any. Defaults to
             *         `rect`. Other possible values are `callout` or other shapes
             *         defined in {@link Highcharts.SVGRenderer#symbols}.
             * @param  {number} anchorX
             *         In case the `shape` has a pointer, like a flag, this is the
             *         coordinates it should be pinned to.
             * @param  {number} anchorY
             *         In case the `shape` has a pointer, like a flag, this is the
             *         coordinates it should be pinned to.
             * @param  {Boolean} baseline
             *         Whether to position the label relative to the text baseline,
             *	       like {@link Highcharts.SVGRenderer#text|renderer.text}, or to the
             *	       upper border of the rectangle.
             * @param  {String} className
             *         Class name for the group.
             *
             * @return {SVGElement}
             *         The generated label.
             *
             * @sample highcharts/members/renderer-label-on-chart/
             *         A label on the chart
             */
            label: function(
                str,
                x,
                y,
                shape,
                anchorX,
                anchorY,
                useHTML,
                baseline,
                className
            ) {

                var renderer = this,
                    wrapper = renderer.g(className !== 'button' && 'label'),
                    text = wrapper.text = renderer.text('', 0, 0, useHTML)
                    .attr({
                        zIndex: 1
                    }),
                    box,
                    bBox,
                    alignFactor = 0,
                    padding = 3,
                    paddingLeft = 0,
                    width,
                    height,
                    wrapperX,
                    wrapperY,
                    textAlign,
                    deferredAttr = {},
                    strokeWidth,
                    baselineOffset,
                    hasBGImage = /^url\((.*?)\)$/.test(shape),
                    needsBox = hasBGImage,
                    getCrispAdjust,
                    updateBoxSize,
                    updateTextPadding,
                    boxAttr;

                if (className) {
                    wrapper.addClass('highcharts-' + className);
                }


                needsBox = hasBGImage;
                getCrispAdjust = function() {
                    return (strokeWidth || 0) % 2 / 2;
                };



                /**
                 * This function runs after the label is added to the DOM (when the
                 * bounding box is available), and after the text of the label is
                 * updated to detect the new bounding box and reflect it in the border
                 * box.
                 */
                updateBoxSize = function() {
                    var style = text.element.style,
                        crispAdjust,
                        attribs = {};

                    bBox = (
                        (width === undefined || height === undefined || textAlign) &&
                        defined(text.textStr) &&
                        text.getBBox()
                    ); // #3295 && 3514 box failure when string equals 0
                    wrapper.width = (
                        (width || bBox.width || 0) +
                        2 * padding +
                        paddingLeft
                    );
                    wrapper.height = (height || bBox.height || 0) + 2 * padding;

                    // Update the label-scoped y offset
                    baselineOffset = padding +
                        renderer.fontMetrics(style && style.fontSize, text).b;


                    if (needsBox) {

                        // Create the border box if it is not already present
                        if (!box) {
                            // Symbol definition exists (#5324)
                            wrapper.box = box = renderer.symbols[shape] || hasBGImage ?
                                renderer.symbol(shape) :
                                renderer.rect();

                            box.addClass( // Don't use label className for buttons
                                (className === 'button' ? '' : 'highcharts-label-box') +
                                (className ? ' highcharts-' + className + '-box' : '')
                            );

                            box.add(wrapper);

                            crispAdjust = getCrispAdjust();
                            attribs.x = crispAdjust;
                            attribs.y = (baseline ? -baselineOffset : 0) + crispAdjust;
                        }

                        // Apply the box attributes
                        attribs.width = Math.round(wrapper.width);
                        attribs.height = Math.round(wrapper.height);

                        box.attr(extend(attribs, deferredAttr));
                        deferredAttr = {};
                    }
                };

                /**
                 * This function runs after setting text or padding, but only if padding
                 * is changed
                 */
                updateTextPadding = function() {
                    var textX = paddingLeft + padding,
                        textY;

                    // determin y based on the baseline
                    textY = baseline ? 0 : baselineOffset;

                    // compensate for alignment
                    if (
                        defined(width) &&
                        bBox &&
                        (textAlign === 'center' || textAlign === 'right')
                    ) {
                        textX += {
                                center: 0.5,
                                right: 1
                            }[textAlign] *
                            (width - bBox.width);
                    }

                    // update if anything changed
                    if (textX !== text.x || textY !== text.y) {
                        text.attr('x', textX);
                        if (textY !== undefined) {
                            text.attr('y', textY);
                        }
                    }

                    // record current values
                    text.x = textX;
                    text.y = textY;
                };

                /**
                 * Set a box attribute, or defer it if the box is not yet created
                 * @param {Object} key
                 * @param {Object} value
                 */
                boxAttr = function(key, value) {
                    if (box) {
                        box.attr(key, value);
                    } else {
                        deferredAttr[key] = value;
                    }
                };

                /**
                 * After the text element is added, get the desired size of the border
                 * box and add it before the text in the DOM.
                 */
                wrapper.onAdd = function() {
                    text.add(wrapper);
                    wrapper.attr({
                        // Alignment is available now  (#3295, 0 not rendered if given
                        // as a value)
                        text: (str || str === 0) ? str : '',
                        x: x,
                        y: y
                    });

                    if (box && defined(anchorX)) {
                        wrapper.attr({
                            anchorX: anchorX,
                            anchorY: anchorY
                        });
                    }
                };

                /*
                 * Add specific attribute setters.
                 */

                // only change local variables
                wrapper.widthSetter = function(value) {
                    width = H.isNumber(value) ? value : null; // width:auto => null
                };
                wrapper.heightSetter = function(value) {
                    height = value;
                };
                wrapper['text-alignSetter'] = function(value) {
                    textAlign = value;
                };
                wrapper.paddingSetter = function(value) {
                    if (defined(value) && value !== padding) {
                        padding = wrapper.padding = value;
                        updateTextPadding();
                    }
                };
                wrapper.paddingLeftSetter = function(value) {
                    if (defined(value) && value !== paddingLeft) {
                        paddingLeft = value;
                        updateTextPadding();
                    }
                };


                // change local variable and prevent setting attribute on the group
                wrapper.alignSetter = function(value) {
                    value = {
                        left: 0,
                        center: 0.5,
                        right: 1
                    }[value];
                    if (value !== alignFactor) {
                        alignFactor = value;
                        // Bounding box exists, means we're dynamically changing
                        if (bBox) {
                            wrapper.attr({
                                x: wrapperX
                            }); // #5134
                        }
                    }
                };

                // apply these to the box and the text alike
                wrapper.textSetter = function(value) {
                    if (value !== undefined) {
                        text.textSetter(value);
                    }
                    updateBoxSize();
                    updateTextPadding();
                };

                // apply these to the box but not to the text
                wrapper['stroke-widthSetter'] = function(value, key) {
                    if (value) {
                        needsBox = true;
                    }
                    strokeWidth = this['stroke-width'] = value;
                    boxAttr(key, value);
                };

                wrapper.strokeSetter =
                    wrapper.fillSetter =
                    wrapper.rSetter = function(value, key) {
                        if (key !== 'r') {
                            if (key === 'fill' && value) {
                                needsBox = true;
                            }
                            // for animation getter (#6776)
                            wrapper[key] = value;
                        }
                        boxAttr(key, value);
                    };

                wrapper.anchorXSetter = function(value, key) {
                    anchorX = wrapper.anchorX = value;
                    boxAttr(key, Math.round(value) - getCrispAdjust() - wrapperX);
                };
                wrapper.anchorYSetter = function(value, key) {
                    anchorY = wrapper.anchorY = value;
                    boxAttr(key, value - wrapperY);
                };

                // rename attributes
                wrapper.xSetter = function(value) {
                    wrapper.x = value; // for animation getter
                    if (alignFactor) {
                        value -= alignFactor * ((width || bBox.width) + 2 * padding);
                    }
                    wrapperX = Math.round(value);
                    wrapper.attr('translateX', wrapperX);
                };
                wrapper.ySetter = function(value) {
                    wrapperY = wrapper.y = Math.round(value);
                    wrapper.attr('translateY', wrapperY);
                };

                // Redirect certain methods to either the box or the text
                var baseCss = wrapper.css;
                return extend(wrapper, {
                    /**
                     * Pick up some properties and apply them to the text instead of the
                     * wrapper.
                     * @ignore
                     */
                    css: function(styles) {
                        if (styles) {
                            var textStyles = {};
                            // Create a copy to avoid altering the original object
                            // (#537)
                            styles = merge(styles);
                            each(wrapper.textProps, function(prop) {
                                if (styles[prop] !== undefined) {
                                    textStyles[prop] = styles[prop];
                                    delete styles[prop];
                                }
                            });
                            text.css(textStyles);
                        }
                        return baseCss.call(wrapper, styles);
                    },
                    /**
                     * Return the bounding box of the box, not the group.
                     * @ignore
                     */
                    getBBox: function() {
                        return {
                            width: bBox.width + 2 * padding,
                            height: bBox.height + 2 * padding,
                            x: bBox.x - padding,
                            y: bBox.y - padding
                        };
                    },

                    /**
                     * Apply the shadow to the box.
                     * @ignore
                     */
                    shadow: function(b) {
                        if (b) {
                            updateBoxSize();
                            if (box) {
                                box.shadow(b);
                            }
                        }
                        return wrapper;
                    },

                    /**
                     * Destroy and release memory.
                     * @ignore
                     */
                    destroy: function() {

                        // Added by button implementation
                        removeEvent(wrapper.element, 'mouseenter');
                        removeEvent(wrapper.element, 'mouseleave');

                        if (text) {
                            text = text.destroy();
                        }
                        if (box) {
                            box = box.destroy();
                        }
                        // Call base implementation to destroy the rest
                        SVGElement.prototype.destroy.call(wrapper);

                        // Release local pointers (#1298)
                        wrapper =
                            renderer =
                            updateBoxSize =
                            updateTextPadding =
                            boxAttr = null;
                    }
                });
            }
        }); // end SVGRenderer


        // general renderer
        H.Renderer = SVGRenderer;

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        /* eslint max-len: ["warn", 80, 4] */
        var attr = H.attr,
            createElement = H.createElement,
            css = H.css,
            defined = H.defined,
            each = H.each,
            extend = H.extend,
            isFirefox = H.isFirefox,
            isMS = H.isMS,
            isWebKit = H.isWebKit,
            pick = H.pick,
            pInt = H.pInt,
            SVGElement = H.SVGElement,
            SVGRenderer = H.SVGRenderer,
            win = H.win,
            wrap = H.wrap;

        // Extend SvgElement for useHTML option
        extend(SVGElement.prototype, /** @lends SVGElement.prototype */ {
            /**
             * Apply CSS to HTML elements. This is used in text within SVG rendering and
             * by the VML renderer
             */
            htmlCss: function(styles) {
                var wrapper = this,
                    element = wrapper.element,
                    textWidth = styles && element.tagName === 'SPAN' && styles.width;

                if (textWidth) {
                    delete styles.width;
                    wrapper.textWidth = textWidth;
                    wrapper.updateTransform();
                }
                if (styles && styles.textOverflow === 'ellipsis') {
                    styles.whiteSpace = 'nowrap';
                    styles.overflow = 'hidden';
                }
                wrapper.styles = extend(wrapper.styles, styles);
                css(wrapper.element, styles);

                return wrapper;
            },

            /**
             * VML and useHTML method for calculating the bounding box based on offsets
             * @param {Boolean} refresh Whether to force a fresh value from the DOM or
             * to use the cached value.
             *
             * @return {Object} A hash containing values for x, y, width and height
             */

            htmlGetBBox: function() {
                var wrapper = this,
                    element = wrapper.element;

                return {
                    x: element.offsetLeft,
                    y: element.offsetTop,
                    width: element.offsetWidth,
                    height: element.offsetHeight
                };
            },

            /**
             * VML override private method to update elements based on internal
             * properties based on SVG transform
             */
            htmlUpdateTransform: function() {
                // aligning non added elements is expensive
                if (!this.added) {
                    this.alignOnAdd = true;
                    return;
                }

                var wrapper = this,
                    renderer = wrapper.renderer,
                    elem = wrapper.element,
                    translateX = wrapper.translateX || 0,
                    translateY = wrapper.translateY || 0,
                    x = wrapper.x || 0,
                    y = wrapper.y || 0,
                    align = wrapper.textAlign || 'left',
                    alignCorrection = {
                        left: 0,
                        center: 0.5,
                        right: 1
                    }[align],
                    styles = wrapper.styles;

                // apply translate
                css(elem, {
                    marginLeft: translateX,
                    marginTop: translateY
                });


                if (wrapper.shadows) { // used in labels/tooltip
                    each(wrapper.shadows, function(shadow) {
                        css(shadow, {
                            marginLeft: translateX + 1,
                            marginTop: translateY + 1
                        });
                    });
                }


                // apply inversion
                if (wrapper.inverted) { // wrapper is a group
                    each(elem.childNodes, function(child) {
                        renderer.invertChild(child, elem);
                    });
                }

                if (elem.tagName === 'SPAN') {

                    var rotation = wrapper.rotation,
                        baseline,
                        textWidth = pInt(wrapper.textWidth),
                        whiteSpace = styles && styles.whiteSpace,
                        currentTextTransform = [
                            rotation,
                            align,
                            elem.innerHTML,
                            wrapper.textWidth,
                            wrapper.textAlign
                        ].join(',');

                    // Do the calculations and DOM access only if properties changed
                    if (currentTextTransform !== wrapper.cTT) {


                        baseline = renderer.fontMetrics(elem.style.fontSize).b;

                        // Renderer specific handling of span rotation
                        if (defined(rotation)) {
                            wrapper.setSpanRotation(
                                rotation,
                                alignCorrection,
                                baseline
                            );
                        }

                        // Reset multiline/ellipsis in order to read width (#4928,
                        // #5417)
                        css(elem, {
                            width: '',
                            whiteSpace: whiteSpace || 'nowrap'
                        });

                        // Update textWidth
                        if (
                            elem.offsetWidth > textWidth &&
                            /[ \-]/.test(elem.textContent || elem.innerText)
                        ) { // #983, #1254
                            css(elem, {
                                width: textWidth + 'px',
                                display: 'block',
                                whiteSpace: whiteSpace || 'normal' // #3331
                            });
                        }


                        wrapper.getSpanCorrection(
                            elem.offsetWidth,
                            baseline,
                            alignCorrection,
                            rotation,
                            align
                        );
                    }

                    // apply position with correction
                    css(elem, {
                        left: (x + (wrapper.xCorr || 0)) + 'px',
                        top: (y + (wrapper.yCorr || 0)) + 'px'
                    });

                    // Force reflow in webkit to apply the left and top on useHTML
                    // element (#1249)
                    if (isWebKit) {
                        // Assigned to baseline for lint purpose
                        baseline = elem.offsetHeight;
                    }

                    // record current text transform
                    wrapper.cTT = currentTextTransform;
                }
            },

            /**
             * Set the rotation of an individual HTML span
             */
            setSpanRotation: function(rotation, alignCorrection, baseline) {
                var rotationStyle = {},
                    cssTransformKey = this.renderer.getTransformKey();

                rotationStyle[cssTransformKey] = rotationStyle.transform =
                    'rotate(' + rotation + 'deg)';
                rotationStyle[cssTransformKey + (isFirefox ? 'Origin' : '-origin')] =
                    rotationStyle.transformOrigin =
                    (alignCorrection * 100) + '% ' + baseline + 'px';
                css(this.element, rotationStyle);
            },

            /**
             * Get the correction in X and Y positioning as the element is rotated.
             */
            getSpanCorrection: function(width, baseline, alignCorrection) {
                this.xCorr = -width * alignCorrection;
                this.yCorr = -baseline;
            }
        });

        // Extend SvgRenderer for useHTML option.
        extend(SVGRenderer.prototype, /** @lends SVGRenderer.prototype */ {

            getTransformKey: function() {
                return isMS && !/Edge/.test(win.navigator.userAgent) ?
                    '-ms-transform' :
                    isWebKit ?
                    '-webkit-transform' :
                    isFirefox ?
                    'MozTransform' :
                    win.opera ?
                    '-o-transform' :
                    '';
            },

            /**
             * Create HTML text node. This is used by the VML renderer as well as the
             * SVG renderer through the useHTML option.
             *
             * @param {String} str
             * @param {Number} x
             * @param {Number} y
             */
            html: function(str, x, y) {
                var wrapper = this.createElement('span'),
                    element = wrapper.element,
                    renderer = wrapper.renderer,
                    isSVG = renderer.isSVG,
                    addSetters = function(element, style) {
                        // These properties are set as attributes on the SVG group, and
                        // as identical CSS properties on the div. (#3542)
                        each(['opacity', 'visibility'], function(prop) {
                            wrap(element, prop + 'Setter', function(
                                proceed,
                                value,
                                key,
                                elem
                            ) {
                                proceed.call(this, value, key, elem);
                                style[key] = value;
                            });
                        });
                    };

                // Text setter
                wrapper.textSetter = function(value) {
                    if (value !== element.innerHTML) {
                        delete this.bBox;
                    }
                    this.textStr = value;
                    element.innerHTML = pick(value, '');
                    wrapper.htmlUpdateTransform();
                };

                // Add setters for the element itself (#4938)
                if (isSVG) { // #4938, only for HTML within SVG
                    addSetters(wrapper, wrapper.element.style);
                }

                // Various setters which rely on update transform
                wrapper.xSetter =
                    wrapper.ySetter =
                    wrapper.alignSetter =
                    wrapper.rotationSetter =
                    function(value, key) {
                        if (key === 'align') {
                            // Do not overwrite the SVGElement.align method. Same as VML.
                            key = 'textAlign';
                        }
                        wrapper[key] = value;
                        wrapper.htmlUpdateTransform();
                    };

                // Set the default attributes
                wrapper
                    .attr({
                        text: str,
                        x: Math.round(x),
                        y: Math.round(y)
                    })
                    .css({

                        fontFamily: this.style.fontFamily,
                        fontSize: this.style.fontSize,

                        position: 'absolute'
                    });

                // Keep the whiteSpace style outside the wrapper.styles collection
                element.style.whiteSpace = 'nowrap';

                // Use the HTML specific .css method
                wrapper.css = wrapper.htmlCss;

                // This is specific for HTML within SVG
                if (isSVG) {
                    wrapper.add = function(svgGroupWrapper) {

                        var htmlGroup,
                            container = renderer.box.parentNode,
                            parentGroup,
                            parents = [];

                        this.parentGroup = svgGroupWrapper;

                        // Create a mock group to hold the HTML elements
                        if (svgGroupWrapper) {
                            htmlGroup = svgGroupWrapper.div;
                            if (!htmlGroup) {

                                // Read the parent chain into an array and read from top
                                // down
                                parentGroup = svgGroupWrapper;
                                while (parentGroup) {

                                    parents.push(parentGroup);

                                    // Move up to the next parent group
                                    parentGroup = parentGroup.parentGroup;
                                }

                                // Ensure dynamically updating position when any parent
                                // is translated
                                each(parents.reverse(), function(parentGroup) {
                                    var htmlGroupStyle,
                                        cls = attr(parentGroup.element, 'class');

                                    // Common translate setter for X and Y on the HTML
                                    // group. Using CSS transform instead of left and
                                    // right prevents flickering in IE and Edge when 
                                    // moving tooltip (#6957).
                                    function translateSetter(value, key) {
                                        parentGroup[key] = value;

                                        // In IE and Edge, use translate because items
                                        // would flicker below a HTML tooltip (#6957)
                                        if (isMS) {
                                            htmlGroupStyle[renderer.getTransformKey()] =
                                                'translate(' + (
                                                    parentGroup.x ||
                                                    parentGroup.translateX
                                                ) + 'px,' + (
                                                    parentGroup.y ||
                                                    parentGroup.translateY
                                                ) + 'px)';

                                            // Otherwise, use left and top. Using translate
                                            // doesn't work well with offline export (#7254,
                                            // #7280)
                                        } else {
                                            if (key === 'translateX') {
                                                htmlGroupStyle.left = value + 'px';
                                            } else {
                                                htmlGroupStyle.top = value + 'px';
                                            }
                                        }

                                        parentGroup.doTransform = true;
                                    }

                                    if (cls) {
                                        cls = {
                                            className: cls
                                        };
                                    } // else null

                                    // Create a HTML div and append it to the parent div
                                    // to emulate the SVG group structure
                                    htmlGroup =
                                        parentGroup.div =
                                        parentGroup.div || createElement('div', cls, {
                                            position: 'absolute',
                                            left: (parentGroup.translateX || 0) + 'px',
                                            top: (parentGroup.translateY || 0) + 'px',
                                            display: parentGroup.display,
                                            opacity: parentGroup.opacity, // #5075
                                            pointerEvents: (
                                                parentGroup.styles &&
                                                parentGroup.styles.pointerEvents
                                            ) // #5595

                                            // the top group is appended to container
                                        }, htmlGroup || container);

                                    // Shortcut
                                    htmlGroupStyle = htmlGroup.style;

                                    // Set listeners to update the HTML div's position
                                    // whenever the SVG group position is changed.
                                    extend(parentGroup, {
                                        classSetter: function(value) {
                                            this.element.setAttribute('class', value);
                                            htmlGroup.className = value;
                                        },
                                        on: function() {
                                            if (parents[0].div) { // #6418
                                                wrapper.on.apply({
                                                        element: parents[0].div
                                                    },
                                                    arguments
                                                );
                                            }
                                            return parentGroup;
                                        },
                                        translateXSetter: translateSetter,
                                        translateYSetter: translateSetter
                                    });
                                    addSetters(parentGroup, htmlGroupStyle);
                                });

                            }
                        } else {
                            htmlGroup = container;
                        }

                        htmlGroup.appendChild(element);

                        // Shared with VML:
                        wrapper.added = true;
                        if (wrapper.alignOnAdd) {
                            wrapper.htmlUpdateTransform();
                        }

                        return wrapper;
                    };
                }
                return wrapper;
            }
        });

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var color = H.color,
            getTZOffset = H.getTZOffset,
            isTouchDevice = H.isTouchDevice,
            merge = H.merge,
            pick = H.pick,
            svg = H.svg,
            win = H.win;

        /* ****************************************************************************
         * Handle the options                                                         *
         *****************************************************************************/
        /** 	 
         * @optionparent
         */
        H.defaultOptions = {


            /**
             * An array containing the default colors for the chart's series. When
             * all colors are used, new colors are pulled from the start again.
             * 
             * Default colors can also be set on a series or series.type basis,
             * see [column.colors](#plotOptions.column.colors), [pie.colors](#plotOptions.
             * pie.colors).
             * 
             * In styled mode, the colors option doesn't exist. Instead, colors
             * are defined in CSS and applied either through series or point class
             * names, or through the [chart.colorCount](#chart.colorCount) option.
             * 
             * 
             * ### Legacy
             * 
             * In Highcharts 3.x, the default colors were:
             * 
             * <pre>colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', 
             *     '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a']</pre> 
             * 
             * In Highcharts 2.x, the default colors were:
             * 
             * <pre>colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE', 
             *    '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92']</pre>
             * 
             * @type {Array<Color>}
             * @sample {highcharts} highcharts/chart/colors/ Assign a global color theme
             * @default ["#7cb5ec", "#434348", "#90ed7d", "#f7a35c", "#8085e9",
             *          "#f15c80", "#e4d354", "#2b908f", "#f45b5b", "#91e8e1"]
             */
            colors: '#7cb5ec #434348 #90ed7d #f7a35c #8085e9 #f15c80 #e4d354 #2b908f #f45b5b #91e8e1'.split(' '),



            /**
             * Styled mode only. Configuration object for adding SVG definitions for
             * reusable elements. See [gradients, shadows and patterns](http://www.
             * highcharts.com/docs/chart-design-and-style/gradients-shadows-and-
             * patterns) for more information and code examples.
             * 
             * @type {Object}
             * @since 5.0.0
             * @apioption defs
             */

            /**
             * @ignore
             */
            symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
            lang: {

                /**
                 * The loading text that appears when the chart is set into the loading
                 * state following a call to `chart.showLoading`.
                 * 
                 * @type {String}
                 * @default Loading...
                 */
                loading: 'Loading...',

                /**
                 * An array containing the months names. Corresponds to the `%B` format
                 * in `Highcharts.dateFormat()`.
                 * 
                 * @type {Array<String>}
                 * @default [ "January" , "February" , "March" , "April" , "May" ,
                 *          "June" , "July" , "August" , "September" , "October" ,
                 *          "November" , "December"]
                 */
                months: [
                    'January', 'February', 'March', 'April', 'May', 'June', 'July',
                    'August', 'September', 'October', 'November', 'December'
                ],

                /**
                 * An array containing the months names in abbreviated form. Corresponds
                 * to the `%b` format in `Highcharts.dateFormat()`.
                 * 
                 * @type {Array<String>}
                 * @default [ "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" ,
                 *          "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec"]
                 */
                shortMonths: [
                    'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul',
                    'Aug', 'Sep', 'Oct', 'Nov', 'Dec'
                ],

                /**
                 * An array containing the weekday names.
                 * 
                 * @type {Array<String>}
                 * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday",
                 *          "Friday", "Saturday"]
                 */
                weekdays: [
                    'Sunday', 'Monday', 'Tuesday', 'Wednesday',
                    'Thursday', 'Friday', 'Saturday'
                ],

                /**
                 * Short week days, starting Sunday. If not specified, Highcharts uses
                 * the first three letters of the `lang.weekdays` option.
                 * 
                 * @type {Array<String>}
                 * @sample highcharts/lang/shortweekdays/
                 *         Finnish two-letter abbreviations
                 * @since 4.2.4
                 * @apioption lang.shortWeekdays
                 */

                /**
                 * What to show in a date field for invalid dates. Defaults to an empty
                 * string.
                 * 
                 * @type {String}
                 * @since 4.1.8
                 * @product highcharts highstock
                 * @apioption lang.invalidDate
                 */

                /**
                 * The default decimal point used in the `Highcharts.numberFormat`
                 * method unless otherwise specified in the function arguments.
                 * 
                 * @type {String}
                 * @default .
                 * @since 1.2.2
                 */
                decimalPoint: '.',

                /**
                 * [Metric prefixes](http://en.wikipedia.org/wiki/Metric_prefix) used
                 * to shorten high numbers in axis labels. Replacing any of the positions
                 * with `null` causes the full number to be written. Setting `numericSymbols`
                 * to `null` disables shortening altogether.
                 * 
                 * @type {Array<String>}
                 * @sample {highcharts} highcharts/lang/numericsymbols/
                 *         Replacing the symbols with text
                 * @sample {highstock} highcharts/lang/numericsymbols/
                 *         Replacing the symbols with text
                 * @default [ "k" , "M" , "G" , "T" , "P" , "E"]
                 * @since 2.3.0
                 */
                numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'],

                /**
                 * The magnitude of [numericSymbols](#lang.numericSymbol) replacements.
                 * Use 10000 for Japanese, Korean and various Chinese locales, which
                 * use symbols for 10^4, 10^8 and 10^12.
                 * 
                 * @type {Number}
                 * @sample highcharts/lang/numericsymbolmagnitude/
                 *         10000 magnitude for Japanese
                 * @default 1000
                 * @since 5.0.3
                 * @apioption lang.numericSymbolMagnitude
                 */

                /**
                 * The text for the label appearing when a chart is zoomed.
                 * 
                 * @type {String}
                 * @default Reset zoom
                 * @since 1.2.4
                 */
                resetZoom: 'Reset zoom',

                /**
                 * The tooltip title for the label appearing when a chart is zoomed.
                 * 
                 * @type {String}
                 * @default Reset zoom level 1:1
                 * @since 1.2.4
                 */
                resetZoomTitle: 'Reset zoom level 1:1',

                /**
                 * The default thousands separator used in the `Highcharts.numberFormat`
                 * method unless otherwise specified in the function arguments. Since
                 * Highcharts 4.1 it defaults to a single space character, which is
                 * compatible with ISO and works across Anglo-American and continental
                 * European languages.
                 * 
                 * The default is a single space.
                 * 
                 * @type {String}
                 * @default  
                 * @since 1.2.2
                 */
                thousandsSep: ' '
            },

            /**
             * Global options that don't apply to each chart. These options, like
             * the `lang` options, must be set using the `Highcharts.setOptions`
             * method.
             * 
             * <pre>Highcharts.setOptions({
             *     global: {
             *         useUTC: false
             *     }
             * });</pre>
             *
             */
            global: {

                /**
                 * Whether to use UTC time for axis scaling, tickmark placement and
                 * time display in `Highcharts.dateFormat`. Advantages of using UTC
                 * is that the time displays equally regardless of the user agent's
                 * time zone settings. Local time can be used when the data is loaded
                 * in real time or when correct Daylight Saving Time transitions are
                 * required.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/global/useutc-true/ True by default
                 * @sample {highcharts} highcharts/global/useutc-false/ False
                 * @default true
                 */
                useUTC: true

                /**
                 * A custom `Date` class for advanced date handling. For example,
                 * [JDate](https://githubcom/tahajahangir/jdate) can be hooked in to
                 * handle Jalali dates.
                 * 
                 * @type {Object}
                 * @since 4.0.4
                 * @product highcharts highstock
                 * @apioption global.Date
                 */

                /**
                 * _Canvg rendering for Android 2.x is removed as of Highcharts 5.0\.
                 * Use the [libURL](#exporting.libURL) option to configure exporting._
                 * 
                 * The URL to the additional file to lazy load for Android 2.x devices.
                 * These devices don't support SVG, so we download a helper file that
                 * contains [canvg](http://code.google.com/p/canvg/), its dependency
                 * rbcolor, and our own CanVG Renderer class. To avoid hotlinking to
                 * our site, you can install canvas-tools.js on your own server and
                 * change this option accordingly.
                 * 
                 * @type {String}
                 * @deprecated
                 * @default http://code.highcharts.com/{version}/modules/canvas-tools.js
                 * @product highcharts highmaps
                 * @apioption global.canvasToolsURL
                 */

                /**
                 * A callback to return the time zone offset for a given datetime. It
                 * takes the timestamp in terms of milliseconds since January 1 1970,
                 * and returns the timezone offset in minutes. This provides a hook
                 * for drawing time based charts in specific time zones using their
                 * local DST crossover dates, with the help of external libraries.
                 * 
                 * @type {Function}
                 * @see [global.timezoneOffset](#global.timezoneOffset)
                 * @sample {highcharts} highcharts/global/gettimezoneoffset/
                 *         Use moment.js to draw Oslo time regardless of browser locale
                 * @sample {highstock} highcharts/global/gettimezoneoffset/
                 *         Use moment.js to draw Oslo time regardless of browser locale
                 * @since 4.1.0
                 * @product highcharts highstock
                 * @apioption global.getTimezoneOffset
                 */

                /**
                 * Requires [moment.js](http://momentjs.com/). If the timezone option
                 * is specified, it creates a default
                 * [getTimezoneOffset](#global.getTimezoneOffset) function that looks
                 * up the specified timezone in moment.js. If moment.js is not included,
                 * this throws a Highcharts error in the console, but does not crash the
                 * chart.
                 * 
                 * @type {String}
                 * @see [getTimezoneOffset](#global.getTimezoneOffset)
                 * @sample {highcharts} highcharts/global/timezone/ Europe/Oslo
                 * @sample {highstock} highcharts/global/timezone/ Europe/Oslo
                 * @default undefined
                 * @since 5.0.7
                 * @product highcharts highstock
                 * @apioption global.timezone
                 */

                /**
                 * The timezone offset in minutes. Positive values are west, negative
                 * values are east of UTC, as in the ECMAScript [getTimezoneOffset](https://developer.
                 * mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset)
                 * method. Use this to display UTC based data in a predefined time zone.
                 * 
                 * @type {Number}
                 * @see [global.getTimezoneOffset](#global.getTimezoneOffset)
                 * @sample {highcharts} highcharts/global/timezoneoffset/
                 *         Timezone offset
                 * @sample {highstock} highcharts/global/timezoneoffset/
                 *         Timezone offset
                 * @default 0
                 * @since 3.0.8
                 * @product highcharts highstock
                 * @apioption global.timezoneOffset
                 */
            },
            chart: {

                /**
                 * When using multiple axis, the ticks of two or more opposite axes
                 * will automatically be aligned by adding ticks to the axis or axes
                 * with the least ticks, as if `tickAmount` were specified.
                 * 
                 * This can be prevented by setting `alignTicks` to false. If the grid
                 * lines look messy, it's a good idea to hide them for the secondary
                 * axis by setting `gridLineWidth` to 0.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/chart/alignticks-true/ True by default
                 * @sample {highcharts} highcharts/chart/alignticks-false/ False
                 * @sample {highstock} stock/chart/alignticks-true/
                 *         True by default
                 * @sample {highstock} stock/chart/alignticks-false/
                 *         False
                 * @default true
                 * @product highcharts highstock
                 * @apioption chart.alignTicks
                 */


                /**
                 * Set the overall animation for all chart updating. Animation can be
                 * disabled throughout the chart by setting it to false here. It can
                 * be overridden for each individual API method as a function parameter.
                 * The only animation not affected by this option is the initial series
                 * animation, see [plotOptions.series.animation](#plotOptions.series.
                 * animation).
                 * 
                 * The animation can either be set as a boolean or a configuration
                 * object. If `true`, it will use the 'swing' jQuery easing and a
                 * duration of 500 ms. If used as a configuration object, the following
                 * properties are supported:
                 * 
                 * <dl>
                 * 
                 * <dt>duration</dt>
                 * 
                 * <dd>The duration of the animation in milliseconds.</dd>
                 * 
                 * <dt>easing</dt>
                 * 
                 * <dd>A string reference to an easing function set on the `Math` object.
                 * See [the easing demo](http://jsfiddle.net/gh/get/library/pure/
                 * highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
                 * series-animation-easing/).</dd>
                 * 
                 * </dl>
                 * 
                 * @type {Boolean|Object}
                 * @sample {highcharts} highcharts/chart/animation-none/
                 *         Updating with no animation
                 * @sample {highcharts} highcharts/chart/animation-duration/
                 *         With a longer duration
                 * @sample {highcharts} highcharts/chart/animation-easing/
                 *         With a jQuery UI easing
                 * @sample {highmaps} maps/chart/animation-none/
                 *         Updating with no animation
                 * @sample {highmaps} maps/chart/animation-duration/
                 *         With a longer duration
                 * @default true
                 * @apioption chart.animation
                 */

                /**
                 * A CSS class name to apply to the charts container `div`, allowing
                 * unique CSS styling for each chart.
                 * 
                 * @type {String}
                 * @apioption chart.className
                 */

                /**
                 * Event listeners for the chart.
                 * 
                 * @apioption chart.events
                 */

                /**
                 * Fires when a series is added to the chart after load time, using
                 * the `addSeries` method. One parameter, `event`, is passed to the
                 * function, containing common event information.
                 * Through `event.options` you can access the series options that was
                 * passed to the `addSeries` method. Returning false prevents the series
                 * from being added.
                 * 
                 * @type {Function}
                 * @context Chart
                 * @sample {highcharts} highcharts/chart/events-addseries/ Alert on add series
                 * @sample {highstock} stock/chart/events-addseries/ Alert on add series
                 * @since 1.2.0
                 * @apioption chart.events.addSeries
                 */

                /**
                 * Fires when clicking on the plot background. One parameter, `event`,
                 * is passed to the function, containing common event information.
                 * 
                 * Information on the clicked spot can be found through `event.xAxis`
                 * and `event.yAxis`, which are arrays containing the axes of each dimension
                 * and each axis' value at the clicked spot. The primary axes are `event.
                 * xAxis[0]` and `event.yAxis[0]`. Remember the unit of a datetime axis
                 * is milliseconds since 1970-01-01 00:00:00.
                 * 
                 * <pre>click: function(e) {
                 *     console.log(
                 *         Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', e.xAxis[0].value),
                 *         e.yAxis[0].value
                 *     )
                 * }</pre>
                 * 
                 * @type {Function}
                 * @context Chart
                 * @sample {highcharts} highcharts/chart/events-click/
                 *         Alert coordinates on click
                 * @sample {highcharts} highcharts/chart/events-container/
                 *         Alternatively, attach event to container
                 * @sample {highstock} stock/chart/events-click/
                 *         Alert coordinates on click
                 * @sample {highstock} highcharts/chart/events-container/
                 *         Alternatively, attach event to container
                 * @sample {highmaps} maps/chart/events-click/
                 *         Record coordinates on click
                 * @sample {highmaps} highcharts/chart/events-container/
                 *         Alternatively, attach event to container
                 * @since 1.2.0
                 * @apioption chart.events.click
                 */


                /**
                 * Fires when the chart is finished loading. Since v4.2.2, it also waits
                 * for images to be loaded, for example from point markers. One parameter,
                 * `event`, is passed to the function, containing common event information.
                 * 
                 * There is also a second parameter to the chart constructor where a
                 * callback function can be passed to be executed on chart.load.
                 * 
                 * @type {Function}
                 * @context Chart
                 * @sample {highcharts} highcharts/chart/events-load/
                 *         Alert on chart load
                 * @sample {highstock} stock/chart/events-load/
                 *         Alert on chart load
                 * @sample {highmaps} maps/chart/events-load/
                 *         Add series on chart load
                 * @apioption chart.events.load
                 */

                /**
                 * Fires when the chart is redrawn, either after a call to chart.redraw()
                 * or after an axis, series or point is modified with the `redraw` option
                 * set to true. One parameter, `event`, is passed to the function, containing common event information.
                 * 
                 * @type {Function}
                 * @context Chart
                 * @sample {highcharts} highcharts/chart/events-redraw/
                 *         Alert on chart redraw
                 * @sample {highstock} stock/chart/events-redraw/
                 *         Alert on chart redraw when adding a series or moving the
                 *         zoomed range
                 * @sample {highmaps} maps/chart/events-redraw/
                 *         Set subtitle on chart redraw
                 * @since 1.2.0
                 * @apioption chart.events.redraw
                 */

                /**
                 * Fires after initial load of the chart (directly after the `load`
                 * event), and after each redraw (directly after the `redraw` event).
                 * 
                 * @type {Function}
                 * @context Chart
                 * @since 5.0.7
                 * @apioption chart.events.render
                 */

                /**
                 * Fires when an area of the chart has been selected. Selection is enabled
                 * by setting the chart's zoomType. One parameter, `event`, is passed
                 * to the function, containing common event information. The default action for the selection event is to
                 * zoom the chart to the selected area. It can be prevented by calling
                 * `event.preventDefault()`.
                 * 
                 * Information on the selected area can be found through `event.xAxis`
                 * and `event.yAxis`, which are arrays containing the axes of each dimension
                 * and each axis' min and max values. The primary axes are `event.xAxis[0]`
                 * and `event.yAxis[0]`. Remember the unit of a datetime axis is milliseconds
                 * since 1970-01-01 00:00:00.
                 * 
                 * <pre>selection: function(event) {
                 *     // log the min and max of the primary, datetime x-axis
                 *     console.log(
                 *         Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', event.xAxis[0].min),
                 *         Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', event.xAxis[0].max)
                 *     );
                 *     // log the min and max of the y axis
                 *     console.log(event.yAxis[0].min, event.yAxis[0].max);
                 * }</pre>
                 * 
                 * @type {Function}
                 * @sample {highcharts} highcharts/chart/events-selection/
                 *         Report on selection and reset
                 * @sample {highcharts} highcharts/chart/events-selection-points/
                 *         Select a range of points through a drag selection
                 * @sample {highstock} stock/chart/events-selection/
                 *         Report on selection and reset
                 * @sample {highstock} highcharts/chart/events-selection-points/
                 *         Select a range of points through a drag selection (Highcharts)
                 * @apioption chart.events.selection
                 */

                /**
                 * The margin between the outer edge of the chart and the plot area.
                 * The numbers in the array designate top, right, bottom and left
                 * respectively. Use the options `marginTop`, `marginRight`,
                 * `marginBottom` and `marginLeft` for shorthand setting of one option.
                 * 
                 * By default there is no margin. The actual space is dynamically calculated
                 * from the offset of axis labels, axis title, title, subtitle and legend
                 * in addition to the `spacingTop`, `spacingRight`, `spacingBottom`
                 * and `spacingLeft` options.
                 * 
                 * @type {Array}
                 * @sample {highcharts} highcharts/chart/margins-zero/
                 *         Zero margins
                 * @sample {highstock} stock/chart/margin-zero/
                 *         Zero margins
                 *
                 * @defaults {all} null
                 * @apioption chart.margin
                 */

                /**
                 * The margin between the bottom outer edge of the chart and the plot
                 * area. Use this to set a fixed pixel value for the margin as opposed
                 * to the default dynamic margin. See also `spacingBottom`.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/chart/marginbottom/
                 *         100px bottom margin
                 * @sample {highstock} stock/chart/marginbottom/
                 *         100px bottom margin
                 * @sample {highmaps} maps/chart/margin/
                 *         100px margins
                 * @since 2.0
                 * @apioption chart.marginBottom
                 */

                /**
                 * The margin between the left outer edge of the chart and the plot
                 * area. Use this to set a fixed pixel value for the margin as opposed
                 * to the default dynamic margin. See also `spacingLeft`.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/chart/marginleft/
                 *         150px left margin
                 * @sample {highstock} stock/chart/marginleft/
                 *         150px left margin
                 * @sample {highmaps} maps/chart/margin/
                 *         100px margins
                 * @default null
                 * @since 2.0
                 * @apioption chart.marginLeft
                 */

                /**
                 * The margin between the right outer edge of the chart and the plot
                 * area. Use this to set a fixed pixel value for the margin as opposed
                 * to the default dynamic margin. See also `spacingRight`.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/chart/marginright/
                 *         100px right margin
                 * @sample {highstock} stock/chart/marginright/
                 *         100px right margin
                 * @sample {highmaps} maps/chart/margin/
                 *         100px margins
                 * @default null
                 * @since 2.0
                 * @apioption chart.marginRight
                 */

                /**
                 * The margin between the top outer edge of the chart and the plot area.
                 * Use this to set a fixed pixel value for the margin as opposed to
                 * the default dynamic margin. See also `spacingTop`.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/chart/margintop/ 100px top margin
                 * @sample {highstock} stock/chart/margintop/
                 *         100px top margin
                 * @sample {highmaps} maps/chart/margin/
                 *         100px margins
                 * @default null
                 * @since 2.0
                 * @apioption chart.marginTop
                 */

                /**
                 * Allows setting a key to switch between zooming and panning. Can be
                 * one of `alt`, `ctrl`, `meta` (the command key on Mac and Windows
                 * key on Windows) or `shift`. The keys are mapped directly to the key
                 * properties of the click event argument (`event.altKey`, `event.ctrlKey`,
                 * `event.metaKey` and `event.shiftKey`).
                 * 
                 * @validvalue [null, "alt", "ctrl", "meta", "shift"]
                 * @type {String}
                 * @since 4.0.3
                 * @product highcharts
                 * @apioption chart.panKey
                 */

                /**
                 * Allow panning in a chart. Best used with [panKey](#chart.panKey)
                 * to combine zooming and panning.
                 * 
                 * On touch devices, when the [tooltip.followTouchMove](#tooltip.followTouchMove)
                 * option is `true` (default), panning requires two fingers. To allow
                 * panning with one finger, set `followTouchMove` to `false`.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/chart/pankey/ Zooming and panning
                 * @default {highcharts} false
                 * @default {highstock} true
                 * @since 4.0.3
                 * @product highcharts highstock
                 * @apioption chart.panning
                 */


                /**
                 * Equivalent to [zoomType](#chart.zoomType), but for multitouch gestures
                 * only. By default, the `pinchType` is the same as the `zoomType` setting.
                 * However, pinching can be enabled separately in some cases, for example
                 * in stock charts where a mouse drag pans the chart, while pinching
                 * is enabled. When [tooltip.followTouchMove](#tooltip.followTouchMove)
                 * is true, pinchType only applies to two-finger touches.
                 * 
                 * @validvalue ["x", "y", "xy"]
                 * @type {String}
                 * @default {highcharts} null
                 * @default {highstock} x
                 * @since 3.0
                 * @product highcharts highstock
                 * @apioption chart.pinchType
                 */

                /**
                 * The corner radius of the outer chart border.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/chart/borderradius/ 20px radius
                 * @sample {highstock} stock/chart/border/ 10px radius
                 * @sample {highmaps} maps/chart/border/ Border options
                 * @default 0
                 */
                borderRadius: 0,


                /**
                 * Alias of `type`.
                 * 
                 * @validvalue ["line", "spline", "column", "area", "areaspline", "pie"]
                 * @type {String}
                 * @deprecated
                 * @sample {highcharts} highcharts/chart/defaultseriestype/ Bar
                 * @default line
                 * @product highcharts
                 */
                defaultSeriesType: 'line',

                /**
                 * If true, the axes will scale to the remaining visible series once
                 * one series is hidden. If false, hiding and showing a series will
                 * not affect the axes or the other series. For stacks, once one series
                 * within the stack is hidden, the rest of the stack will close in
                 * around it even if the axis is not affected.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/chart/ignorehiddenseries-true/
                 *         True by default
                 * @sample {highcharts} highcharts/chart/ignorehiddenseries-false/
                 *         False
                 * @sample {highcharts} highcharts/chart/ignorehiddenseries-true-stacked/
                 *         True with stack
                 * @sample {highstock} stock/chart/ignorehiddenseries-true/
                 *         True by default
                 * @sample {highstock} stock/chart/ignorehiddenseries-false/
                 *         False
                 * @default true
                 * @since 1.2.0
                 * @product highcharts highstock
                 */
                ignoreHiddenSeries: true,


                /**
                 * Whether to invert the axes so that the x axis is vertical and y axis
                 * is horizontal. When `true`, the x axis is [reversed](#xAxis.reversed)
                 * by default.
                 *
                 * @productdesc {highcharts}
                 * If a bar series is present in the chart, it will be inverted
                 * automatically. Inverting the chart doesn't have an effect if there
                 * are no cartesian series in the chart, or if the chart is
                 * [polar](#chart.polar).
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/chart/inverted/
                 *         Inverted line
                 * @sample {highstock} stock/navigator/inverted/
                 *         Inverted stock chart
                 * @default false
                 * @product highcharts highstock
                 * @apioption chart.inverted
                 */

                /**
                 * The distance between the outer edge of the chart and the content,
                 * like title or legend, or axis title and labels if present. The
                 * numbers in the array designate top, right, bottom and left respectively.
                 * Use the options spacingTop, spacingRight, spacingBottom and spacingLeft
                 * options for shorthand setting of one option.
                 * 
                 * @type {Array<Number>}
                 * @see [chart.margin](#chart.margin)
                 * @default [10, 10, 15, 10]
                 * @since 3.0.6
                 */
                spacing: [10, 10, 15, 10],

                /**
                 * The button that appears after a selection zoom, allowing the user
                 * to reset zoom.
                 *
                 */
                resetZoomButton: {

                    /**
                     * A collection of attributes for the button. The object takes SVG
                     * attributes like `fill`, `stroke`, `stroke-width` or `r`, the border
                     * radius. The theme also supports `style`, a collection of CSS properties
                     * for the text. Equivalent attributes for the hover state are given
                     * in `theme.states.hover`.
                     * 
                     * @type {Object}
                     * @sample {highcharts} highcharts/chart/resetzoombutton-theme/
                     *         Theming the button
                     * @sample {highstock} highcharts/chart/resetzoombutton-theme/
                     *         Theming the button
                     * @since 2.2
                     */
                    theme: {

                        /**
                         * The Z index for the reset zoom button.
                         */
                        zIndex: 20
                    },

                    /**
                     * The position of the button.
                     * 
                     * @type {Object}
                     * @sample {highcharts} highcharts/chart/resetzoombutton-position/
                     *         Above the plot area
                     * @sample {highstock} highcharts/chart/resetzoombutton-position/
                     *         Above the plot area
                     * @sample {highmaps} highcharts/chart/resetzoombutton-position/
                     *         Above the plot area
                     * @since 2.2
                     */
                    position: {

                        /**
                         * The horizontal alignment of the button.
                         * 
                         * @type {String}
                         */
                        align: 'right',

                        /**
                         * The horizontal offset of the button.
                         * 
                         * @type {Number}
                         */
                        x: -10,

                        /**
                         * The vertical alignment of the button.
                         * 
                         * @validvalue ["top", "middle", "bottom"]
                         * @type {String}
                         * @default top
                         * @apioption chart.resetZoomButton.position.verticalAlign
                         */

                        /**
                         * The vertical offset of the button.
                         * 
                         * @type {Number}
                         */
                        y: 10
                    }

                    /**
                     * What frame the button should be placed related to. Can be either
                     * `plot` or `chart`
                     * 
                     * @validvalue ["plot", "chart"]
                     * @type {String}
                     * @sample {highcharts} highcharts/chart/resetzoombutton-relativeto/
                     *         Relative to the chart
                     * @sample {highstock} highcharts/chart/resetzoombutton-relativeto/
                     *         Relative to the chart
                     * @default plot
                     * @since 2.2
                     * @apioption chart.resetZoomButton.relativeTo
                     */
                },

                /**
                 * An explicit width for the chart. By default (when `null`) the width
                 * is calculated from the offset width of the containing element.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/chart/width/ 800px wide
                 * @sample {highstock} stock/chart/width/ 800px wide
                 * @sample {highmaps} maps/chart/size/ Chart with explicit size
                 * @default null
                 */
                width: null,

                /**
                 * An explicit height for the chart. If a _number_, the height is
                 * given in pixels. If given a _percentage string_ (for example `'56%'`),
                 * the height is given as the percentage of the actual chart width.
                 * This allows for preserving the aspect ratio across responsive
                 * sizes.
                 * 
                 * By default (when `null`) the height is calculated from the offset
                 * height of the containing element, or 400 pixels if the containing
                 * element's height is 0.
                 * 
                 * @type {Number|String}
                 * @sample {highcharts} highcharts/chart/height/
                 *         500px height
                 * @sample {highstock} stock/chart/height/
                 *         300px height
                 * @sample {highmaps} maps/chart/size/
                 *         Chart with explicit size
                 * @sample highcharts/chart/height-percent/
                 *         Highcharts with percentage height
                 * @default null
                 */
                height: null,



                /**
                 * The color of the outer chart border.
                 * 
                 * @type {Color}
                 * @see In styled mode, the stroke is set with the `.highcharts-background`
                 * class.
                 * @sample {highcharts} highcharts/chart/bordercolor/ Brown border
                 * @sample {highstock} stock/chart/border/ Brown border
                 * @sample {highmaps} maps/chart/border/ Border options
                 * @default #335cad
                 */
                borderColor: '#335cad',

                /**
                 * The pixel width of the outer chart border.
                 * 
                 * @type {Number}
                 * @see In styled mode, the stroke is set with the `.highcharts-background`
                 * class.
                 * @sample {highcharts} highcharts/chart/borderwidth/ 5px border
                 * @sample {highstock} stock/chart/border/
                 *         2px border
                 * @sample {highmaps} maps/chart/border/
                 *         Border options
                 * @default 0
                 * @apioption chart.borderWidth
                 */

                /**
                 * The background color or gradient for the outer chart area.
                 * 
                 * @type {Color}
                 * @see In styled mode, the background is set with the `.highcharts-background` class.
                 * @sample {highcharts} highcharts/chart/backgroundcolor-color/ Color
                 * @sample {highcharts} highcharts/chart/backgroundcolor-gradient/ Gradient
                 * @sample {highstock} stock/chart/backgroundcolor-color/
                 *         Color
                 * @sample {highstock} stock/chart/backgroundcolor-gradient/
                 *         Gradient
                 * @sample {highmaps} maps/chart/backgroundcolor-color/
                 *         Color
                 * @sample {highmaps} maps/chart/backgroundcolor-gradient/
                 *         Gradient
                 * @default #FFFFFF
                 */
                backgroundColor: '#ffffff',

                /**
                 * The background color or gradient for the plot area.
                 * 
                 * @type {Color}
                 * @see In styled mode, the plot background is set with the `.highcharts-plot-background` class.
                 * @sample {highcharts} highcharts/chart/plotbackgroundcolor-color/
                 *         Color
                 * @sample {highcharts} highcharts/chart/plotbackgroundcolor-gradient/
                 *         Gradient
                 * @sample {highstock} stock/chart/plotbackgroundcolor-color/
                 *         Color
                 * @sample {highstock} stock/chart/plotbackgroundcolor-gradient/
                 *         Gradient
                 * @sample {highmaps} maps/chart/plotbackgroundcolor-color/
                 *         Color
                 * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
                 *         Gradient
                 * @default null
                 * @apioption chart.plotBackgroundColor
                 */


                /**
                 * The URL for an image to use as the plot background. To set an image
                 * as the background for the entire chart, set a CSS background image
                 * to the container element. Note that for the image to be applied to
                 * exported charts, its URL needs to be accessible by the export server.
                 * 
                 * @type {String}
                 * @see In styled mode, a plot background image can be set with the
                 * `.highcharts-plot-background` class and a [custom pattern](http://www.
                 * highcharts.com/docs/chart-design-and-style/gradients-shadows-and-
                 * patterns).
                 * @sample {highcharts} highcharts/chart/plotbackgroundimage/ Skies
                 * @sample {highstock} stock/chart/plotbackgroundimage/ Skies
                 * @default null
                 * @apioption chart.plotBackgroundImage
                 */

                /**
                 * The color of the inner chart or plot area border.
                 * 
                 * @type {Color}
                 * @see In styled mode, a plot border stroke can be set with the `.
                 * highcharts-plot-border` class.
                 * @sample {highcharts} highcharts/chart/plotbordercolor/ Blue border
                 * @sample {highstock} stock/chart/plotborder/ Blue border
                 * @sample {highmaps} maps/chart/plotborder/ Plot border options
                 * @default #cccccc
                 */
                plotBorderColor: '#cccccc'


                /**
                 * The pixel width of the plot area border.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/chart/plotborderwidth/ 1px border
                 * @sample {highstock} stock/chart/plotborder/
                 *         2px border
                 * @sample {highmaps} maps/chart/plotborder/
                 *         Plot border options
                 * @default 0
                 * @apioption chart.plotBorderWidth
                 */

                /**
                 * Whether to apply a drop shadow to the plot area. Requires that
                 * plotBackgroundColor be set. The shadow can be an object configuration
                 * containing `color`, `offsetX`, `offsetY`, `opacity` and `width`.
                 * 
                 * @type {Boolean|Object}
                 * @sample {highcharts} highcharts/chart/plotshadow/ Plot shadow
                 * @sample {highstock} stock/chart/plotshadow/
                 *         Plot shadow
                 * @sample {highmaps} maps/chart/plotborder/
                 *         Plot border options
                 * @default false
                 * @apioption chart.plotShadow
                 */

                /**
                 * When true, cartesian charts like line, spline, area and column are
                 * transformed into the polar coordinate system. Requires `highcharts-
                 * more.js`.
                 * 
                 * @type {Boolean}
                 * @default false
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption chart.polar
                 */

                /**
                 * Whether to reflow the chart to fit the width of the container div
                 * on resizing the window.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/chart/reflow-true/ True by default
                 * @sample {highcharts} highcharts/chart/reflow-false/ False
                 * @sample {highstock} stock/chart/reflow-true/
                 *         True by default
                 * @sample {highstock} stock/chart/reflow-false/
                 *         False
                 * @sample {highmaps} maps/chart/reflow-true/
                 *         True by default
                 * @sample {highmaps} maps/chart/reflow-false/
                 *         False
                 * @default true
                 * @since 2.1
                 * @apioption chart.reflow
                 */




                /**
                 * The HTML element where the chart will be rendered. If it is a string,
                 * the element by that id is used. The HTML element can also be passed
                 * by direct reference, or as the first argument of the chart constructor,
                 *  in which case the option is not needed.
                 * 
                 * @type {String|Object}
                 * @sample {highcharts} highcharts/chart/reflow-true/
                 *         String
                 * @sample {highcharts} highcharts/chart/renderto-object/
                 *         Object reference
                 * @sample {highcharts} highcharts/chart/renderto-jquery/
                 *         Object reference through jQuery
                 * @sample {highstock} stock/chart/renderto-string/
                 *         String
                 * @sample {highstock} stock/chart/renderto-object/
                 *         Object reference
                 * @sample {highstock} stock/chart/renderto-jquery/
                 *         Object reference through jQuery
                 * @apioption chart.renderTo
                 */

                /**
                 * The background color of the marker square when selecting (zooming
                 * in on) an area of the chart.
                 * 
                 * @type {Color}
                 * @see In styled mode, the selection marker fill is set with the
                 * `.highcharts-selection-marker` class.
                 * @default rgba(51,92,173,0.25)
                 * @since 2.1.7
                 * @apioption chart.selectionMarkerFill
                 */

                /**
                 * Whether to apply a drop shadow to the outer chart area. Requires
                 * that backgroundColor be set. The shadow can be an object configuration
                 * containing `color`, `offsetX`, `offsetY`, `opacity` and `width`.
                 * 
                 * @type {Boolean|Object}
                 * @sample {highcharts} highcharts/chart/shadow/ Shadow
                 * @sample {highstock} stock/chart/shadow/
                 *         Shadow
                 * @sample {highmaps} maps/chart/border/
                 *         Chart border and shadow
                 * @default false
                 * @apioption chart.shadow
                 */

                /**
                 * Whether to show the axes initially. This only applies to empty charts
                 * where series are added dynamically, as axes are automatically added
                 * to cartesian series.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/chart/showaxes-false/ False by default
                 * @sample {highcharts} highcharts/chart/showaxes-true/ True
                 * @since 1.2.5
                 * @product highcharts
                 * @apioption chart.showAxes
                 */

                /**
                 * The space between the bottom edge of the chart and the content (plot
                 * area, axis title and labels, title, subtitle or legend in top position).
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/chart/spacingbottom/
                 *         Spacing bottom set to 100
                 * @sample {highstock} stock/chart/spacingbottom/
                 *         Spacing bottom set to 100
                 * @sample {highmaps} maps/chart/spacing/
                 *         Spacing 100 all around
                 * @default 15
                 * @since 2.1
                 * @apioption chart.spacingBottom
                 */

                /**
                 * The space between the left edge of the chart and the content (plot
                 * area, axis title and labels, title, subtitle or legend in top position).
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/chart/spacingleft/
                 *         Spacing left set to 100
                 * @sample {highstock} stock/chart/spacingleft/
                 *         Spacing left set to 100
                 * @sample {highmaps} maps/chart/spacing/
                 *         Spacing 100 all around
                 * @default 10
                 * @since 2.1
                 * @apioption chart.spacingLeft
                 */

                /**
                 * The space between the right edge of the chart and the content (plot
                 * area, axis title and labels, title, subtitle or legend in top
                 * position).
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/chart/spacingright-100/
                 *         Spacing set to 100
                 * @sample {highcharts} highcharts/chart/spacingright-legend/
                 *         Legend in right position with default spacing
                 * @sample {highstock} stock/chart/spacingright/
                 *         Spacing set to 100
                 * @sample {highmaps} maps/chart/spacing/
                 *         Spacing 100 all around
                 * @default 10
                 * @since 2.1
                 * @apioption chart.spacingRight
                 */

                /**
                 * The space between the top edge of the chart and the content (plot
                 * area, axis title and labels, title, subtitle or legend in top
                 * position).
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/chart/spacingtop-100/
                 *         A top spacing of 100
                 * @sample {highcharts} highcharts/chart/spacingtop-10/
                 *         Floating chart title makes the plot area align to the default
                 *         spacingTop of 10.
                 * @sample {highstock} stock/chart/spacingtop/
                 *         A top spacing of 100
                 * @sample {highmaps} maps/chart/spacing/
                 *         Spacing 100 all around
                 * @default 10
                 * @since 2.1
                 * @apioption chart.spacingTop
                 */

                /**
                 * Additional CSS styles to apply inline to the container `div`. Note
                 * that since the default font styles are applied in the renderer, it
                 * is ignorant of the individual chart options and must be set globally.
                 * 
                 * @type {CSSObject}
                 * @see In styled mode, general chart styles can be set with the `.highcharts-root` class.
                 * @sample {highcharts} highcharts/chart/style-serif-font/
                 *         Using a serif type font
                 * @sample {highcharts} highcharts/css/em/
                 *         Styled mode with relative font sizes
                 * @sample {highstock} stock/chart/style/
                 *         Using a serif type font
                 * @sample {highmaps} maps/chart/style-serif-font/
                 *         Using a serif type font
                 * @default {"fontFamily":"\"Lucida Grande\", \"Lucida Sans Unicode\", Verdana, Arial, Helvetica, sans-serif","fontSize":"12px"}
                 * @apioption chart.style
                 */

                /**
                 * The default series type for the chart. Can be any of the chart types
                 * listed under [plotOptions](#plotOptions).
                 * 
                 * @validvalue ["line", "spline", "column", "bar", "area", "areaspline", "pie", "arearange", "areasplinerange", "boxplot", "bubble", "columnrange", "errorbar", "funnel", "gauge", "heatmap", "polygon", "pyramid", "scatter", "solidgauge", "treemap", "waterfall"]
                 * @type {String}
                 * @sample {highcharts} highcharts/chart/type-bar/ Bar
                 * @sample {highstock} stock/chart/type/
                 *         Areaspline
                 * @sample {highmaps} maps/chart/type-mapline/
                 *         Mapline
                 * @default {highcharts} line
                 * @default {highstock} line
                 * @default {highmaps} map
                 * @since 2.1.0
                 * @apioption chart.type
                 */

                /**
                 * Decides in what dimensions the user can zoom by dragging the mouse.
                 * Can be one of `x`, `y` or `xy`.
                 * 
                 * @validvalue [null, "x", "y", "xy"]
                 * @type {String}
                 * @see [panKey](#chart.panKey)
                 * @sample {highcharts} highcharts/chart/zoomtype-none/ None by default
                 * @sample {highcharts} highcharts/chart/zoomtype-x/ X
                 * @sample {highcharts} highcharts/chart/zoomtype-y/ Y
                 * @sample {highcharts} highcharts/chart/zoomtype-xy/ Xy
                 * @sample {highstock} stock/demo/basic-line/ None by default
                 * @sample {highstock} stock/chart/zoomtype-x/ X
                 * @sample {highstock} stock/chart/zoomtype-y/ Y
                 * @sample {highstock} stock/chart/zoomtype-xy/ Xy
                 * @product highcharts highstock
                 * @apioption chart.zoomType
                 */
            },

            /**
             * The chart's main title.
             * 
             * @sample {highmaps} maps/title/title/ Title options demonstrated
             */
            title: {

                /**
                 * The title of the chart. To disable the title, set the `text` to
                 * `null`.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/title/text/ Custom title
                 * @sample {highstock} stock/chart/title-text/ Custom title
                 * @default {highcharts|highmaps} Chart title
                 * @default {highstock} null
                 */
                text: 'Chart title',

                /**
                 * The horizontal alignment of the title. Can be one of "left", "center"
                 * and "right".
                 * 
                 * @validvalue ["left", "center", "right"]
                 * @type {String}
                 * @sample {highcharts} highcharts/title/align/ Aligned to the plot area (x = 70px     = margin left - spacing left)
                 * @sample {highstock} stock/chart/title-align/ Aligned to the plot area (x = 50px     = margin left - spacing left)
                 * @default center
                 * @since 2.0
                 */
                align: 'center',

                /**
                 * The margin between the title and the plot area, or if a subtitle
                 * is present, the margin between the subtitle and the plot area.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/title/margin-50/ A chart title margin of 50
                 * @sample {highcharts} highcharts/title/margin-subtitle/ The same margin applied with a subtitle
                 * @sample {highstock} stock/chart/title-margin/ A chart title margin of 50
                 * @default 15
                 * @since 2.1
                 */
                margin: 15,

                /**
                 * Adjustment made to the title width, normally to reserve space for
                 * the exporting burger menu.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/title/widthadjust/ Wider menu, greater padding
                 * @sample {highstock} highcharts/title/widthadjust/ Wider menu, greater padding
                 * @sample {highmaps} highcharts/title/widthadjust/ Wider menu, greater padding
                 * @default -44
                 * @since 4.2.5
                 */
                widthAdjust: -44

                /**
                 * When the title is floating, the plot area will not move to make space
                 * for it.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/chart/zoomtype-none/ False by default
                 * @sample {highcharts} highcharts/title/floating/
                 *         True - title on top of the plot area
                 * @sample {highstock} stock/chart/title-floating/
                 *         True - title on top of the plot area
                 * @default false
                 * @since 2.1
                 * @apioption title.floating
                 */

                /**
                 * CSS styles for the title. Use this for font styling, but use `align`,
                 * `x` and `y` for text alignment.
                 * 
                 * In styled mode, the title style is given in the `.highcharts-title` class.
                 * 
                 * @type {CSSObject}
                 * @sample {highcharts} highcharts/title/style/ Custom color and weight
                 * @sample {highstock} stock/chart/title-style/ Custom color and weight
                 * @sample highcharts/css/titles/ Styled mode
                 * @default {highcharts|highmaps} { "color": "#333333", "fontSize": "18px" }
                 * @default {highstock} { "color": "#333333", "fontSize": "16px" }
                 * @apioption title.style
                 */

                /**
                 * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
                 * and-string-formatting#html) to render the text.
                 * 
                 * @type {Boolean}
                 * @default false
                 * @apioption title.useHTML
                 */

                /**
                 * The vertical alignment of the title. Can be one of `"top"`, `"middle"`
                 * and `"bottom"`. When a value is given, the title behaves as if [floating](#title.
                 * floating) were `true`.
                 * 
                 * @validvalue ["top", "middle", "bottom"]
                 * @type {String}
                 * @sample {highcharts} highcharts/title/verticalalign/
                 *         Chart title in bottom right corner
                 * @sample {highstock} stock/chart/title-verticalalign/
                 *         Chart title in bottom right corner
                 * @since 2.1
                 * @apioption title.verticalAlign
                 */

                /**
                 * The x position of the title relative to the alignment within chart.
                 * spacingLeft and chart.spacingRight.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/title/align/
                 *         Aligned to the plot area (x = 70px = margin left - spacing left)
                 * @sample {highstock} stock/chart/title-align/
                 *         Aligned to the plot area (x = 50px = margin left - spacing left)
                 * @default 0
                 * @since 2.0
                 * @apioption title.x
                 */

                /**
                 * The y position of the title relative to the alignment within [chart.
                 * spacingTop](#chart.spacingTop) and [chart.spacingBottom](#chart.spacingBottom).
                 *  By default it depends on the font size.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/title/y/
                 *         Title inside the plot area
                 * @sample {highstock} stock/chart/title-verticalalign/
                 *         Chart title in bottom right corner
                 * @since 2.0
                 * @apioption title.y
                 */

            },

            /**
             * The chart's subtitle. This can be used both to display a subtitle below
             * the main title, and to display random text anywhere in the chart. The
             * subtitle can be updated after chart initialization through the 
             * `Chart.setTitle` method.
             * 
             * @sample {highmaps} maps/title/subtitle/ Subtitle options demonstrated
             */
            subtitle: {

                /**
                 * The subtitle of the chart.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/subtitle/text/ Custom subtitle
                 * @sample {highcharts} highcharts/subtitle/text-formatted/ Formatted and linked text.
                 * @sample {highstock} stock/chart/subtitle-text Custom subtitle
                 * @sample {highstock} stock/chart/subtitle-text-formatted Formatted and linked text.
                 */
                text: '',

                /**
                 * The horizontal alignment of the subtitle. Can be one of "left",
                 *  "center" and "right".
                 * 
                 * @validvalue ["left", "center", "right"]
                 * @type {String}
                 * @sample {highcharts} highcharts/subtitle/align/ Footnote at right of plot area
                 * @sample {highstock} stock/chart/subtitle-footnote Footnote at bottom right of plot area
                 * @default center
                 * @since 2.0
                 */
                align: 'center',

                /**
                 * Adjustment made to the subtitle width, normally to reserve space
                 * for the exporting burger menu.
                 * 
                 * @type {Number}
                 * @see [title.widthAdjust](#title.widthAdjust)
                 * @sample {highcharts} highcharts/title/widthadjust/ Wider menu, greater padding
                 * @sample {highstock} highcharts/title/widthadjust/ Wider menu, greater padding
                 * @sample {highmaps} highcharts/title/widthadjust/ Wider menu, greater padding
                 * @default -44
                 * @since 4.2.5
                 */
                widthAdjust: -44

                /**
                 * When the subtitle is floating, the plot area will not move to make
                 * space for it.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/subtitle/floating/
                 *         Floating title and subtitle
                 * @sample {highstock} stock/chart/subtitle-footnote
                 *         Footnote floating at bottom right of plot area
                 * @default false
                 * @since 2.1
                 * @apioption subtitle.floating
                 */

                /**
                 * CSS styles for the title.
                 * 
                 * In styled mode, the subtitle style is given in the `.highcharts-subtitle` class.
                 * 
                 * @type {CSSObject}
                 * @sample {highcharts} highcharts/subtitle/style/
                 *         Custom color and weight
                 * @sample {highcharts} highcharts/css/titles/
                 *         Styled mode
                 * @sample {highstock} stock/chart/subtitle-style
                 *         Custom color and weight
                 * @sample {highstock} highcharts/css/titles/
                 *         Styled mode
                 * @sample {highmaps} highcharts/css/titles/
                 *         Styled mode
                 * @default { "color": "#666666" }
                 * @apioption subtitle.style
                 */

                /**
                 * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
                 * and-string-formatting#html) to render the text.
                 * 
                 * @type {Boolean}
                 * @default false
                 * @apioption subtitle.useHTML
                 */

                /**
                 * The vertical alignment of the title. Can be one of "top", "middle"
                 * and "bottom". When a value is given, the title behaves as floating.
                 * 
                 * @validvalue ["top", "middle", "bottom"]
                 * @type {String}
                 * @sample {highcharts} highcharts/subtitle/verticalalign/
                 *         Footnote at the bottom right of plot area
                 * @sample {highstock} stock/chart/subtitle-footnote
                 *         Footnote at the bottom right of plot area
                 * @default  
                 * @since 2.1
                 * @apioption subtitle.verticalAlign
                 */

                /**
                 * The x position of the subtitle relative to the alignment within chart.
                 * spacingLeft and chart.spacingRight.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/subtitle/align/
                 *         Footnote at right of plot area
                 * @sample {highstock} stock/chart/subtitle-footnote
                 *         Footnote at the bottom right of plot area
                 * @default 0
                 * @since 2.0
                 * @apioption subtitle.x
                 */

                /**
                 * The y position of the subtitle relative to the alignment within chart.
                 * spacingTop and chart.spacingBottom. By default the subtitle is laid
                 * out below the title unless the title is floating.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/subtitle/verticalalign/
                 *         Footnote at the bottom right of plot area
                 * @sample {highstock} stock/chart/subtitle-footnote
                 *         Footnote at the bottom right of plot area
                 * @default {highcharts}  null
                 * @default {highstock}  null
                 * @default {highmaps}  
                 * @since 2.0
                 * @apioption subtitle.y
                 */
            },

            /**
             * The plotOptions is a wrapper object for config objects for each series
             * type. The config objects for each series can also be overridden for
             * each series item as given in the series array.
             * 
             * Configuration options for the series are given in three levels. Options
             * for all series in a chart are given in the [plotOptions.series](#plotOptions.
             * series) object. Then options for all series of a specific type are
             * given in the plotOptions of that type, for example plotOptions.line.
             * Next, options for one single series are given in [the series array](#series).
             *
             */
            plotOptions: {},

            /**
             * HTML labels that can be positioned anywhere in the chart area.
             *
             */
            labels: {

                /**
                 * A HTML label that can be positioned anywhere in the chart area.
                 * 
                 * @type {Array<Object>}
                 * @apioption labels.items
                 */

                /**
                 * Inner HTML or text for the label.
                 * 
                 * @type {String}
                 * @apioption labels.items.html
                 */

                /**
                 * CSS styles for each label. To position the label, use left and top
                 * like this:
                 * 
                 * <pre>style: {
                 *     left: '100px',
                 *     top: '100px'
                 * }</pre>
                 * 
                 * @type {CSSObject}
                 * @apioption labels.items.style
                 */

                /**
                 * Shared CSS styles for all labels.
                 * 
                 * @type {CSSObject}
                 * @default { "color": "#333333" }
                 */
                style: {
                    position: 'absolute',
                    color: '#333333'
                }
            },

            /**
             * The legend is a box containing a symbol and name for each series
             * item or point item in the chart. Each series (or points in case
             * of pie charts) is represented by a symbol and its name in the legend.
             *  
             * It is possible to override the symbol creator function and
             * create [custom legend symbols](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/studies/legend-
             * custom-symbol/).
             * 
             * @productdesc {highmaps}
             * A Highmaps legend by default contains one legend item per series, but if
             * a `colorAxis` is defined, the axis will be displayed in the legend.
             * Either as a gradient, or as multiple legend items for `dataClasses`.
             */
            legend: {

                /**
                 * The background color of the legend.
                 * 
                 * @type {Color}
                 * @see In styled mode, the legend background fill can be applied with
                 * the `.highcharts-legend-box` class.
                 * @sample {highcharts} highcharts/legend/backgroundcolor/ Yellowish background
                 * @sample {highstock} stock/legend/align/ Various legend options
                 * @sample {highmaps} maps/legend/border-background/ Border and background options
                 * @apioption legend.backgroundColor
                 */

                /**
                 * The width of the drawn border around the legend.
                 * 
                 * @type {Number}
                 * @see In styled mode, the legend border stroke width can be applied
                 * with the `.highcharts-legend-box` class.
                 * @sample {highcharts} highcharts/legend/borderwidth/ 2px border width
                 * @sample {highstock} stock/legend/align/ Various legend options
                 * @sample {highmaps} maps/legend/border-background/ Border and background options
                 * @default 0
                 * @apioption legend.borderWidth
                 */

                /**
                 * Enable or disable the legend.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/legend/enabled-false/ Legend disabled
                 * @sample {highstock} stock/legend/align/ Various legend options
                 * @sample {highmaps} maps/legend/enabled-false/ Legend disabled
                 * @default {highstock} false
                 * @default {highmaps} true
                 */
                enabled: true,

                /**
                 * The horizontal alignment of the legend box within the chart area.
                 * Valid values are `left`, `center` and `right`.
                 * 
                 * In the case that the legend is aligned in a corner position, the
                 * `layout` option will determine whether to place it above/below
                 * or on the side of the plot area.
                 * 
                 * @validvalue ["left", "center", "right"]
                 * @type {String}
                 * @sample {highcharts} highcharts/legend/align/
                 *         Legend at the right of the chart
                 * @sample {highstock} stock/legend/align/
                 *         Various legend options
                 * @sample {highmaps} maps/legend/alignment/
                 *         Legend alignment
                 * @since 2.0
                 */
                align: 'center',

                /**
                 * When the legend is floating, the plot area ignores it and is allowed
                 * to be placed below it.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/legend/floating-false/ False by default
                 * @sample {highcharts} highcharts/legend/floating-true/ True
                 * @sample {highmaps} maps/legend/alignment/ Floating legend
                 * @default false
                 * @since 2.1
                 * @apioption legend.floating
                 */

                /**
                 * The layout of the legend items. Can be one of "horizontal" or "vertical".
                 * 
                 * @validvalue ["horizontal", "vertical"]
                 * @type {String}
                 * @sample {highcharts} highcharts/legend/layout-horizontal/ Horizontal by default
                 * @sample {highcharts} highcharts/legend/layout-vertical/ Vertical
                 * @sample {highstock} stock/legend/layout-horizontal/ Horizontal by default
                 * @sample {highmaps} maps/legend/padding-itemmargin/ Vertical with data classes
                 * @sample {highmaps} maps/legend/layout-vertical/ Vertical with color axis gradient
                 * @default horizontal
                 */
                layout: 'horizontal',

                /**
                 * In a legend with horizontal layout, the itemDistance defines the
                 * pixel distance between each item.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/legend/layout-horizontal/ 50px item distance
                 * @sample {highstock} highcharts/legend/layout-horizontal/ 50px item distance
                 * @default {highcharts} 20
                 * @default {highstock} 20
                 * @default {highmaps} 8
                 * @since 3.0.3
                 * @apioption legend.itemDistance
                 */

                /**
                 * The pixel bottom margin for each legend item.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
                 * @sample {highstock} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
                 * @sample {highmaps} maps/legend/padding-itemmargin/ Padding and item margins demonstrated
                 * @default 0
                 * @since 2.2.0
                 * @apioption legend.itemMarginBottom
                 */

                /**
                 * The pixel top margin for each legend item.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
                 * @sample {highstock} highcharts/legend/padding-itemmargin/ Padding and item margins demonstrated
                 * @sample {highmaps} maps/legend/padding-itemmargin/ Padding and item margins demonstrated
                 * @default 0
                 * @since 2.2.0
                 * @apioption legend.itemMarginTop
                 */

                /**
                 * The width for each legend item. This is useful in a horizontal layout
                 * with many items when you want the items to align vertically. .
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/legend/itemwidth-default/ Null by default
                 * @sample {highcharts} highcharts/legend/itemwidth-80/ 80 for aligned legend items
                 * @default null
                 * @since 2.0
                 * @apioption legend.itemWidth
                 */

                /**
                 * A [format string](http://www.highcharts.com/docs/chart-concepts/labels-
                 * and-string-formatting) for each legend label. Available variables
                 * relates to properties on the series, or the point in case of pies.
                 * 
                 * @type {String}
                 * @default {name}
                 * @since 1.3
                 * @apioption legend.labelFormat
                 */

                /**
                 * Callback function to format each of the series' labels. The `this`
                 * keyword refers to the series object, or the point object in case
                 * of pie charts. By default the series or point name is printed.
                 *
                 * @productdesc {highmaps}
                 *              In Highmaps the context can also be a data class in case
                 *              of a `colorAxis`.
                 * 
                 * @type {Function}
                 * @sample {highcharts} highcharts/legend/labelformatter/ Add text
                 * @sample {highmaps} maps/legend/labelformatter/ Data classes with label formatter
                 * @context {Series|Point}
                 */
                labelFormatter: function() {
                    return this.name;
                },

                /**
                 * Line height for the legend items. Deprecated as of 2.1\. Instead,
                 * the line height for each item can be set using itemStyle.lineHeight,
                 * and the padding between items using itemMarginTop and itemMarginBottom.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/legend/lineheight/ Setting padding
                 * @default 16
                 * @since 2.0
                 * @product highcharts
                 * @apioption legend.lineHeight
                 */

                /**
                 * If the plot area sized is calculated automatically and the legend
                 * is not floating, the legend margin is the space between the legend
                 * and the axis labels or plot area.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/legend/margin-default/ 12 pixels by default
                 * @sample {highcharts} highcharts/legend/margin-30/ 30 pixels
                 * @default 12
                 * @since 2.1
                 * @apioption legend.margin
                 */

                /**
                 * Maximum pixel height for the legend. When the maximum height is extended,
                 *  navigation will show.
                 * 
                 * @type {Number}
                 * @default undefined
                 * @since 2.3.0
                 * @apioption legend.maxHeight
                 */

                /**
                 * The color of the drawn border around the legend.
                 * 
                 * @type {Color}
                 * @see In styled mode, the legend border stroke can be applied with
                 * the `.highcharts-legend-box` class.
                 * @sample {highcharts} highcharts/legend/bordercolor/ Brown border
                 * @sample {highstock} stock/legend/align/ Various legend options
                 * @sample {highmaps} maps/legend/border-background/ Border and background options
                 * @default #999999
                 */
                borderColor: '#999999',

                /**
                 * The border corner radius of the legend.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/legend/borderradius-default/ Square by default
                 * @sample {highcharts} highcharts/legend/borderradius-round/ 5px rounded
                 * @sample {highmaps} maps/legend/border-background/ Border and background options
                 * @default 0
                 */
                borderRadius: 0,

                /**
                 * Options for the paging or navigation appearing when the legend
                 * is overflown. Navigation works well on screen, but not in static
                 * exported images. One way of working around that is to [increase
                 * the chart height in export](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/legend/navigation-
                 * enabled-false/).
                 *
                 */
                navigation: {


                    /**
                     * The color for the active up or down arrow in the legend page navigation.
                     * 
                     * @type {Color}
                     * @see In styled mode, the active arrow be styled with the `.highcharts-legend-nav-active` class.
                     * @sample {highcharts} highcharts/legend/navigation/ Legend page navigation demonstrated
                     * @sample {highstock} highcharts/legend/navigation/ Legend page navigation demonstrated
                     * @default #003399
                     * @since 2.2.4
                     */
                    activeColor: '#003399',

                    /**
                     * The color of the inactive up or down arrow in the legend page
                     * navigation. .
                     * 
                     * @type {Color}
                     * @see In styled mode, the inactive arrow be styled with the
                     *      `.highcharts-legend-nav-inactive` class.
                     * @sample {highcharts} highcharts/legend/navigation/
                     *         Legend page navigation demonstrated
                     * @sample {highstock} highcharts/legend/navigation/
                     *         Legend page navigation demonstrated
                     * @default {highcharts} #cccccc
                     * @default {highstock} #cccccc
                     * @default {highmaps} ##cccccc
                     * @since 2.2.4
                     */
                    inactiveColor: '#cccccc'


                    /**
                     * How to animate the pages when navigating up or down. A value of `true`
                     * applies the default navigation given in the chart.animation option.
                     * Additional options can be given as an object containing values for
                     * easing and duration.
                     * 
                     * @type {Boolean|Object}
                     * @sample {highcharts} highcharts/legend/navigation/
                     *         Legend page navigation demonstrated
                     * @sample {highstock} highcharts/legend/navigation/
                     *         Legend page navigation demonstrated
                     * @default true
                     * @since 2.2.4
                     * @apioption legend.navigation.animation
                     */

                    /**
                     * The pixel size of the up and down arrows in the legend paging
                     * navigation.
                     * 
                     * @type {Number}
                     * @sample {highcharts} highcharts/legend/navigation/
                     *         Legend page navigation demonstrated
                     * @sample {highstock} highcharts/legend/navigation/
                     *         Legend page navigation demonstrated
                     * @default 12
                     * @since 2.2.4
                     * @apioption legend.navigation.arrowSize
                     */

                    /**
                     * Whether to enable the legend navigation. In most cases, disabling
                     * the navigation results in an unwanted overflow.
                     * 
                     * See also the [adapt chart to legend](http://www.highcharts.com/plugin-
                     * registry/single/8/Adapt-Chart-To-Legend) plugin for a solution to
                     * extend the chart height to make room for the legend, optionally in
                     * exported charts only.
                     * 
                     * @type {Boolean}
                     * @default true
                     * @since 4.2.4
                     * @apioption legend.navigation.enabled
                     */

                    /**
                     * Text styles for the legend page navigation.
                     * 
                     * @type {CSSObject}
                     * @see In styled mode, the navigation items are styled with the
                     * `.highcharts-legend-navigation` class.
                     * @sample {highcharts} highcharts/legend/navigation/
                     *         Legend page navigation demonstrated
                     * @sample {highstock} highcharts/legend/navigation/
                     *         Legend page navigation demonstrated
                     * @since 2.2.4
                     * @apioption legend.navigation.style
                     */
                },

                /**
                 * The inner padding of the legend box.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/legend/padding-itemmargin/
                 *         Padding and item margins demonstrated
                 * @sample {highstock} highcharts/legend/padding-itemmargin/
                 *         Padding and item margins demonstrated
                 * @sample {highmaps} maps/legend/padding-itemmargin/
                 *         Padding and item margins demonstrated
                 * @default 8
                 * @since 2.2.0
                 * @apioption legend.padding
                 */

                /**
                 * Whether to reverse the order of the legend items compared to the
                 * order of the series or points as defined in the configuration object.
                 * 
                 * @type {Boolean}
                 * @see [yAxis.reversedStacks](#yAxis.reversedStacks),
                 *      [series.legendIndex](#series.legendIndex)
                 * @sample {highcharts} highcharts/legend/reversed/
                 *         Stacked bar with reversed legend
                 * @default false
                 * @since 1.2.5
                 * @apioption legend.reversed
                 */

                /**
                 * Whether to show the symbol on the right side of the text rather than
                 * the left side. This is common in Arabic and Hebraic.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/legend/rtl/ Symbol to the right
                 * @default false
                 * @since 2.2
                 * @apioption legend.rtl
                 */

                /**
                 * CSS styles for the legend area. In the 1.x versions the position
                 * of the legend area was determined by CSS. In 2.x, the position is
                 * determined by properties like `align`, `verticalAlign`, `x` and `y`,
                 *  but the styles are still parsed for backwards compatibility.
                 * 
                 * @type {CSSObject}
                 * @deprecated
                 * @product highcharts highstock
                 * @apioption legend.style
                 */



                /**
                 * CSS styles for each legend item. Only a subset of CSS is supported,
                 * notably those options related to text. The default `textOverflow`
                 * property makes long texts truncate. Set it to `null` to wrap text
                 * instead. A `width` property can be added to control the text width.
                 * 
                 * @type {CSSObject}
                 * @see In styled mode, the legend items can be styled with the `.
                 * highcharts-legend-item` class.
                 * @sample {highcharts} highcharts/legend/itemstyle/ Bold black text
                 * @sample {highmaps} maps/legend/itemstyle/ Item text styles
                 * @default { "color": "#333333", "cursor": "pointer", "fontSize": "12px", "fontWeight": "bold", "textOverflow": "ellipsis" }
                 */
                itemStyle: {
                    color: '#333333',
                    fontSize: '12px',
                    fontWeight: 'bold',
                    textOverflow: 'ellipsis'
                },

                /**
                 * CSS styles for each legend item in hover mode. Only a subset of
                 * CSS is supported, notably those options related to text. Properties
                 * are inherited from `style` unless overridden here.
                 * 
                 * @type {CSSObject}
                 * @see In styled mode, the hovered legend items can be styled with
                 * the `.highcharts-legend-item:hover` pesudo-class.
                 * @sample {highcharts} highcharts/legend/itemhoverstyle/ Red on hover
                 * @sample {highmaps} maps/legend/itemstyle/ Item text styles
                 * @default { "color": "#000000" }
                 */
                itemHoverStyle: {
                    color: '#000000'
                },

                /**
                 * CSS styles for each legend item when the corresponding series or
                 * point is hidden. Only a subset of CSS is supported, notably those
                 * options related to text. Properties are inherited from `style`
                 * unless overridden here.
                 * 
                 * @type {CSSObject}
                 * @see In styled mode, the hidden legend items can be styled with
                 * the `.highcharts-legend-item-hidden` class.
                 * @sample {highcharts} highcharts/legend/itemhiddenstyle/ Darker gray color
                 * @default { "color": "#cccccc" }
                 */
                itemHiddenStyle: {
                    color: '#cccccc'
                },

                /**
                 * Whether to apply a drop shadow to the legend. A `backgroundColor`
                 * also needs to be applied for this to take effect. The shadow can be
                 * an object configuration containing `color`, `offsetX`, `offsetY`,
                 * `opacity` and `width`.
                 * 
                 * @type {Boolean|Object}
                 * @sample {highcharts} highcharts/legend/shadow/
                 *         White background and drop shadow
                 * @sample {highstock} stock/legend/align/
                 *         Various legend options
                 * @sample {highmaps} maps/legend/border-background/
                 *         Border and background options
                 * @default false
                 */
                shadow: false,


                /**
                 * Default styling for the checkbox next to a legend item when
                 * `showCheckbox` is true.
                 */
                itemCheckboxStyle: {
                    position: 'absolute',
                    width: '13px', // for IE precision
                    height: '13px'
                },
                // itemWidth: undefined,

                /**
                 * When this is true, the legend symbol width will be the same as
                 * the symbol height, which in turn defaults to the font size of the
                 * legend items.
                 * 
                 * @type {Boolean}
                 * @default true
                 * @since 5.0.0
                 */
                squareSymbol: true,

                /**
                 * The pixel height of the symbol for series types that use a rectangle
                 * in the legend. Defaults to the font size of legend items.
                 *
                 * @productdesc {highmaps}
                 * In Highmaps, when the symbol is the gradient of a vertical color
                 * axis, the height defaults to 200.
                 * 
                 * @type {Number}
                 * @sample {highmaps} maps/legend/layout-vertical-sized/
                 *         Sized vertical gradient
                 * @sample {highmaps} maps/legend/padding-itemmargin/
                 *         No distance between data classes
                 * @since 3.0.8
                 * @apioption legend.symbolHeight
                 */

                /**
                 * The border radius of the symbol for series types that use a rectangle
                 * in the legend. Defaults to half the `symbolHeight`.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/legend/symbolradius/ Round symbols
                 * @sample {highstock} highcharts/legend/symbolradius/ Round symbols
                 * @sample {highmaps} highcharts/legend/symbolradius/ Round symbols
                 * @since 3.0.8
                 * @apioption legend.symbolRadius
                 */

                /**
                 * The pixel width of the legend item symbol. When the `squareSymbol`
                 * option is set, this defaults to the `symbolHeight`, otherwise 16.
                 * 
                 * @productdesc {highmaps}
                 * In Highmaps, when the symbol is the gradient of a horizontal color
                 * axis, the width defaults to 200.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/legend/symbolwidth/
                 *         Greater symbol width and padding
                 * @sample {highmaps} maps/legend/padding-itemmargin/
                 *         Padding and item margins demonstrated
                 * @sample {highmaps} maps/legend/layout-vertical-sized/
                 *         Sized vertical gradient
                 * @apioption legend.symbolWidth
                 */

                /**
                 * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
                 * and-string-formatting#html) to render the legend item texts. Prior
                 * to 4.1.7, when using HTML, [legend.navigation](#legend.navigation)
                 * was disabled.
                 * 
                 * @type {Boolean}
                 * @default false
                 * @apioption legend.useHTML
                 */

                /**
                 * The width of the legend box.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/legend/width/ Aligned to the plot area
                 * @default null
                 * @since 2.0
                 * @apioption legend.width
                 */

                /**
                 * The pixel padding between the legend item symbol and the legend
                 * item text.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/legend/symbolpadding/ Greater symbol width and padding
                 * @default 5
                 */
                symbolPadding: 5,

                /**
                 * The vertical alignment of the legend box. Can be one of `top`,
                 * `middle` or `bottom`. Vertical position can be further determined
                 * by the `y` option.
                 * 
                 * In the case that the legend is aligned in a corner position, the
                 * `layout` option will determine whether to place it above/below
                 * or on the side of the plot area.
                 * 
                 * @validvalue ["top", "middle", "bottom"]
                 * @type {String}
                 * @sample {highcharts} highcharts/legend/verticalalign/ Legend 100px from the top of the chart
                 * @sample {highstock} stock/legend/align/ Various legend options
                 * @sample {highmaps} maps/legend/alignment/ Legend alignment
                 * @default bottom
                 * @since 2.0
                 */
                verticalAlign: 'bottom',
                // width: undefined,

                /**
                 * The x offset of the legend relative to its horizontal alignment
                 * `align` within chart.spacingLeft and chart.spacingRight. Negative
                 * x moves it to the left, positive x moves it to the right.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/legend/width/ Aligned to the plot area
                 * @default 0
                 * @since 2.0
                 */
                x: 0,

                /**
                 * The vertical offset of the legend relative to it's vertical alignment
                 * `verticalAlign` within chart.spacingTop and chart.spacingBottom.
                 *  Negative y moves it up, positive y moves it down.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/legend/verticalalign/ Legend 100px from the top of the chart
                 * @sample {highstock} stock/legend/align/ Various legend options
                 * @sample {highmaps} maps/legend/alignment/ Legend alignment
                 * @default 0
                 * @since 2.0
                 */
                y: 0,

                /**
                 * A title to be added on top of the legend.
                 * 
                 * @sample {highcharts} highcharts/legend/title/ Legend title
                 * @sample {highmaps} maps/legend/alignment/ Legend with title
                 * @since 3.0
                 */
                title: {
                    /**
                     * A text or HTML string for the title.
                     * 
                     * @type {String}
                     * @default null
                     * @since 3.0
                     * @apioption legend.title.text
                     */



                    /**
                     * Generic CSS styles for the legend title.
                     * 
                     * @type {CSSObject}
                     * @see In styled mode, the legend title is styled with the
                     * `.highcharts-legend-title` class.
                     * @default {"fontWeight":"bold"}
                     * @since 3.0
                     */
                    style: {
                        fontWeight: 'bold'
                    }

                }
            },


            /**
             * The loading options control the appearance of the loading screen
             * that covers the plot area on chart operations. This screen only
             * appears after an explicit call to `chart.showLoading()`. It is a
             * utility for developers to communicate to the end user that something
             * is going on, for example while retrieving new data via an XHR connection.
             * The "Loading..." text itself is not part of this configuration
             * object, but part of the `lang` object.
             *
             */
            loading: {

                /**
                 * The duration in milliseconds of the fade out effect.
                 * 
                 * @type {Number}
                 * @sample highcharts/loading/hideduration/ Fade in and out over a second
                 * @default 100
                 * @since 1.2.0
                 * @apioption loading.hideDuration
                 */

                /**
                 * The duration in milliseconds of the fade in effect.
                 * 
                 * @type {Number}
                 * @sample highcharts/loading/hideduration/ Fade in and out over a second
                 * @default 100
                 * @since 1.2.0
                 * @apioption loading.showDuration
                 */


                /**
                 * CSS styles for the loading label `span`.
                 * 
                 * @type {CSSObject}
                 * @see In styled mode, the loading label is styled with the
                 * `.highcharts-legend-loading-inner` class.
                 * @sample {highcharts|highmaps} highcharts/loading/labelstyle/ Vertically centered
                 * @sample {highstock} stock/loading/general/ Label styles
                 * @default { "fontWeight": "bold", "position": "relative", "top": "45%" }
                 * @since 1.2.0
                 */
                labelStyle: {
                    fontWeight: 'bold',
                    position: 'relative',
                    top: '45%'
                },

                /**
                 * CSS styles for the loading screen that covers the plot area.
                 * 
                 * @type {CSSObject}
                 * @see In styled mode, the loading label is styled with the `.highcharts-legend-loading` class.
                 * @sample {highcharts|highmaps} highcharts/loading/style/ Gray plot area, white text
                 * @sample {highstock} stock/loading/general/ Gray plot area, white text
                 * @default { "position": "absolute", "backgroundColor": "#ffffff", "opacity": 0.5, "textAlign": "center" }
                 * @since 1.2.0
                 */
                style: {
                    position: 'absolute',
                    backgroundColor: '#ffffff',
                    opacity: 0.5,
                    textAlign: 'center'
                }

            },


            /**
             * Options for the tooltip that appears when the user hovers over a
             * series or point.
             *
             */
            tooltip: {

                /**
                 * Enable or disable the tooltip.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/tooltip/enabled/ Disabled
                 * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Disable tooltip and show values on chart instead
                 * @default true
                 */
                enabled: true,

                /**
                 * Enable or disable animation of the tooltip. In slow legacy IE browsers
                 * the animation is disabled by default.
                 * 
                 * @type {Boolean}
                 * @default true
                 * @since 2.3.0
                 */
                animation: svg,

                /**
                 * The radius of the rounded border corners.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/tooltip/bordercolor-default/ 5px by default
                 * @sample {highcharts} highcharts/tooltip/borderradius-0/ Square borders
                 * @sample {highmaps} maps/tooltip/background-border/ Background and border demo
                 * @default 3
                 */
                borderRadius: 3,

                /**
                 * For series on a datetime axes, the date format in the tooltip's
                 * header will by default be guessed based on the closest data points.
                 * This member gives the default string representations used for
                 * each unit. For an overview of the replacement codes, see [dateFormat](#Highcharts.
                 * dateFormat).
                 * 
                 * Defaults to:
                 * 
                 * <pre>{
                 *     millisecond:"%A, %b %e, %H:%M:%S.%L",
                 *     second:"%A, %b %e, %H:%M:%S",
                 *     minute:"%A, %b %e, %H:%M",
                 *     hour:"%A, %b %e, %H:%M",
                 *     day:"%A, %b %e, %Y",
                 *     week:"Week from %A, %b %e, %Y",
                 *     month:"%B %Y",
                 *     year:"%Y"
                 * }</pre>
                 * 
                 * @type {Object}
                 * @see [xAxis.dateTimeLabelFormats](#xAxis.dateTimeLabelFormats)
                 * @product highcharts highstock
                 */
                dateTimeLabelFormats: {
                    millisecond: '%A, %b %e, %H:%M:%S.%L',
                    second: '%A, %b %e, %H:%M:%S',
                    minute: '%A, %b %e, %H:%M',
                    hour: '%A, %b %e, %H:%M',
                    day: '%A, %b %e, %Y',
                    week: 'Week from %A, %b %e, %Y',
                    month: '%B %Y',
                    year: '%Y'
                },

                /**
                 * A string to append to the tooltip format.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/tooltip/footerformat/ A table for value alignment
                 * @sample {highmaps} maps/tooltip/format/ Format demo
                 * @default false
                 * @since 2.2
                 */
                footerFormat: '',

                /**
                 * Padding inside the tooltip, in pixels.
                 * 
                 * @type {Number}
                 * @default 8
                 * @since 5.0.0
                 */
                padding: 8,

                /**
                 * Proximity snap for graphs or single points. It defaults to 10 for
                 * mouse-powered devices and 25 for touch devices.
                 * 
                 * Note that in most cases the whole plot area captures the mouse
                 * movement, and in these cases `tooltip.snap` doesn't make sense.
                 * This applies when [stickyTracking](#plotOptions.series.stickyTracking)
                 * is `true` (default) and when the tooltip is [shared](#tooltip.shared)
                 * or [split](#tooltip.split).
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/tooltip/bordercolor-default/ 10 px by default
                 * @sample {highcharts} highcharts/tooltip/snap-50/ 50 px on graph
                 * @default 10/25
                 * @since 1.2.0
                 * @product highcharts highstock
                 */
                snap: isTouchDevice ? 25 : 10,


                /**
                 * The background color or gradient for the tooltip.
                 * 
                 * In styled mode, the stroke width is set in the `.highcharts-tooltip-box` class.
                 * 
                 * @type {Color}
                 * @sample {highcharts} highcharts/tooltip/backgroundcolor-solid/ Yellowish background
                 * @sample {highcharts} highcharts/tooltip/backgroundcolor-gradient/ Gradient
                 * @sample {highcharts} highcharts/css/tooltip-border-background/ Tooltip in styled mode
                 * @sample {highstock} stock/tooltip/general/ Custom tooltip
                 * @sample {highstock} highcharts/css/tooltip-border-background/ Tooltip in styled mode
                 * @sample {highmaps} maps/tooltip/background-border/ Background and border demo
                 * @sample {highmaps} highcharts/css/tooltip-border-background/ Tooltip in styled mode
                 * @default rgba(247,247,247,0.85)
                 */
                backgroundColor: color('#f7f7f7').setOpacity(0.85).get(),

                /**
                 * The pixel width of the tooltip border.
                 * 
                 * In styled mode, the stroke width is set in the `.highcharts-tooltip-box` class.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/tooltip/bordercolor-default/ 2px by default
                 * @sample {highcharts} highcharts/tooltip/borderwidth/ No border (shadow only)
                 * @sample {highcharts} highcharts/css/tooltip-border-background/ Tooltip in styled mode
                 * @sample {highstock} stock/tooltip/general/ Custom tooltip
                 * @sample {highstock} highcharts/css/tooltip-border-background/ Tooltip in styled mode
                 * @sample {highmaps} maps/tooltip/background-border/ Background and border demo
                 * @sample {highmaps} highcharts/css/tooltip-border-background/ Tooltip in styled mode
                 * @default 1
                 */
                borderWidth: 1,

                /**
                 * The HTML of the tooltip header line. Variables are enclosed by
                 * curly brackets. Available variables are `point.key`, `series.name`,
                 * `series.color` and other members from the `point` and `series`
                 * objects. The `point.key` variable contains the category name, x
                 * value or datetime string depending on the type of axis. For datetime
                 * axes, the `point.key` date format can be set using tooltip.xDateFormat.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/tooltip/footerformat/
                 *         A HTML table in the tooltip
                 * @sample {highstock} highcharts/tooltip/footerformat/
                 *         A HTML table in the tooltip
                 * @sample {highmaps} maps/tooltip/format/ Format demo
                 */
                headerFormat: '<span style="font-size: 10px">{point.key}</span><br/>',

                /**
                 * The HTML of the point's line in the tooltip. Variables are enclosed
                 * by curly brackets. Available variables are point.x, point.y, series.
                 * name and series.color and other properties on the same form. Furthermore,
                 * point.y can be extended by the `tooltip.valuePrefix` and `tooltip.
                 * valueSuffix` variables. This can also be overridden for each series,
                 * which makes it a good hook for displaying units.
                 * 
                 * In styled mode, the dot is colored by a class name rather
                 * than the point color.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/tooltip/pointformat/ A different point format with value suffix
                 * @sample {highmaps} maps/tooltip/format/ Format demo
                 * @default <span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.y}</b><br/>
                 * @since 2.2
                 */
                pointFormat: '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.y}</b><br/>',

                /**
                 * Whether to apply a drop shadow to the tooltip.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/tooltip/bordercolor-default/ True by default
                 * @sample {highcharts} highcharts/tooltip/shadow/ False
                 * @sample {highmaps} maps/tooltip/positioner/ Fixed tooltip position, border and shadow disabled
                 * @default true
                 */
                shadow: true,

                /**
                 * CSS styles for the tooltip. The tooltip can also be styled through
                 * the CSS class `.highcharts-tooltip`.
                 * 
                 * @type {CSSObject}
                 * @sample {highcharts} highcharts/tooltip/style/ Greater padding, bold text
                 * @default { "color": "#333333", "cursor": "default", "fontSize": "12px", "pointerEvents": "none", "whiteSpace": "nowrap" }
                 */
                style: {
                    color: '#333333',
                    cursor: 'default',
                    fontSize: '12px',
                    pointerEvents: 'none', // #1686 http://caniuse.com/#feat=pointer-events
                    whiteSpace: 'nowrap'
                }



                /**
                 * The color of the tooltip border. When `null`, the border takes the
                 * color of the corresponding series or point.
                 * 
                 * @type {Color}
                 * @sample {highcharts} highcharts/tooltip/bordercolor-default/
                 *         Follow series by default
                 * @sample {highcharts} highcharts/tooltip/bordercolor-black/
                 *         Black border
                 * @sample {highstock} stock/tooltip/general/
                 *         Styled tooltip
                 * @sample {highmaps} maps/tooltip/background-border/
                 *         Background and border demo
                 * @default null
                 * @apioption tooltip.borderColor
                 */

                /**
                 * Since 4.1, the crosshair definitions are moved to the Axis object
                 * in order for a better separation from the tooltip. See [xAxis.crosshair](#xAxis.
                 * crosshair)<a>.</a>
                 * 
                 * @type {Mixed}
                 * @deprecated
                 * @sample {highcharts} highcharts/tooltip/crosshairs-x/
                 *         Enable a crosshair for the x value
                 * @default true
                 * @apioption tooltip.crosshairs
                 */

                /**
                 * Whether the tooltip should follow the mouse as it moves across columns,
                 * pie slices and other point types with an extent. By default it behaves
                 * this way for scatter, bubble and pie series by override in the `plotOptions`
                 * for those series types.
                 * 
                 * For touch moves to behave the same way, [followTouchMove](#tooltip.
                 * followTouchMove) must be `true` also.
                 * 
                 * @type {Boolean}
                 * @default {highcharts} false
                 * @default {highstock} false
                 * @default {highmaps} true
                 * @since 3.0
                 * @apioption tooltip.followPointer
                 */

                /**
                 * Whether the tooltip should follow the finger as it moves on a touch
                 * device. If this is `true` and [chart.panning](#chart.panning) is
                 * set,`followTouchMove` will take over one-finger touches, so the user
                 * needs to use two fingers for zooming and panning.
                 * 
                 * @type {Boolean}
                 * @default {highcharts} true
                 * @default {highstock} true
                 * @default {highmaps} false
                 * @since 3.0.1
                 * @apioption tooltip.followTouchMove
                 */

                /**
                 * Callback function to format the text of the tooltip from scratch. Return
                 * `false` to disable tooltip for a specific point on series.
                 * 
                 * A subset of HTML is supported. Unless `useHTML` is true, the HTML of the
                 * tooltip is parsed and converted to SVG, therefore this isn't a complete HTML
                 * renderer. The following tags are supported: `<b>`, `<strong>`, `<i>`, `<em>`,
                 * `<br/>`, `<span>`. Spans can be styled with a `style` attribute,
                 * but only text-related CSS that is shared with SVG is handled.
                 * 
                 * Since version 2.1 the tooltip can be shared between multiple series
                 * through the `shared` option. The available data in the formatter
                 * differ a bit depending on whether the tooltip is shared or not. In
                 * a shared tooltip, all properties except `x`, which is common for
                 * all points, are kept in an array, `this.points`.
                 * 
                 * Available data are:
                 * 
                 * <dl>
                 * 
                 * <dt>this.percentage (not shared) / this.points[i].percentage (shared)</dt>
                 * 
                 * <dd>Stacked series and pies only. The point's percentage of the total.
                 * </dd>
                 * 
                 * <dt>this.point (not shared) / this.points[i].point (shared)</dt>
                 * 
                 * <dd>The point object. The point name, if defined, is available through
                 * `this.point.name`.</dd>
                 * 
                 * <dt>this.points</dt>
                 * 
                 * <dd>In a shared tooltip, this is an array containing all other properties
                 * for each point.</dd>
                 * 
                 * <dt>this.series (not shared) / this.points[i].series (shared)</dt>
                 * 
                 * <dd>The series object. The series name is available through
                 * `this.series.name`.</dd>
                 * 
                 * <dt>this.total (not shared) / this.points[i].total (shared)</dt>
                 * 
                 * <dd>Stacked series only. The total value at this point's x value.
                 * </dd>
                 * 
                 * <dt>this.x</dt>
                 * 
                 * <dd>The x value. This property is the same regardless of the tooltip
                 * being shared or not.</dd>
                 * 
                 * <dt>this.y (not shared) / this.points[i].y (shared)</dt>
                 * 
                 * <dd>The y value.</dd>
                 * 
                 * </dl>
                 * 
                 * @type {Function}
                 * @sample {highcharts} highcharts/tooltip/formatter-simple/
                 *         Simple string formatting
                 * @sample {highcharts} highcharts/tooltip/formatter-shared/
                 *         Formatting with shared tooltip
                 * @sample {highstock} stock/tooltip/formatter/
                 *         Formatting with shared tooltip
                 * @sample {highmaps} maps/tooltip/formatter/
                 *         String formatting
                 * @apioption tooltip.formatter
                 */

                /**
                 * The number of milliseconds to wait until the tooltip is hidden when
                 * mouse out from a point or chart.
                 * 
                 * @type {Number}
                 * @default 500
                 * @since 3.0
                 * @apioption tooltip.hideDelay
                 */

                /**
                 * A callback function for formatting the HTML output for a single point
                 * in the tooltip. Like the `pointFormat` string, but with more flexibility.
                 * 
                 * @type {Function}
                 * @context Point
                 * @since 4.1.0
                 * @apioption tooltip.pointFormatter
                 */

                /**
                 * A callback function to place the tooltip in a default position. The
                 * callback receives three parameters: `labelWidth`, `labelHeight` and
                 * `point`, where point contains values for `plotX` and `plotY` telling
                 * where the reference point is in the plot area. Add `chart.plotLeft`
                 * and `chart.plotTop` to get the full coordinates.
                 * 
                 * The return should be an object containing x and y values, for example
                 * `{ x: 100, y: 100 }`.
                 * 
                 * @type {Function}
                 * @sample {highcharts} highcharts/tooltip/positioner/ A fixed tooltip position
                 * @sample {highstock} stock/tooltip/positioner/ A fixed tooltip position on top of the chart
                 * @sample {highmaps} maps/tooltip/positioner/ A fixed tooltip position
                 * @since 2.2.4
                 * @apioption tooltip.positioner
                 */

                /**
                 * The name of a symbol to use for the border around the tooltip.
                 * 
                 * @type {String}
                 * @default callout
                 * @validvalue ["callout", "square"]
                 * @since 4.0
                 * @apioption tooltip.shape
                 */

                /**
                 * When the tooltip is shared, the entire plot area will capture mouse
                 * movement or touch events. Tooltip texts for series types with ordered
                 * data (not pie, scatter, flags etc) will be shown in a single bubble.
                 * This is recommended for single series charts and for tablet/mobile
                 * optimized charts.
                 * 
                 * See also [tooltip.split](#tooltip.split), that is better suited for
                 * charts with many series, especially line-type series.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/tooltip/shared-false/ False by default
                 * @sample {highcharts} highcharts/tooltip/shared-true/ True
                 * @sample {highcharts} highcharts/tooltip/shared-x-crosshair/ True with x axis crosshair
                 * @sample {highcharts} highcharts/tooltip/shared-true-mixed-types/ True with mixed series types
                 * @default false
                 * @since 2.1
                 * @product highcharts highstock
                 * @apioption tooltip.shared
                 */

                /**
                 * Split the tooltip into one label per series, with the header close
                 * to the axis. This is recommended over [shared](#tooltip.shared) tooltips
                 * for charts with multiple line series, generally making them easier
                 * to read.
                 *
                 * @productdesc {highstock} In Highstock, tooltips are split by default
                 * since v6.0.0. Stock charts typically contain multi-dimension points
                 * and multiple panes, making split tooltips the preferred layout over
                 * the previous `shared` tooltip.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/tooltip/split/ Split tooltip
                 * @sample {highstock} highcharts/tooltip/split/ Split tooltip
                 * @sample {highmaps} highcharts/tooltip/split/ Split tooltip
                 * @default {highcharts} false
                 * @default {highstock} true
                 * @product highcharts highstock
                 * @since 5.0.0
                 * @apioption tooltip.split
                 */

                /**
                 * Use HTML to render the contents of the tooltip instead of SVG. Using
                 * HTML allows advanced formatting like tables and images in the tooltip.
                 * It is also recommended for rtl languages as it works around rtl
                 * bugs in early Firefox.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/tooltip/footerformat/ A table for value alignment
                 * @sample {highcharts} highcharts/tooltip/fullhtml/ Full HTML tooltip
                 * @sample {highstock} highcharts/tooltip/footerformat/ A table for value alignment
                 * @sample {highstock} highcharts/tooltip/fullhtml/ Full HTML tooltip
                 * @sample {highmaps} maps/tooltip/usehtml/ Pure HTML tooltip
                 * @default false
                 * @since 2.2
                 * @apioption tooltip.useHTML
                 */

                /**
                 * How many decimals to show in each series' y value. This is overridable
                 * in each series' tooltip options object. The default is to preserve
                 * all decimals.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
                 * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
                 * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
                 * @since 2.2
                 * @apioption tooltip.valueDecimals
                 */

                /**
                 * A string to prepend to each series' y value. Overridable in each
                 * series' tooltip options object.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
                 * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
                 * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
                 * @since 2.2
                 * @apioption tooltip.valuePrefix
                 */

                /**
                 * A string to append to each series' y value. Overridable in each series'
                 * tooltip options object.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
                 * @sample {highstock} highcharts/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
                 * @sample {highmaps} maps/tooltip/valuedecimals/ Set decimals, prefix and suffix for the value
                 * @since 2.2
                 * @apioption tooltip.valueSuffix
                 */

                /**
                 * The format for the date in the tooltip header if the X axis is a
                 * datetime axis. The default is a best guess based on the smallest
                 * distance between points in the chart.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/tooltip/xdateformat/ A different format
                 * @product highcharts highstock
                 * @apioption tooltip.xDateFormat
                 */
            },


            /**
             * Highchart by default puts a credits label in the lower right corner
             * of the chart. This can be changed using these options.
             */
            credits: {

                /**
                 * Whether to show the credits text.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/credits/enabled-false/ Credits disabled
                 * @sample {highstock} stock/credits/enabled/ Credits disabled
                 * @sample {highmaps} maps/credits/enabled-false/ Credits disabled
                 * @default true
                 */
                enabled: true,

                /**
                 * The URL for the credits label.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/credits/href/ Custom URL and text
                 * @sample {highmaps} maps/credits/customized/ Custom URL and text
                 * @default {highcharts} http://www.highcharts.com
                 * @default {highstock} "http://www.highcharts.com"
                 * @default {highmaps} http://www.highcharts.com
                 */
                href: 'http://www.highcharts.com',

                /**
                 * Position configuration for the credits label.
                 * 
                 * @type {Object}
                 * @sample {highcharts} highcharts/credits/position-left/ Left aligned
                 * @sample {highcharts} highcharts/credits/position-left/ Left aligned
                 * @sample {highmaps} maps/credits/customized/ Left aligned
                 * @sample {highmaps} maps/credits/customized/ Left aligned
                 * @since 2.1
                 */
                position: {

                    /**
                     * Horizontal alignment of the credits.
                     * 
                     * @validvalue ["left", "center", "right"]
                     * @type {String}
                     * @default right
                     */
                    align: 'right',

                    /**
                     * Horizontal pixel offset of the credits.
                     * 
                     * @type {Number}
                     * @default -10
                     */
                    x: -10,

                    /**
                     * Vertical alignment of the credits.
                     * 
                     * @validvalue ["top", "middle", "bottom"]
                     * @type {String}
                     * @default bottom
                     */
                    verticalAlign: 'bottom',

                    /**
                     * Vertical pixel offset of the credits.
                     * 
                     * @type {Number}
                     * @default -5
                     */
                    y: -5
                },


                /**
                 * CSS styles for the credits label.
                 * 
                 * @type {CSSObject}
                 * @see In styled mode, credits styles can be set with the
                 * `.highcharts-credits` class.
                 * @default { "cursor": "pointer", "color": "#999999", "fontSize": "10px" }
                 */
                style: {
                    cursor: 'pointer',
                    color: '#999999',
                    fontSize: '9px'
                },


                /**
                 * The text for the credits label.
                 *
                 * @productdesc {highmaps}
                 * If a map is loaded as GeoJSON, the text defaults to `Highcharts @
                 * {map-credits}`. Otherwise, it defaults to `Highcharts.com`.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/credits/href/ Custom URL and text
                 * @sample {highmaps} maps/credits/customized/ Custom URL and text
                 * @default {highcharts|highstock} Highcharts.com
                 */
                text: 'Highcharts.com'
            }
        };



        /**
         * Sets the getTimezoneOffset function. If the timezone option is set, a default
         * getTimezoneOffset function with that timezone is returned. If not, the
         * specified getTimezoneOffset function is returned. If neither are specified,
         * undefined is returned.
         * @return {function} a getTimezoneOffset function or undefined
         */
        function getTimezoneOffsetOption() {
            var globalOptions = H.defaultOptions.global,
                moment = win.moment;

            if (globalOptions.timezone) {
                if (!moment) {
                    // getTimezoneOffset-function stays undefined because it depends on
                    // Moment.js
                    H.error(25);

                } else {
                    return function(timestamp) {
                        return -moment.tz(
                            timestamp,
                            globalOptions.timezone
                        ).utcOffset();
                    };
                }
            }

            // If not timezone is set, look for the getTimezoneOffset callback
            return globalOptions.useUTC && globalOptions.getTimezoneOffset;
        }

        /**
         * Set the time methods globally based on the useUTC option. Time method can be
         *   either local time or UTC (default). It is called internally on initiating
         *   Highcharts and after running `Highcharts.setOptions`.
         *
         * @private
         */
        function setTimeMethods() {
            var globalOptions = H.defaultOptions.global,
                Date,
                useUTC = globalOptions.useUTC,
                GET = useUTC ? 'getUTC' : 'get',
                SET = useUTC ? 'setUTC' : 'set',
                setters = ['Minutes', 'Hours', 'Day', 'Date', 'Month', 'FullYear'],
                getters = setters.concat(['Milliseconds', 'Seconds']),
                n;

            H.Date = Date = globalOptions.Date || win.Date; // Allow using a different Date class
            Date.hcTimezoneOffset = useUTC && globalOptions.timezoneOffset;
            Date.hcGetTimezoneOffset = getTimezoneOffsetOption();
            Date.hcMakeTime = function(year, month, date, hours, minutes, seconds) {
                var d;
                if (useUTC) {
                    d = Date.UTC.apply(0, arguments);
                    d += getTZOffset(d);
                } else {
                    d = new Date(
                        year,
                        month,
                        pick(date, 1),
                        pick(hours, 0),
                        pick(minutes, 0),
                        pick(seconds, 0)
                    ).getTime();
                }
                return d;
            };

            // Dynamically set setters and getters. Use for loop, H.each is not yet 
            // overridden in oldIE.
            for (n = 0; n < setters.length; n++) {
                Date['hcGet' + setters[n]] = GET + setters[n];
            }
            for (n = 0; n < getters.length; n++) {
                Date['hcSet' + getters[n]] = SET + getters[n];
            }
        }

        /**
         * Merge the default options with custom options and return the new options
         * structure. Commonly used for defining reusable templates.
         *
         * @function #setOptions
         * @memberOf  Highcharts
         * @sample highcharts/global/useutc-false Setting a global option
         * @sample highcharts/members/setoptions Applying a global theme
         * @param {Object} options The new custom chart options.
         * @returns {Object} Updated options.
         */
        H.setOptions = function(options) {

            // Copy in the default options
            H.defaultOptions = merge(true, H.defaultOptions, options);

            // Apply UTC
            setTimeMethods();

            return H.defaultOptions;
        };

        /**
         * Get the updated default options. Until 3.0.7, merely exposing defaultOptions for outside modules
         * wasn't enough because the setOptions method created a new object.
         */
        H.getOptions = function() {
            return H.defaultOptions;
        };


        // Series defaults
        H.defaultPlotOptions = H.defaultOptions.plotOptions;

        // set the default time methods
        setTimeMethods();

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var correctFloat = H.correctFloat,
            defined = H.defined,
            destroyObjectProperties = H.destroyObjectProperties,
            isNumber = H.isNumber,
            merge = H.merge,
            pick = H.pick,
            deg2rad = H.deg2rad;

        /**
         * The Tick class
         */
        H.Tick = function(axis, pos, type, noLabel) {
            this.axis = axis;
            this.pos = pos;
            this.type = type || '';
            this.isNew = true;
            this.isNewLabel = true;

            if (!type && !noLabel) {
                this.addLabel();
            }
        };

        H.Tick.prototype = {
            /**
             * Write the tick label
             */
            addLabel: function() {
                var tick = this,
                    axis = tick.axis,
                    options = axis.options,
                    chart = axis.chart,
                    categories = axis.categories,
                    names = axis.names,
                    pos = tick.pos,
                    labelOptions = options.labels,
                    str,
                    tickPositions = axis.tickPositions,
                    isFirst = pos === tickPositions[0],
                    isLast = pos === tickPositions[tickPositions.length - 1],
                    value = categories ?
                    pick(categories[pos], names[pos], pos) :
                    pos,
                    label = tick.label,
                    tickPositionInfo = tickPositions.info,
                    dateTimeLabelFormat;

                // Set the datetime label format. If a higher rank is set for this
                // position, use that. If not, use the general format.
                if (axis.isDatetimeAxis && tickPositionInfo) {
                    dateTimeLabelFormat =
                        options.dateTimeLabelFormats[
                            tickPositionInfo.higherRanks[pos] ||
                            tickPositionInfo.unitName
                        ];
                }
                // set properties for access in render method
                tick.isFirst = isFirst;
                tick.isLast = isLast;

                // get the string
                str = axis.labelFormatter.call({
                    axis: axis,
                    chart: chart,
                    isFirst: isFirst,
                    isLast: isLast,
                    dateTimeLabelFormat: dateTimeLabelFormat,
                    value: axis.isLog ? correctFloat(axis.lin2log(value)) : value,
                    pos: pos
                });

                // first call
                if (!defined(label)) {

                    tick.label = label =
                        defined(str) && labelOptions.enabled ?
                        chart.renderer.text(
                            str,
                            0,
                            0,
                            labelOptions.useHTML
                        )

                        // without position absolute, IE export sometimes is
                        // wrong.
                        .css(merge(labelOptions.style))

                        .add(axis.labelGroup) :
                        null;

                    // Un-rotated length
                    tick.labelLength = label && label.getBBox().width;
                    // Base value to detect change for new calls to getBBox
                    tick.rotation = 0;

                    // update
                } else if (label) {
                    label.attr({
                        text: str
                    });
                }
            },

            /**
             * Get the offset height or width of the label
             */
            getLabelSize: function() {
                return this.label ?
                    this.label.getBBox()[this.axis.horiz ? 'height' : 'width'] :
                    0;
            },

            /**
             * Handle the label overflow by adjusting the labels to the left and right
             * edge, or hide them if they collide into the neighbour label.
             */
            handleOverflow: function(xy) {
                var axis = this.axis,
                    pxPos = xy.x,
                    chartWidth = axis.chart.chartWidth,
                    spacing = axis.chart.spacing,
                    leftBound = pick(axis.labelLeft, Math.min(axis.pos, spacing[3])),
                    rightBound = pick(
                        axis.labelRight,
                        Math.max(axis.pos + axis.len, chartWidth - spacing[1])
                    ),
                    label = this.label,
                    rotation = this.rotation,
                    factor = {
                        left: 0,
                        center: 0.5,
                        right: 1
                    }[axis.labelAlign],
                    labelWidth = label.getBBox().width,
                    slotWidth = axis.getSlotWidth(),
                    modifiedSlotWidth = slotWidth,
                    xCorrection = factor,
                    goRight = 1,
                    leftPos,
                    rightPos,
                    textWidth,
                    css = {};

                // Check if the label overshoots the chart spacing box. If it does, move
                // it. If it now overshoots the slotWidth, add ellipsis.
                if (!rotation) {
                    leftPos = pxPos - factor * labelWidth;
                    rightPos = pxPos + (1 - factor) * labelWidth;

                    if (leftPos < leftBound) {
                        modifiedSlotWidth = xy.x + modifiedSlotWidth * (1 - factor) - leftBound;
                    } else if (rightPos > rightBound) {
                        modifiedSlotWidth =
                            rightBound - xy.x + modifiedSlotWidth * factor;
                        goRight = -1;
                    }

                    modifiedSlotWidth = Math.min(slotWidth, modifiedSlotWidth); // #4177
                    if (modifiedSlotWidth < slotWidth && axis.labelAlign === 'center') {
                        xy.x += (
                            goRight *
                            (
                                slotWidth -
                                modifiedSlotWidth -
                                xCorrection * (
                                    slotWidth - Math.min(labelWidth, modifiedSlotWidth)
                                )
                            )
                        );
                    }
                    // If the label width exceeds the available space, set a text width
                    // to be picked up below. Also, if a width has been set before, we
                    // need to set a new one because the reported labelWidth will be
                    // limited by the box (#3938).
                    if (
                        labelWidth > modifiedSlotWidth ||
                        (axis.autoRotation && (label.styles || {}).width)
                    ) {
                        textWidth = modifiedSlotWidth;
                    }

                    // Add ellipsis to prevent rotated labels to be clipped against the edge
                    // of the chart
                } else if (rotation < 0 && pxPos - factor * labelWidth < leftBound) {
                    textWidth = Math.round(
                        pxPos / Math.cos(rotation * deg2rad) - leftBound
                    );
                } else if (rotation > 0 && pxPos + factor * labelWidth > rightBound) {
                    textWidth = Math.round(
                        (chartWidth - pxPos) / Math.cos(rotation * deg2rad)
                    );
                }

                if (textWidth) {
                    css.width = textWidth;
                    if (!(axis.options.labels.style || {}).textOverflow) {
                        css.textOverflow = 'ellipsis';
                    }
                    label.css(css);
                }
            },

            /**
             * Get the x and y position for ticks and labels
             */
            getPosition: function(horiz, pos, tickmarkOffset, old) {
                var axis = this.axis,
                    chart = axis.chart,
                    cHeight = (old && chart.oldChartHeight) || chart.chartHeight;

                return {
                    x: horiz ?
                        (
                            axis.translate(pos + tickmarkOffset, null, null, old) +
                            axis.transB
                        ) :
                        (
                            axis.left +
                            axis.offset +
                            (
                                axis.opposite ?
                                (
                                    (
                                        (old && chart.oldChartWidth) ||
                                        chart.chartWidth
                                    ) -
                                    axis.right -
                                    axis.left
                                ) :
                                0
                            )
                        ),

                    y: horiz ?
                        (
                            cHeight -
                            axis.bottom +
                            axis.offset -
                            (axis.opposite ? axis.height : 0)
                        ) :
                        (
                            cHeight -
                            axis.translate(pos + tickmarkOffset, null, null, old) -
                            axis.transB
                        )
                };

            },

            /**
             * Get the x, y position of the tick label
             */
            getLabelPosition: function(
                x,
                y,
                label,
                horiz,
                labelOptions,
                tickmarkOffset,
                index,
                step
            ) {
                var axis = this.axis,
                    transA = axis.transA,
                    reversed = axis.reversed,
                    staggerLines = axis.staggerLines,
                    rotCorr = axis.tickRotCorr || {
                        x: 0,
                        y: 0
                    },
                    yOffset = labelOptions.y,
                    line;

                if (!defined(yOffset)) {
                    if (axis.side === 0) {
                        yOffset = label.rotation ? -8 : -label.getBBox().height;
                    } else if (axis.side === 2) {
                        yOffset = rotCorr.y + 8;
                    } else {
                        // #3140, #3140
                        yOffset = Math.cos(label.rotation * deg2rad) *
                            (rotCorr.y - label.getBBox(false, 0).height / 2);
                    }
                }

                x = x + labelOptions.x + rotCorr.x - (tickmarkOffset && horiz ?
                    tickmarkOffset * transA * (reversed ? -1 : 1) : 0);
                y = y + yOffset - (tickmarkOffset && !horiz ?
                    tickmarkOffset * transA * (reversed ? 1 : -1) : 0);

                // Correct for staggered labels
                if (staggerLines) {
                    line = (index / (step || 1) % staggerLines);
                    if (axis.opposite) {
                        line = staggerLines - line - 1;
                    }
                    y += line * (axis.labelOffset / staggerLines);
                }

                return {
                    x: x,
                    y: Math.round(y)
                };
            },

            /**
             * Extendible method to return the path of the marker
             */
            getMarkPath: function(x, y, tickLength, tickWidth, horiz, renderer) {
                return renderer.crispLine([
                    'M',
                    x,
                    y,
                    'L',
                    x + (horiz ? 0 : -tickLength),
                    y + (horiz ? tickLength : 0)
                ], tickWidth);
            },

            /**
             * Renders the gridLine.
             * @param  {Boolean} old         Whether or not the tick is old
             * @param  {number} opacity      The opacity of the grid line
             * @param  {number} reverseCrisp Modifier for avoiding overlapping 1 or -1
             * @return {undefined}
             */
            renderGridLine: function(old, opacity, reverseCrisp) {
                var tick = this,
                    axis = tick.axis,
                    options = axis.options,
                    gridLine = tick.gridLine,
                    gridLinePath,
                    attribs = {},
                    pos = tick.pos,
                    type = tick.type,
                    tickmarkOffset = axis.tickmarkOffset,
                    renderer = axis.chart.renderer;


                var gridPrefix = type ? type + 'Grid' : 'grid',
                    gridLineWidth = options[gridPrefix + 'LineWidth'],
                    gridLineColor = options[gridPrefix + 'LineColor'],
                    dashStyle = options[gridPrefix + 'LineDashStyle'];


                if (!gridLine) {

                    attribs.stroke = gridLineColor;
                    attribs['stroke-width'] = gridLineWidth;
                    if (dashStyle) {
                        attribs.dashstyle = dashStyle;
                    }

                    if (!type) {
                        attribs.zIndex = 1;
                    }
                    if (old) {
                        attribs.opacity = 0;
                    }
                    tick.gridLine = gridLine = renderer.path()
                        .attr(attribs)
                        .addClass(
                            'highcharts-' + (type ? type + '-' : '') + 'grid-line'
                        )
                        .add(axis.gridGroup);
                }

                // If the parameter 'old' is set, the current call will be followed
                // by another call, therefore do not do any animations this time
                if (!old && gridLine) {
                    gridLinePath = axis.getPlotLinePath(
                        pos + tickmarkOffset,
                        gridLine.strokeWidth() * reverseCrisp,
                        old, true
                    );
                    if (gridLinePath) {
                        gridLine[tick.isNew ? 'attr' : 'animate']({
                            d: gridLinePath,
                            opacity: opacity
                        });
                    }
                }
            },

            /**
             * Renders the tick mark.
             * @param  {Object} xy           The position vector of the mark
             * @param  {number} xy.x         The x position of the mark
             * @param  {number} xy.y         The y position of the mark
             * @param  {number} opacity      The opacity of the mark
             * @param  {number} reverseCrisp Modifier for avoiding overlapping 1 or -1
             * @return {undefined}
             */
            renderMark: function(xy, opacity, reverseCrisp) {
                var tick = this,
                    axis = tick.axis,
                    options = axis.options,
                    renderer = axis.chart.renderer,
                    type = tick.type,
                    tickPrefix = type ? type + 'Tick' : 'tick',
                    tickSize = axis.tickSize(tickPrefix),
                    mark = tick.mark,
                    isNewMark = !mark,
                    x = xy.x,
                    y = xy.y;


                var tickWidth = pick(
                        options[tickPrefix + 'Width'], !type && axis.isXAxis ? 1 : 0
                    ), // X axis defaults to 1
                    tickColor = options[tickPrefix + 'Color'];


                if (tickSize) {

                    // negate the length
                    if (axis.opposite) {
                        tickSize[0] = -tickSize[0];
                    }

                    // First time, create it
                    if (isNewMark) {
                        tick.mark = mark = renderer.path()
                            .addClass('highcharts-' + (type ? type + '-' : '') + 'tick')
                            .add(axis.axisGroup);


                        mark.attr({
                            stroke: tickColor,
                            'stroke-width': tickWidth
                        });

                    }
                    mark[isNewMark ? 'attr' : 'animate']({
                        d: tick.getMarkPath(
                            x,
                            y,
                            tickSize[0],
                            mark.strokeWidth() * reverseCrisp,
                            axis.horiz,
                            renderer),
                        opacity: opacity
                    });

                }
            },

            /**
             * Renders the tick label.
             * Note: The label should already be created in init(), so it should only
             * have to be moved into place.
             * @param  {Object} xy      The position vector of the label
             * @param  {number} xy.x    The x position of the label
             * @param  {number} xy.y    The y position of the label
             * @param  {Boolean} old    Whether or not the tick is old
             * @param  {number} opacity The opacity of the label
             * @param  {number} index   The index of the tick
             * @return {undefined}
             */
            renderLabel: function(xy, old, opacity, index) {
                var tick = this,
                    axis = tick.axis,
                    horiz = axis.horiz,
                    options = axis.options,
                    label = tick.label,
                    labelOptions = options.labels,
                    step = labelOptions.step,
                    tickmarkOffset = axis.tickmarkOffset,
                    show = true,
                    x = xy.x,
                    y = xy.y;
                if (label && isNumber(x)) {
                    label.xy = xy = tick.getLabelPosition(
                        x,
                        y,
                        label,
                        horiz,
                        labelOptions,
                        tickmarkOffset,
                        index,
                        step
                    );

                    // Apply show first and show last. If the tick is both first and
                    // last, it is a single centered tick, in which case we show the
                    // label anyway (#2100).
                    if (
                        (
                            tick.isFirst &&
                            !tick.isLast &&
                            !pick(options.showFirstLabel, 1)
                        ) ||
                        (
                            tick.isLast &&
                            !tick.isFirst &&
                            !pick(options.showLastLabel, 1)
                        )
                    ) {
                        show = false;

                        // Handle label overflow and show or hide accordingly
                    } else if (horiz && !axis.isRadial && !labelOptions.step &&
                        !labelOptions.rotation && !old && opacity !== 0) {
                        tick.handleOverflow(xy);
                    }

                    // apply step
                    if (step && index % step) {
                        // show those indices dividable by step
                        show = false;
                    }

                    // Set the new position, and show or hide
                    if (show && isNumber(xy.y)) {
                        xy.opacity = opacity;
                        label[tick.isNewLabel ? 'attr' : 'animate'](xy);
                        tick.isNewLabel = false;
                    } else {
                        label.attr('y', -9999); // #1338
                        tick.isNewLabel = true;
                    }
                }
            },

            /**
             * Put everything in place
             *
             * @param index {Number}
             * @param old {Boolean} Use old coordinates to prepare an animation into new
             *                      position
             */
            render: function(index, old, opacity) {
                var tick = this,
                    axis = tick.axis,
                    horiz = axis.horiz,
                    pos = tick.pos,
                    tickmarkOffset = axis.tickmarkOffset,
                    xy = tick.getPosition(horiz, pos, tickmarkOffset, old),
                    x = xy.x,
                    y = xy.y,
                    reverseCrisp = ((horiz && x === axis.pos + axis.len) ||
                        (!horiz && y === axis.pos)) ? -1 : 1; // #1480, #1687

                opacity = pick(opacity, 1);
                this.isActive = true;

                // Create the grid line
                this.renderGridLine(old, opacity, reverseCrisp);

                // create the tick mark
                this.renderMark(xy, opacity, reverseCrisp);

                // the label is created on init - now move it into place
                this.renderLabel(xy, old, opacity, index);

                tick.isNew = false;
            },

            /**
             * Destructor for the tick prototype
             */
            destroy: function() {
                destroyObjectProperties(this, this.axis);
            }
        };

    }(Highcharts));
    var Axis = (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */

        var addEvent = H.addEvent,
            animObject = H.animObject,
            arrayMax = H.arrayMax,
            arrayMin = H.arrayMin,
            color = H.color,
            correctFloat = H.correctFloat,
            defaultOptions = H.defaultOptions,
            defined = H.defined,
            deg2rad = H.deg2rad,
            destroyObjectProperties = H.destroyObjectProperties,
            each = H.each,
            extend = H.extend,
            fireEvent = H.fireEvent,
            format = H.format,
            getMagnitude = H.getMagnitude,
            grep = H.grep,
            inArray = H.inArray,
            isArray = H.isArray,
            isNumber = H.isNumber,
            isString = H.isString,
            merge = H.merge,
            normalizeTickInterval = H.normalizeTickInterval,
            objectEach = H.objectEach,
            pick = H.pick,
            removeEvent = H.removeEvent,
            splat = H.splat,
            syncTimeout = H.syncTimeout,
            Tick = H.Tick;

        /**
         * Create a new axis object. Called internally when instanciating a new chart or
         * adding axes by {@link Highcharts.Chart#addAxis}.
         *
         * A chart can have from 0 axes (pie chart) to multiples. In a normal, single
         * series cartesian chart, there is one X axis and one Y axis.
         * 
         * The X axis or axes are referenced by {@link Highcharts.Chart.xAxis}, which is
         * an array of Axis objects. If there is only one axis, it can be referenced
         * through `chart.xAxis[0]`, and multiple axes have increasing indices. The same
         * pattern goes for Y axes.
         * 
         * If you need to get the axes from a series object, use the `series.xAxis` and
         * `series.yAxis` properties. These are not arrays, as one series can only be
         * associated to one X and one Y axis.
         * 
         * A third way to reference the axis programmatically is by `id`. Add an `id` in
         * the axis configuration options, and get the axis by
         * {@link Highcharts.Chart#get}.
         * 
         * Configuration options for the axes are given in options.xAxis and
         * options.yAxis.
         * 
         * @class Highcharts.Axis
         * @memberOf Highcharts
         * @param {Highcharts.Chart} chart - The Chart instance to apply the axis on.
         * @param {Object} options - Axis options
         */
        var Axis = function() {
            this.init.apply(this, arguments);
        };

        H.extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {

            /**
             * The X axis or category axis. Normally this is the horizontal axis,
             * though if the chart is inverted this is the vertical axis. In case of
             * multiple axes, the xAxis node is an array of configuration objects.
             * 
             * See [the Axis object](#Axis) for programmatic access to the axis.
             *
             * @productdesc {highmaps}
             * In Highmaps, the axis is hidden, but it is used behind the scenes to
             * control features like zooming and panning. Zooming is in effect the same
             * as setting the extremes of one of the exes.
             * 
             * @optionparent xAxis
             */
            defaultOptions: {
                /**
                 * Whether to allow decimals in this axis' ticks. When counting
                 * integers, like persons or hits on a web page, decimals should
                 * be avoided in the labels.
                 *
                 * @type      {Boolean}
                 * @see       [minTickInterval](#xAxis.minTickInterval)
                 * @sample    {highcharts|highstock}
                 *            highcharts/yaxis/allowdecimals-true/
                 *            True by default
                 * @sample    {highcharts|highstock}
                 *            highcharts/yaxis/allowdecimals-false/
                 *            False
                 * @default   true
                 * @since     2.0
                 * @apioption xAxis.allowDecimals
                 */
                // allowDecimals: null,


                /**
                 * When using an alternate grid color, a band is painted across the
                 * plot area between every other grid line.
                 *
                 * @type      {Color}
                 * @sample    {highcharts} highcharts/yaxis/alternategridcolor/
                 *            Alternate grid color on the Y axis
                 * @sample    {highstock} stock/xaxis/alternategridcolor/
                 *            Alternate grid color on the Y axis
                 * @default   null
                 * @apioption xAxis.alternateGridColor
                 */
                // alternateGridColor: null,

                /**
                 * An array defining breaks in the axis, the sections defined will be
                 * left out and all the points shifted closer to each other.
                 *
                 * @productdesc {highcharts}
                 * Requires that the broken-axis.js module is loaded.
                 *
                 * @type      {Array}
                 * @sample    {highcharts}
                 *            highcharts/axisbreak/break-simple/
                 *            Simple break
                 * @sample    {highcharts|highstock}
                 *            highcharts/axisbreak/break-visualized/
                 *            Advanced with callback
                 * @sample    {highstock}
                 *            stock/demo/intraday-breaks/
                 *            Break on nights and weekends
                 * @since     4.1.0
                 * @product   highcharts highstock
                 * @apioption xAxis.breaks
                 */

                /**
                 * A number indicating how much space should be left between the start
                 * and the end of the break. The break size is given in axis units,
                 * so for instance on a `datetime` axis, a break size of 3600000 would
                 * indicate the equivalent of an hour.
                 *
                 * @type      {Number}
                 * @default   0
                 * @since     4.1.0
                 * @product   highcharts highstock
                 * @apioption xAxis.breaks.breakSize
                 */

                /**
                 * The point where the break starts.
                 *
                 * @type      {Number}
                 * @since     4.1.0
                 * @product   highcharts highstock
                 * @apioption xAxis.breaks.from
                 */

                /**
                 * Defines an interval after which the break appears again. By default
                 * the breaks do not repeat.
                 *
                 * @type      {Number}
                 * @default   0
                 * @since     4.1.0
                 * @product   highcharts highstock
                 * @apioption xAxis.breaks.repeat
                 */

                /**
                 * The point where the break ends.
                 *
                 * @type      {Number}
                 * @since     4.1.0
                 * @product   highcharts highstock
                 * @apioption xAxis.breaks.to
                 */

                /**
                 * If categories are present for the xAxis, names are used instead of
                 * numbers for that axis. Since Highcharts 3.0, categories can also
                 * be extracted by giving each point a [name](#series.data) and setting
                 * axis [type](#xAxis.type) to `category`. However, if you have multiple
                 * series, best practice remains defining the `categories` array.
                 *
                 * Example:
                 *
                 * <pre>categories: ['Apples', 'Bananas', 'Oranges']</pre>
                 *
                 * @type      {Array<String>}
                 * @sample    {highcharts} highcharts/chart/reflow-true/
                 *            With
                 * @sample    {highcharts} highcharts/xaxis/categories/
                 *            Without
                 * @product   highcharts
                 * @default   null
                 * @apioption xAxis.categories
                 */
                // categories: [],

                /**
                 * The highest allowed value for automatically computed axis extremes.
                 *
                 * @type      {Number}
                 * @see       [floor](#xAxis.floor)
                 * @sample    {highcharts|highstock} highcharts/yaxis/floor-ceiling/
                 *            Floor and ceiling
                 * @since     4.0
                 * @product   highcharts highstock
                 * @apioption xAxis.ceiling
                 */

                /**
                 * A class name that opens for styling the axis by CSS, especially in
                 * Highcharts styled mode. The class name is applied to group elements
                 * for the grid, axis elements and labels.
                 *
                 * @type      {String}
                 * @sample    {highcharts|highstock|highmaps}
                 *            highcharts/css/axis/
                 *            Multiple axes with separate styling
                 * @since     5.0.0
                 * @apioption xAxis.className
                 */

                /**
                 * Configure a crosshair that follows either the mouse pointer or the
                 * hovered point.
                 *
                 * In styled mode, the crosshairs are styled in the
                 * `.highcharts-crosshair`, `.highcharts-crosshair-thin` or
                 * `.highcharts-xaxis-category` classes.
                 *
                 * @productdesc {highstock}
                 * In Highstock, bu default, the crosshair is enabled on the X axis and
                 * disabled on the Y axis.
                 *
                 * @type      {Boolean|Object}
                 * @sample    {highcharts} highcharts/xaxis/crosshair-both/
                 *            Crosshair on both axes
                 * @sample    {highstock} stock/xaxis/crosshairs-xy/
                 *            Crosshair on both axes
                 * @sample    {highmaps} highcharts/xaxis/crosshair-both/
                 *            Crosshair on both axes
                 * @default   false
                 * @since     4.1
                 * @apioption xAxis.crosshair
                 */

                /**
                 * A class name for the crosshair, especially as a hook for styling.
                 *
                 * @type      {String}
                 * @since     5.0.0
                 * @apioption xAxis.crosshair.className
                 */

                /**
                 * The color of the crosshair. Defaults to `#cccccc` for numeric and
                 * datetime axes, and `rgba(204,214,235,0.25)` for category axes, where
                 * the crosshair by default highlights the whole category.
                 *
                 * @type      {Color}
                 * @sample    {highcharts|highstock|highmaps}
                 *            highcharts/xaxis/crosshair-customized/
                 *            Customized crosshairs
                 * @default   #cccccc
                 * @since     4.1
                 * @apioption xAxis.crosshair.color
                 */

                /**
                 * The dash style for the crosshair. See
                 * [series.dashStyle](#plotOptions.series.dashStyle)
                 * for possible values.
                 *
                 * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
                 *              "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
                 *              "DashDot", "LongDashDot", "LongDashDotDot"]
                 * @type       {String}
                 * @sample     {highcharts|highmaps} highcharts/xaxis/crosshair-dotted/
                 *             Dotted crosshair
                 * @sample     {highstock} stock/xaxis/crosshair-dashed/
                 *             Dashed X axis crosshair
                 * @default    Solid
                 * @since      4.1
                 * @apioption  xAxis.crosshair.dashStyle
                 */

                /**
                 * Whether the crosshair should snap to the point or follow the pointer
                 * independent of points.
                 *
                 * @type      {Boolean}
                 * @sample    {highcharts|highstock}
                 *            highcharts/xaxis/crosshair-snap-false/
                 *            True by default
                 * @sample    {highmaps}
                 *            maps/demo/latlon-advanced/
                 *            Snap is false
                 * @default   true
                 * @since     4.1
                 * @apioption xAxis.crosshair.snap
                 */

                /**
                 * The pixel width of the crosshair. Defaults to 1 for numeric or
                 * datetime axes, and for one category width for category axes.
                 *
                 * @type      {Number}
                 * @sample    {highcharts} highcharts/xaxis/crosshair-customized/
                 *            Customized crosshairs
                 * @sample    {highstock} highcharts/xaxis/crosshair-customized/
                 *            Customized crosshairs
                 * @sample    {highmaps} highcharts/xaxis/crosshair-customized/
                 *            Customized crosshairs
                 * @default   1
                 * @since     4.1
                 * @apioption xAxis.crosshair.width
                 */

                /**
                 * The Z index of the crosshair. Higher Z indices allow drawing the
                 * crosshair on top of the series or behind the grid lines.
                 *
                 * @type      {Number}
                 * @default   2
                 * @since     4.1
                 * @apioption xAxis.crosshair.zIndex
                 */

                /**
                 * For a datetime axis, the scale will automatically adjust to the
                 * appropriate unit. This member gives the default string
                 * representations used for each unit. For intermediate values,
                 * different units may be used, for example the `day` unit can be used
                 * on midnight and `hour` unit be used for intermediate values on the
                 * same axis. For an overview of the replacement codes, see
                 * [dateFormat](#Highcharts.dateFormat). Defaults to:
                 * 
                 * <pre>{
                 *     millisecond: '%H:%M:%S.%L',
                 *     second: '%H:%M:%S',
                 *     minute: '%H:%M',
                 *     hour: '%H:%M',
                 *     day: '%e. %b',
                 *     week: '%e. %b',
                 *     month: '%b \'%y',
                 *     year: '%Y'
                 * }</pre>
                 * 
                 * @type    {Object}
                 * @sample  {highcharts} highcharts/xaxis/datetimelabelformats/
                 *          Different day format on X axis
                 * @sample  {highstock} stock/xaxis/datetimelabelformats/
                 *          More information in x axis labels
                 * @product highcharts highstock
                 */
                dateTimeLabelFormats: {
                    millisecond: '%H:%M:%S.%L',
                    second: '%H:%M:%S',
                    minute: '%H:%M',
                    hour: '%H:%M',
                    day: '%e. %b',
                    week: '%e. %b',
                    month: '%b \'%y',
                    year: '%Y'
                },

                /**
                 * _Requires Accessibility module_
                 *
                 * Description of the axis to screen reader users.
                 *
                 * @type      {String}
                 * @default   undefined
                 * @since     5.0.0
                 * @apioption xAxis.description
                 */

                /**
                 * Whether to force the axis to end on a tick. Use this option with
                 * the `maxPadding` option to control the axis end.
                 *
                 * @productdesc {highstock}
                 * In Highstock, `endOnTick` is always false when the navigator is
                 * enabled, to prevent jumpy scrolling.
                 * 
                 * @sample {highcharts} highcharts/chart/reflow-true/
                 *         True by default
                 * @sample {highcharts} highcharts/yaxis/endontick/
                 *         False
                 * @sample {highstock} stock/demo/basic-line/
                 *         True by default
                 * @sample {highstock} stock/xaxis/endontick/
                 *         False
                 * @since  1.2.0
                 */
                endOnTick: false,

                /**
                 * Event handlers for the axis.
                 *
                 * @apioption xAxis.events
                 */

                /**
                 * An event fired after the breaks have rendered.
                 *
                 * @type      {Function}
                 * @see       [breaks](#xAxis.breaks)
                 * @sample    {highcharts} highcharts/axisbreak/break-event/
                 *            AfterBreak Event
                 * @since     4.1.0
                 * @product   highcharts
                 * @apioption xAxis.events.afterBreaks
                 */

                /**
                 * As opposed to the `setExtremes` event, this event fires after the
                 * final min and max values are computed and corrected for `minRange`.
                 *
                 *
                 * Fires when the minimum and maximum is set for the axis, either by
                 * calling the `.setExtremes()` method or by selecting an area in the
                 * chart. One parameter, `event`, is passed to the function, containing
                 * common event information.
                 *
                 * The new user set minimum and maximum values can be found by `event.
                 * min` and `event.max`. These reflect the axis minimum and maximum
                 * in axis values. The actual data extremes are found in `event.dataMin`
                 * and `event.dataMax`.
                 *
                 * @type      {Function}
                 * @context   Axis
                 * @since     2.3
                 * @apioption xAxis.events.afterSetExtremes
                 */

                /**
                 * An event fired when a break from this axis occurs on a point.
                 *
                 * @type      {Function}
                 * @see       [breaks](#xAxis.breaks)
                 * @context   Axis
                 * @sample    {highcharts} highcharts/axisbreak/break-visualized/
                 *            Visualization of a Break
                 * @since     4.1.0
                 * @product   highcharts
                 * @apioption xAxis.events.pointBreak
                 */

                /**
                 * An event fired when a point falls inside a break from this axis.
                 *
                 * @type      {Function}
                 * @context   Axis
                 * @product   highcharts highstock
                 * @apioption xAxis.events.pointInBreak
                 */

                /**
                 * Fires when the minimum and maximum is set for the axis, either by
                 * calling the `.setExtremes()` method or by selecting an area in the
                 * chart. One parameter, `event`, is passed to the function,
                 * containing common event information.
                 *
                 * The new user set minimum and maximum values can be found by `event.
                 * min` and `event.max`. These reflect the axis minimum and maximum
                 * in data values. When an axis is zoomed all the way out from the 
                 * "Reset zoom" button, `event.min` and `event.max` are null, and
                 * the new extremes are set based on `this.dataMin` and `this.dataMax`.
                 *
                 * @type      {Function}
                 * @context   Axis
                 * @sample    {highstock} stock/xaxis/events-setextremes/
                 *            Log new extremes on x axis
                 * @since     1.2.0
                 * @apioption xAxis.events.setExtremes
                 */

                /**
                 * The lowest allowed value for automatically computed axis extremes.
                 *
                 * @type      {Number}
                 * @see       [ceiling](#yAxis.ceiling)
                 * @sample    {highcharts} highcharts/yaxis/floor-ceiling/
                 *            Floor and ceiling
                 * @sample    {highstock} stock/demo/lazy-loading/
                 *            Prevent negative stock price on Y axis
                 * @default   null
                 * @since     4.0
                 * @product   highcharts highstock
                 * @apioption xAxis.floor
                 */

                /**
                 * The dash or dot style of the grid lines. For possible values, see
                 * [this demonstration](http://jsfiddle.net/gh/get/library/pure/
                 *highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
                 *series-dashstyle-all/).
                 *
                 * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
                 *              "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
                 *              "DashDot", "LongDashDot", "LongDashDotDot"]
                 * @type       {String}
                 * @sample     {highcharts} highcharts/yaxis/gridlinedashstyle/
                 *             Long dashes
                 * @sample     {highstock} stock/xaxis/gridlinedashstyle/
                 *             Long dashes
                 * @default    Solid
                 * @since      1.2
                 * @apioption  xAxis.gridLineDashStyle
                 */

                /**
                 * The Z index of the grid lines.
                 *
                 * @type      {Number}
                 * @sample    {highcharts|highstock} highcharts/xaxis/gridzindex/
                 *            A Z index of 4 renders the grid above the graph
                 * @default   1
                 * @product   highcharts highstock
                 * @apioption xAxis.gridZIndex
                 */

                /**
                 * An id for the axis. This can be used after render time to get
                 * a pointer to the axis object through `chart.get()`.
                 *
                 * @type      {String}
                 * @sample    {highcharts} highcharts/xaxis/id/
                 *            Get the object
                 * @sample    {highstock} stock/xaxis/id/
                 *            Get the object
                 * @default   null
                 * @since     1.2.0
                 * @apioption xAxis.id
                 */

                /**
                 * The axis labels show the number or category for each tick.
                 *
                 * @productdesc {highmaps}
                 * X and Y axis labels are by default disabled in Highmaps, but the
                 * functionality is inherited from Highcharts and used on `colorAxis`,
                 * and can be enabled on X and Y axes too.
                 */
                labels: {
                    /**
                     * What part of the string the given position is anchored to.
                     * If `left`, the left side of the string is at the axis position.
                     * Can be one of `"left"`, `"center"` or `"right"`. Defaults to
                     * an intelligent guess based on which side of the chart the axis
                     * is on and the rotation of the label.
                     *
                     * @validvalue ["left", "center", "right"]
                     * @type       {String}
                     * @sample     {highcharts} highcharts/xaxis/labels-align-left/
                     *             Left
                     * @sample     {highcharts} highcharts/xaxis/labels-align-right/
                     *             Right
                     * @apioption  xAxis.labels.align
                     */
                    // align: 'center',

                    /**
                     * For horizontal axes, the allowed degrees of label rotation
                     * to prevent overlapping labels. If there is enough space,
                     * labels are not rotated. As the chart gets narrower, it
                     * will start rotating the labels -45 degrees, then remove
                     * every second label and try again with rotations 0 and -45 etc.
                     * Set it to `false` to disable rotation, which will
                     * cause the labels to word-wrap if possible.
                     *
                     * @type      {Array<Number>}
                     * @sample    {highcharts|highstock}
                     *            highcharts/xaxis/labels-autorotation-default/
                     *            Default auto rotation of 0 or -45
                     * @sample    {highcharts|highstock}
                     *            highcharts/xaxis/labels-autorotation-0-90/
                     *            Custom graded auto rotation
                     * @default   [-45]
                     * @since     4.1.0
                     * @product   highcharts highstock
                     * @apioption xAxis.labels.autoRotation
                     */

                    /**
                     * When each category width is more than this many pixels, we don't
                     * apply auto rotation. Instead, we lay out the axis label with word
                     * wrap. A lower limit makes sense when the label contains multiple
                     * short words that don't extend the available horizontal space for
                     * each label.
                     *
                     * @type      {Number}
                     * @sample    {highcharts}
                     *            highcharts/xaxis/labels-autorotationlimit/
                     *            Lower limit
                     * @default   80
                     * @since     4.1.5
                     * @product   highcharts
                     * @apioption xAxis.labels.autoRotationLimit
                     */

                    /**
                     * Polar charts only. The label's pixel distance from the perimeter
                     * of the plot area.
                     *
                     * @type      {Number}
                     * @default   15
                     * @product   highcharts
                     * @apioption xAxis.labels.distance
                     */

                    /**
                     * Enable or disable the axis labels.
                     * 
                     * @sample  {highcharts} highcharts/xaxis/labels-enabled/
                     *          X axis labels disabled
                     * @sample  {highstock} stock/xaxis/labels-enabled/
                     *          X axis labels disabled
                     * @default {highcharts|highstock} true
                     * @default {highmaps} false
                     */
                    enabled: true,

                    /**
                     * A [format string](http://www.highcharts.com/docs/chart-
                     * concepts/labels-and-string-formatting) for the axis label.
                     *
                     * @type      {String}
                     * @sample    {highcharts|highstock} highcharts/yaxis/labels-format/
                     *            Add units to Y axis label
                     * @default   {value}
                     * @since     3.0
                     * @apioption xAxis.labels.format
                     */

                    /**
                     * Callback JavaScript function to format the label. The value
                     * is given by `this.value`. Additional properties for `this` are
                     * `axis`, `chart`, `isFirst` and `isLast`. The value of the default
                     * label formatter can be retrieved by calling
                     * `this.axis.defaultLabelFormatter.call(this)` within the function.
                     *
                     * Defaults to:
                     *
                     * <pre>function() {
                     *     return this.value;
                     * }</pre>
                     *
                     * @type      {Function}
                     * @sample    {highcharts}
                     *            highcharts/xaxis/labels-formatter-linked/
                     *            Linked category names
                     * @sample    {highcharts}
                     *            highcharts/xaxis/labels-formatter-extended/
                     *            Modified numeric labels
                     * @sample    {highstock}
                     *            stock/xaxis/labels-formatter/
                     *            Added units on Y axis
                     * @apioption xAxis.labels.formatter
                     */

                    /**
                     * How to handle overflowing labels on horizontal axis. Can be
                     * undefined, `false` or `"justify"`. By default it aligns inside
                     * the chart area. If "justify", labels will not render outside
                     * the plot area. If `false`, it will not be aligned at all.
                     * If there is room to move it, it will be aligned to the edge,
                     * else it will be removed.
                     *
                     * @deprecated
                     * @validvalue [null, "justify"]
                     * @type       {String}
                     * @since      2.2.5
                     * @apioption  xAxis.labels.overflow
                     */

                    /**
                     * The pixel padding for axis labels, to ensure white space between
                     * them.
                     *
                     * @type      {Number}
                     * @default   5
                     * @product   highcharts
                     * @apioption xAxis.labels.padding
                     */

                    /**
                     * Whether to reserve space for the labels. This can be turned off
                     * when for example the labels are rendered inside the plot area
                     * instead of outside.
                     *
                     * @type      {Boolean}
                     * @sample    {highcharts} highcharts/xaxis/labels-reservespace/
                     *            No reserved space, labels inside plot
                     * @default   true
                     * @since     4.1.10
                     * @product   highcharts
                     * @apioption xAxis.labels.reserveSpace
                     */

                    /**
                     * Rotation of the labels in degrees.
                     *
                     * @type      {Number}
                     * @sample    {highcharts} highcharts/xaxis/labels-rotation/
                     *            X axis labels rotated 90°
                     * @default   0
                     * @apioption xAxis.labels.rotation
                     */
                    // rotation: 0,

                    /**
                     * Horizontal axes only. The number of lines to spread the labels
                     * over to make room or tighter labels.
                     *
                     * @type      {Number}
                     * @sample    {highcharts} highcharts/xaxis/labels-staggerlines/
                     *            Show labels over two lines
                     * @sample    {highstock} stock/xaxis/labels-staggerlines/
                     *            Show labels over two lines
                     * @default   null
                     * @since     2.1
                     * @apioption xAxis.labels.staggerLines
                     */

                    /**
                     * To show only every _n_'th label on the axis, set the step to _n_.
                     * Setting the step to 2 shows every other label.
                     *
                     * By default, the step is calculated automatically to avoid
                     * overlap. To prevent this, set it to 1\. This usually only
                     * happens on a category axis, and is often a sign that you have
                     * chosen the wrong axis type.
                     *
                     * Read more at
                     * [Axis docs](http://www.highcharts.com/docs/chart-concepts/axes)
                     * => What axis should I use?
                     *
                     * @type      {Number}
                     * @sample    {highcharts} highcharts/xaxis/labels-step/
                     *            Showing only every other axis label on a categorized
                     *            x axis
                     * @sample    {highcharts} highcharts/xaxis/labels-step-auto/
                     *            Auto steps on a category axis
                     * @default   null
                     * @since     2.1
                     * @apioption xAxis.labels.step
                     */
                    // step: null,



                    /**
                     * CSS styles for the label. Use `whiteSpace: 'nowrap'` to prevent
                     * wrapping of category labels. Use `textOverflow: 'none'` to
                     * prevent ellipsis (dots).
                     * 
                     * In styled mode, the labels are styled with the
                     * `.highcharts-axis-labels` class.
                     * 
                     * @type   {CSSObject}
                     * @sample {highcharts} highcharts/xaxis/labels-style/
                     *         Red X axis labels
                     */
                    style: {
                        color: '#666666',
                        cursor: 'default',
                        fontSize: '11px'
                    },


                    /**
                     * Whether to [use HTML](http://www.highcharts.com/docs/chart-
                     * concepts/labels-and-string-formatting#html) to render the labels.
                     *
                     * @type      {Boolean}
                     * @default   false
                     * @apioption xAxis.labels.useHTML
                     */

                    /**
                     * The x position offset of the label relative to the tick position
                     * on the axis.
                     * 
                     * @sample {highcharts} highcharts/xaxis/labels-x/
                     *         Y axis labels placed on grid lines
                     */
                    x: 0

                    /**
                     * The y position offset of the label relative to the tick position
                     * on the axis. The default makes it adapt to the font size on
                     * bottom axis.
                     *
                     * @type      {Number}
                     * @sample    {highcharts} highcharts/xaxis/labels-x/
                     *            Y axis labels placed on grid lines
                     * @default   null
                     * @apioption xAxis.labels.y
                     */

                    /**
                     * The Z index for the axis labels.
                     *
                     * @type {Number}
                     * @default 7
                     * @apioption xAxis.labels.zIndex
                     */
                },

                /**
                 * Index of another axis that this axis is linked to. When an axis is
                 * linked to a master axis, it will take the same extremes as
                 * the master, but as assigned by min or max or by setExtremes.
                 * It can be used to show additional info, or to ease reading the
                 * chart by duplicating the scales.
                 *
                 * @type      {Number}
                 * @sample    {highcharts} highcharts/xaxis/linkedto/
                 *            Different string formats of the same date
                 * @sample    {highcharts} highcharts/yaxis/linkedto/
                 *            Y values on both sides
                 * @default   null
                 * @since     2.0.2
                 * @product   highcharts highstock
                 * @apioption xAxis.linkedTo
                 */

                /**
                 * The maximum value of the axis. If `null`, the max value is
                 * automatically calculated.
                 *
                 * If the `endOnTick` option is true, the `max` value might
                 * be rounded up.
                 *
                 * If a [tickAmount](#yAxis.tickAmount) is set, the axis may be extended
                 * beyond the set max in order to reach the given number of ticks. The
                 * same may happen in a chart with multiple axes, determined by [chart.
                 * alignTicks](#chart), where a `tickAmount` is applied internally.
                 *
                 * @type      {Number}
                 * @sample    {highcharts} highcharts/yaxis/max-200/
                 *            Y axis max of 200
                 * @sample    {highcharts} highcharts/yaxis/max-logarithmic/
                 *            Y axis max on logarithmic axis
                 * @sample    {highstock} stock/xaxis/min-max/
                 *            Fixed min and max on X axis
                 * @sample    {highmaps} maps/axis/min-max/
                 *            Pre-zoomed to a specific area
                 * @apioption xAxis.max
                 */

                /**
                 * Padding of the max value relative to the length of the axis. A
                 * padding of 0.05 will make a 100px axis 5px longer. This is useful
                 * when you don't want the highest data value to appear on the edge
                 * of the plot area. When the axis' `max` option is set or a max extreme
                 * is set using `axis.setExtremes()`, the maxPadding will be ignored.
                 * 
                 * @sample  {highcharts} highcharts/yaxis/maxpadding/
                 *          Max padding of 0.25 on y axis
                 * @sample  {highstock} stock/xaxis/minpadding-maxpadding/
                 *          Greater min- and maxPadding
                 * @sample  {highmaps} maps/chart/plotbackgroundcolor-gradient/
                 *          Add some padding
                 * @default {highcharts} 0.01
                 * @default {highstock|highmaps} 0
                 * @since   1.2.0
                 */
                maxPadding: 0.01,

                /**
                 * Deprecated. Use `minRange` instead.
                 *
                 * @deprecated
                 * @type       {Number}
                 * @product    highcharts highstock
                 * @apioption  xAxis.maxZoom
                 */

                /**
                 * The minimum value of the axis. If `null` the min value is 
                 * automatically calculated.
                 *
                 * If the `startOnTick` option is true (default), the `min` value might
                 * be rounded down.
                 *
                 * The automatically calculated minimum value is also affected by
                 * [floor](#yAxis.floor), [softMin](#yAxis.softMin),
                 * [minPadding](#yAxis.minPadding), [minRange](#yAxis.minRange)
                 * as well as [series.threshold](#plotOptions.series.threshold)
                 * and [series.softThreshold](#plotOptions.series.softThreshold).
                 *
                 * @type      {Number}
                 * @sample    {highcharts} highcharts/yaxis/min-startontick-false/
                 *            -50 with startOnTick to false
                 * @sample    {highcharts} highcharts/yaxis/min-startontick-true/
                 *            -50 with startOnTick true by default
                 * @sample    {highstock} stock/xaxis/min-max/
                 *            Set min and max on X axis
                 * @sample    {highmaps} maps/axis/min-max/
                 *            Pre-zoomed to a specific area
                 * @apioption xAxis.min
                 */

                /**
                 * The dash or dot style of the minor grid lines. For possible values,
                 * see [this demonstration](http://jsfiddle.net/gh/get/library/pure/
                 * highcharts/highcharts/tree/master/samples/highcharts/plotoptions/
                 * series-dashstyle-all/).
                 *
                 * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
                 *              "ShortDashDotDot", "Dot", "Dash" ,"LongDash",
                 *              "DashDot", "LongDashDot", "LongDashDotDot"]
                 * @type       {String}
                 * @sample     {highcharts} highcharts/yaxis/minorgridlinedashstyle/
                 *             Long dashes on minor grid lines
                 * @sample     {highstock} stock/xaxis/minorgridlinedashstyle/
                 *             Long dashes on minor grid lines
                 * @default    Solid
                 * @since      1.2
                 * @apioption  xAxis.minorGridLineDashStyle
                 */

                /**
                 * Specific tick interval in axis units for the minor ticks.
                 * On a linear axis, if `"auto"`, the minor tick interval is
                 * calculated as a fifth of the tickInterval. If `null`, minor
                 * ticks are not shown.
                 *
                 * On logarithmic axes, the unit is the power of the value. For example,
                 * setting the minorTickInterval to 1 puts one tick on each of 0.1,
                 * 1, 10, 100 etc. Setting the minorTickInterval to 0.1 produces 9
                 * ticks between 1 and 10, 10 and 100 etc.
                 *
                 * If user settings dictate minor ticks to become too dense, they don't
                 * make sense, and will be ignored to prevent performance problems.
                 *
                 * @type      {Number|String}
                 * @sample    {highcharts} highcharts/yaxis/minortickinterval-null/
                 *            Null by default
                 * @sample    {highcharts} highcharts/yaxis/minortickinterval-5/
                 *            5 units
                 * @sample    {highcharts} highcharts/yaxis/minortickinterval-log-auto/
                 *            "auto"
                 * @sample    {highcharts} highcharts/yaxis/minortickinterval-log/
                 *            0.1
                 * @sample    {highstock} stock/demo/basic-line/
                 *            Null by default
                 * @sample    {highstock} stock/xaxis/minortickinterval-auto/
                 *            "auto"
                 * @apioption xAxis.minorTickInterval
                 */

                /**
                 * The pixel length of the minor tick marks.
                 * 
                 * @sample {highcharts} highcharts/yaxis/minorticklength/
                 *         10px on Y axis
                 * @sample {highstock} stock/xaxis/minorticks/
                 *         10px on Y axis
                 */
                minorTickLength: 2,

                /**
                 * The position of the minor tick marks relative to the axis line.
                 *  Can be one of `inside` and `outside`.
                 * 
                 * @validvalue ["inside", "outside"]
                 * @sample     {highcharts} highcharts/yaxis/minortickposition-outside/
                 *             Outside by default
                 * @sample     {highcharts} highcharts/yaxis/minortickposition-inside/
                 *             Inside
                 * @sample     {highstock} stock/xaxis/minorticks/
                 *             Inside
                 */
                minorTickPosition: 'outside',

                /**
                 * Enable or disable minor ticks. Unless
                 * [minorTickInterval](#xAxis.minorTickInterval) is set, the tick
                 * interval is calculated as a fifth of the `tickInterval`.
                 *
                 * On a logarithmic axis, minor ticks are laid out based on a best
                 * guess, attempting to enter approximately 5 minor ticks between
                 * each major tick.
                 *
                 * Prior to v6.0.0, ticks were unabled in auto layout by setting
                 * `minorTickInterval` to `"auto"`.
                 *
                 * @productdesc {highcharts}
                 * On axes using [categories](#xAxis.categories), minor ticks are not
                 * supported.
                 *
                 * @type      {Boolean}
                 * @default   false
                 * @since     6.0.0
                 * @sample    {highcharts} highcharts/yaxis/minorticks-true/
                 *            Enabled on linear Y axis
                 * @apioption xAxis.minorTicks
                 */

                /**
                 * The pixel width of the minor tick mark.
                 *
                 * @type      {Number}
                 * @sample    {highcharts} highcharts/yaxis/minortickwidth/
                 *            3px width
                 * @sample    {highstock} stock/xaxis/minorticks/
                 *            1px width
                 * @default   0
                 * @apioption xAxis.minorTickWidth
                 */

                /**
                 * Padding of the min value relative to the length of the axis. A
                 * padding of 0.05 will make a 100px axis 5px longer. This is useful
                 * when you don't want the lowest data value to appear on the edge
                 * of the plot area. When the axis' `min` option is set or a min extreme
                 * is set using `axis.setExtremes()`, the minPadding will be ignored.
                 * 
                 * @sample  {highcharts} highcharts/yaxis/minpadding/
                 *          Min padding of 0.2
                 * @sample  {highstock} stock/xaxis/minpadding-maxpadding/
                 *          Greater min- and maxPadding
                 * @sample  {highmaps} maps/chart/plotbackgroundcolor-gradient/
                 *          Add some padding
                 * @default {highcharts} 0.01
                 * @default {highstock|highmaps} 0
                 * @since   1.2.0
                 */
                minPadding: 0.01,

                /**
                 * The minimum range to display on this axis. The entire axis will not
                 * be allowed to span over a smaller interval than this. For example,
                 * for a datetime axis the main unit is milliseconds. If minRange is
                 * set to 3600000, you can't zoom in more than to one hour.
                 *
                 * The default minRange for the x axis is five times the smallest
                 * interval between any of the data points.
                 *
                 * On a logarithmic axis, the unit for the minimum range is the power.
                 * So a minRange of 1 means that the axis can be zoomed to 10-100,
                 * 100-1000, 1000-10000 etc.
                 *
                 * Note that the `minPadding`, `maxPadding`, `startOnTick` and
                 * `endOnTick` settings also affect how the extremes of the axis
                 * are computed.
                 *
                 * @type      {Number}
                 * @sample    {highcharts} highcharts/xaxis/minrange/
                 *            Minimum range of 5
                 * @sample    {highstock} stock/xaxis/minrange/
                 *            Max zoom of 6 months overrides user selections
                 * @sample    {highmaps} maps/axis/minrange/
                 *            Minimum range of 1000
                 * @apioption xAxis.minRange
                 */

                /**
                 * The minimum tick interval allowed in axis values. For example on
                 * zooming in on an axis with daily data, this can be used to prevent
                 * the axis from showing hours. Defaults to the closest distance between
                 * two points on the axis.
                 *
                 * @type      {Number}
                 * @since     2.3.0
                 * @apioption xAxis.minTickInterval
                 */

                /**
                 * The distance in pixels from the plot area to the axis line.
                 * A positive offset moves the axis with it's line, labels and ticks
                 * away from the plot area. This is typically used when two or more
                 * axes are displayed on the same side of the plot. With multiple
                 * axes the offset is dynamically adjusted to avoid collision, this
                 * can be overridden by setting offset explicitly.
                 *
                 * @type      {Number}
                 * @sample    {highcharts} highcharts/yaxis/offset/
                 *            Y axis offset of 70
                 * @sample    {highcharts} highcharts/yaxis/offset-centered/
                 *            Axes positioned in the center of the plot
                 * @sample    {highstock} stock/xaxis/offset/
                 *            Y axis offset by 70 px
                 * @default   0
                 * @apioption xAxis.offset
                 */

                /**
                 * Whether to display the axis on the opposite side of the normal. The
                 * normal is on the left side for vertical axes and bottom for
                 * horizontal, so the opposite sides will be right and top respectively.
                 * This is typically used with dual or multiple axes.
                 *
                 * @type      {Boolean}
                 * @sample    {highcharts} highcharts/yaxis/opposite/
                 *            Secondary Y axis opposite
                 * @sample    {highstock} stock/xaxis/opposite/
                 *            Y axis on left side
                 * @default   false
                 * @apioption xAxis.opposite
                 */

                /**
                 * Whether to reverse the axis so that the highest number is closest
                 * to the origin. If the chart is inverted, the x axis is reversed by
                 * default.
                 *
                 * @type      {Boolean}
                 * @sample    {highcharts} highcharts/yaxis/reversed/
                 *            Reversed Y axis
                 * @sample    {highstock} stock/xaxis/reversed/
                 *            Reversed Y axis
                 * @default   false
                 * @apioption xAxis.reversed
                 */
                // reversed: false,

                /**
                 * Whether to show the last tick label. Defaults to `true` on cartesian
                 * charts, and `false` on polar charts.
                 *
                 * @type      {Boolean}
                 * @sample    {highcharts} highcharts/xaxis/showlastlabel-true/
                 *            Set to true on X axis
                 * @sample    {highstock} stock/xaxis/showfirstlabel/
                 *            Labels below plot lines on Y axis
                 * @default   true
                 * @product   highcharts highstock
                 * @apioption xAxis.showLastLabel
                 */

                /**
                 * For datetime axes, this decides where to put the tick between weeks.
                 *  0 = Sunday, 1 = Monday.
                 * 
                 * @sample  {highcharts} highcharts/xaxis/startofweek-monday/
                 *          Monday by default
                 * @sample  {highcharts} highcharts/xaxis/startofweek-sunday/
                 *          Sunday
                 * @sample  {highstock} stock/xaxis/startofweek-1
                 *          Monday by default
                 * @sample  {highstock} stock/xaxis/startofweek-0
                 *          Sunday
                 * @product highcharts highstock
                 */
                startOfWeek: 1,

                /**
                 * Whether to force the axis to start on a tick. Use this option with
                 * the `minPadding` option to control the axis start.
                 *
                 * @productdesc {highstock}
                 * In Highstock, `startOnTick` is always false when the navigator is
                 * enabled, to prevent jumpy scrolling.
                 * 
                 * @sample  {highcharts} highcharts/xaxis/startontick-false/
                 *          False by default
                 * @sample  {highcharts} highcharts/xaxis/startontick-true/
                 *          True
                 * @sample  {highstock} stock/xaxis/endontick/
                 *          False for Y axis
                 * @since   1.2.0
                 */
                startOnTick: false,

                /**
                 * The pixel length of the main tick marks.
                 * 
                 * @sample {highcharts} highcharts/xaxis/ticklength/
                 *         20 px tick length on the X axis
                 * @sample {highstock} stock/xaxis/ticks/
                 *         Formatted ticks on X axis
                 */
                tickLength: 10,

                /**
                 * For categorized axes only. If `on` the tick mark is placed in the
                 * center of the category, if `between` the tick mark is placed between
                 * categories. The default is `between` if the `tickInterval` is 1,
                 *  else `on`.
                 * 
                 * @validvalue [null, "on", "between"]
                 * @sample     {highcharts} highcharts/xaxis/tickmarkplacement-between/
                 *             "between" by default
                 * @sample     {highcharts} highcharts/xaxis/tickmarkplacement-on/
                 *             "on"
                 * @product    highcharts
                 */
                tickmarkPlacement: 'between',

                /**
                 * If tickInterval is `null` this option sets the approximate pixel
                 * interval of the tick marks. Not applicable to categorized axis.
                 * 
                 * The tick interval is also influenced by the [minTickInterval](#xAxis.
                 * minTickInterval) option, that, by default prevents ticks from being
                 * denser than the data points.
                 * 
                 * @see    [tickInterval](#xAxis.tickInterval),
                 *         [tickPositioner](#xAxis.tickPositioner),
                 *         [tickPositions](#xAxis.tickPositions).
                 * @sample {highcharts} highcharts/xaxis/tickpixelinterval-50/
                 *         50 px on X axis
                 * @sample {highstock} stock/xaxis/tickpixelinterval/
                 *         200 px on X axis
                 */
                tickPixelInterval: 100,

                /**
                 * The position of the major tick marks relative to the axis line.
                 * Can be one of `inside` and `outside`.
                 * 
                 * @validvalue ["inside", "outside"]
                 * @sample     {highcharts} highcharts/xaxis/tickposition-outside/
                 *             "outside" by default
                 * @sample     {highcharts} highcharts/xaxis/tickposition-inside/
                 *             "inside"
                 * @sample     {highstock} stock/xaxis/ticks/
                 *             Formatted ticks on X axis
                 */
                tickPosition: 'outside',

                /**
                 * The axis title, showing next to the axis line.
                 *
                 * @productdesc {highmaps}
                 * In Highmaps, the axis is hidden by default, but adding an axis title
                 * is still possible. X axis and Y axis titles will appear at the bottom
                 * and left by default.
                 */
                title: {

                    /**
                     * Alignment of the title relative to the axis values. Possible
                     * values are "low", "middle" or "high".
                     * 
                     * @validvalue ["low", "middle", "high"]
                     * @sample     {highcharts} highcharts/xaxis/title-align-low/
                     *             "low"
                     * @sample     {highcharts} highcharts/xaxis/title-align-center/
                     *             "middle" by default
                     * @sample     {highcharts} highcharts/xaxis/title-align-high/
                     *             "high"
                     * @sample     {highcharts} highcharts/yaxis/title-offset/
                     *             Place the Y axis title on top of the axis
                     * @sample     {highstock} stock/xaxis/title-align/
                     *             Aligned to "high" value
                     */
                    align: 'middle',



                    /**
                     * CSS styles for the title. If the title text is longer than the
                     * axis length, it will wrap to multiple lines by default. This can
                     * be customized by setting `textOverflow: 'ellipsis'`, by 
                     * setting a specific `width` or by setting `wordSpace: 'nowrap'`.
                     * 
                     * In styled mode, the stroke width is given in the
                     * `.highcharts-axis-title` class.
                     * 
                     * @type    {CSSObject}
                     * @sample  {highcharts} highcharts/xaxis/title-style/
                     *          Red
                     * @sample  {highcharts} highcharts/css/axis/
                     *          Styled mode
                     * @default { "color": "#666666" }
                     */
                    style: {
                        color: '#666666'
                    }

                },

                /**
                 * The type of axis. Can be one of `linear`, `logarithmic`, `datetime`
                 * or `category`. In a datetime axis, the numbers are given in
                 * milliseconds, and tick marks are placed on appropriate values like
                 * full hours or days. In a category axis, the 
                 * [point names](#series.line.data.name) of the chart's series are used
                 * for categories, if not a [categories](#xAxis.categories) array is
                 * defined.
                 * 
                 * @validvalue ["linear", "logarithmic", "datetime", "category"]
                 * @sample     {highcharts} highcharts/xaxis/type-linear/
                 *             Linear
                 * @sample     {highcharts} highcharts/yaxis/type-log/
                 *             Logarithmic
                 * @sample     {highcharts} highcharts/yaxis/type-log-minorgrid/
                 *             Logarithmic with minor grid lines
                 * @sample     {highcharts} highcharts/xaxis/type-log-both/
                 *             Logarithmic on two axes
                 * @sample     {highcharts} highcharts/yaxis/type-log-negative/
                 *             Logarithmic with extension to emulate negative values
                 * @product    highcharts
                 */
                type: 'linear',



                /**
                 * Color of the minor, secondary grid lines.
                 * 
                 * In styled mode, the stroke width is given in the
                 * `.highcharts-minor-grid-line` class.
                 * 
                 * @type    {Color}
                 * @sample  {highcharts} highcharts/yaxis/minorgridlinecolor/
                 *          Bright grey lines from Y axis
                 * @sample  {highcharts|highstock} highcharts/css/axis-grid/
                 *          Styled mode
                 * @sample  {highstock} stock/xaxis/minorgridlinecolor/
                 *          Bright grey lines from Y axis
                 * @default #f2f2f2
                 */
                minorGridLineColor: '#f2f2f2',
                // minorGridLineDashStyle: null,

                /**
                 * Width of the minor, secondary grid lines.
                 * 
                 * In styled mode, the stroke width is given in the
                 * `.highcharts-grid-line` class.
                 * 
                 * @sample {highcharts} highcharts/yaxis/minorgridlinewidth/
                 *         2px lines from Y axis
                 * @sample {highcharts|highstock} highcharts/css/axis-grid/
                 *         Styled mode
                 * @sample {highstock} stock/xaxis/minorgridlinewidth/
                 *         2px lines from Y axis
                 */
                minorGridLineWidth: 1,

                /**
                 * Color for the minor tick marks.
                 * 
                 * @type    {Color}
                 * @sample  {highcharts} highcharts/yaxis/minortickcolor/
                 *          Black tick marks on Y axis
                 * @sample  {highstock} stock/xaxis/minorticks/
                 *          Black tick marks on Y axis
                 * @default #999999
                 */
                minorTickColor: '#999999',

                /**
                 * The color of the line marking the axis itself.
                 * 
                 * In styled mode, the line stroke is given in the
                 * `.highcharts-axis-line` or `.highcharts-xaxis-line` class.
                 * 
                 * @productdesc {highmaps}
                 * In Highmaps, the axis line is hidden by default, because the axis is
                 * not visible by default.
                 * 
                 * @type    {Color}
                 * @sample  {highcharts} highcharts/yaxis/linecolor/
                 *          A red line on Y axis
                 * @sample  {highcharts|highstock} highcharts/css/axis/
                 *          Axes in styled mode
                 * @sample  {highstock} stock/xaxis/linecolor/
                 *          A red line on X axis
                 * @default #ccd6eb
                 */
                lineColor: '#ccd6eb',

                /**
                 * The width of the line marking the axis itself.
                 * 
                 * In styled mode, the stroke width is given in the
                 * `.highcharts-axis-line` or `.highcharts-xaxis-line` class.
                 * 
                 * @sample  {highcharts} highcharts/yaxis/linecolor/
                 *          A 1px line on Y axis
                 * @sample  {highcharts|highstock} highcharts/css/axis/
                 *          Axes in styled mode
                 * @sample  {highstock} stock/xaxis/linewidth/
                 *          A 2px line on X axis
                 * @default {highcharts|highstock} 1
                 * @default {highmaps} 0
                 */
                lineWidth: 1,

                /**
                 * Color of the grid lines extending the ticks across the plot area.
                 * 
                 * In styled mode, the stroke is given in the `.highcharts-grid-line`
                 * class.
                 *
                 * @productdesc {highmaps}
                 * In Highmaps, the grid lines are hidden by default.
                 * 
                 * @type    {Color}
                 * @sample  {highcharts} highcharts/yaxis/gridlinecolor/
                 *          Green lines
                 * @sample  {highcharts|highstock} highcharts/css/axis-grid/
                 *          Styled mode
                 * @sample  {highstock} stock/xaxis/gridlinecolor/
                 *          Green lines
                 * @default #e6e6e6
                 */
                gridLineColor: '#e6e6e6',
                // gridLineDashStyle: 'solid',


                /**
                 * The width of the grid lines extending the ticks across the plot area.
                 *
                 * In styled mode, the stroke width is given in the
                 * `.highcharts-grid-line` class.
                 *
                 * @type      {Number}
                 * @sample    {highcharts} highcharts/yaxis/gridlinewidth/
                 *            2px lines
                 * @sample    {highcharts|highstock} highcharts/css/axis-grid/
                 *            Styled mode
                 * @sample    {highstock} stock/xaxis/gridlinewidth/
                 *            2px lines
                 * @default   0
                 * @apioption xAxis.gridLineWidth
                 */
                // gridLineWidth: 0,

                /**
                 * Color for the main tick marks.
                 * 
                 * In styled mode, the stroke is given in the `.highcharts-tick`
                 * class.
                 * 
                 * @type    {Color}
                 * @sample  {highcharts} highcharts/xaxis/tickcolor/
                 *          Red ticks on X axis
                 * @sample  {highcharts|highstock} highcharts/css/axis-grid/
                 *          Styled mode
                 * @sample  {highstock} stock/xaxis/ticks/
                 *          Formatted ticks on X axis
                 * @default #ccd6eb
                 */
                tickColor: '#ccd6eb'
                // tickWidth: 1

            },

            /**
             * The Y axis or value axis. Normally this is the vertical axis,
             * though if the chart is inverted this is the horizontal axis.
             * In case of multiple axes, the yAxis node is an array of
             * configuration objects.
             *
             * See [the Axis object](#Axis) for programmatic access to the axis.
             *
             * @extends      xAxis
             * @excluding    ordinal,overscroll
             * @optionparent yAxis
             */
            defaultYAxisOptions: {
                /**
                 * @productdesc {highstock}
                 * In Highstock, `endOnTick` is always false when the navigator is
                 * enabled, to prevent jumpy scrolling.
                 */
                endOnTick: true,

                /**
                 * @productdesc {highstock}
                 * In Highstock 1.x, the Y axis was placed on the left side by default.
                 *
                 * @sample    {highcharts} highcharts/yaxis/opposite/
                 *            Secondary Y axis opposite
                 * @sample    {highstock} stock/xaxis/opposite/
                 *            Y axis on left side
                 * @default   {highstock} true
                 * @default   {highcharts} false
                 * @product   highstock highcharts
                 * @apioption yAxis.opposite
                 */

                /**
                 * @see [tickInterval](#xAxis.tickInterval),
                 *      [tickPositioner](#xAxis.tickPositioner),
                 *      [tickPositions](#xAxis.tickPositions).
                 */
                tickPixelInterval: 72,

                showLastLabel: true,

                /**
                 * @extends xAxis.labels
                 */
                labels: {
                    /**
                     * What part of the string the given position is anchored to. Can
                     * be one of `"left"`, `"center"` or `"right"`. The exact position
                     * also depends on the `labels.x` setting.
                     *
                     * Angular gauges and solid gauges defaults to `center`.
                     *
                     * @validvalue ["left", "center", "right"]
                     * @type       {String}
                     * @sample     {highcharts} highcharts/yaxis/labels-align-left/
                     *             Left
                     * @default    {highcharts|highmaps} right
                     * @default    {highstock} left
                     * @apioption  yAxis.labels.align
                     */

                    /**
                     * The x position offset of the label relative to the tick position
                     * on the axis. Defaults to -15 for left axis, 15 for right axis.
                     * 
                     * @sample {highcharts} highcharts/xaxis/labels-x/
                     *         Y axis labels placed on grid lines
                     */
                    x: -8
                },

                /**
                 * @productdesc {highmaps}
                 * In Highmaps, the axis line is hidden by default, because the axis is
                 * not visible by default.
                 * 
                 * @apioption yAxis.lineColor
                 */

                /**
                 * @sample    {highcharts} highcharts/yaxis/min-startontick-false/
                 *            -50 with startOnTick to false
                 * @sample    {highcharts} highcharts/yaxis/min-startontick-true/
                 *            -50 with startOnTick true by default
                 * @sample    {highstock} stock/yaxis/min-max/
                 *            Fixed min and max on Y axis
                 * @sample    {highmaps} maps/axis/min-max/
                 *            Pre-zoomed to a specific area
                 * @apioption yAxis.min
                 */

                /**
                 * @sample    {highcharts} highcharts/yaxis/max-200/
                 *            Y axis max of 200
                 * @sample    {highcharts} highcharts/yaxis/max-logarithmic/
                 *            Y axis max on logarithmic axis
                 * @sample    {highstock} stock/yaxis/min-max/
                 *            Fixed min and max on Y axis
                 * @sample    {highmaps} maps/axis/min-max/
                 *            Pre-zoomed to a specific area
                 * @apioption yAxis.max
                 */

                /**
                 * Padding of the max value relative to the length of the axis. A
                 * padding of 0.05 will make a 100px axis 5px longer. This is useful
                 * when you don't want the highest data value to appear on the edge
                 * of the plot area. When the axis' `max` option is set or a max extreme
                 * is set using `axis.setExtremes()`, the maxPadding will be ignored.
                 * 
                 * @sample  {highcharts} highcharts/yaxis/maxpadding-02/
                 *          Max padding of 0.2
                 * @sample  {highstock} stock/xaxis/minpadding-maxpadding/
                 *          Greater min- and maxPadding
                 * @since   1.2.0
                 * @product highcharts highstock
                 */
                maxPadding: 0.05,

                /**
                 * Padding of the min value relative to the length of the axis. A
                 * padding of 0.05 will make a 100px axis 5px longer. This is useful
                 * when you don't want the lowest data value to appear on the edge
                 * of the plot area. When the axis' `min` option is set or a max extreme
                 * is set using `axis.setExtremes()`, the maxPadding will be ignored.
                 * 
                 * @sample  {highcharts} highcharts/yaxis/minpadding/
                 *          Min padding of 0.2
                 * @sample  {highstock} stock/xaxis/minpadding-maxpadding/
                 *          Greater min- and maxPadding
                 * @since   1.2.0
                 * @product highcharts highstock
                 */
                minPadding: 0.05,

                /**
                 * Whether to force the axis to start on a tick. Use this option with
                 * the `maxPadding` option to control the axis start.
                 * 
                 * @sample  {highcharts} highcharts/xaxis/startontick-false/
                 *          False by default
                 * @sample  {highcharts} highcharts/xaxis/startontick-true/
                 *          True
                 * @sample  {highstock} stock/xaxis/endontick/
                 *          False for Y axis
                 * @since   1.2.0
                 * @product highcharts highstock
                 */
                startOnTick: true,

                /**
                 * @extends xAxis.title
                 */
                title: {

                    /**
                     * The rotation of the text in degrees. 0 is horizontal, 270 is
                     * vertical reading from bottom to top.
                     * 
                     * @sample {highcharts} highcharts/yaxis/title-offset/
                     *         Horizontal
                     */
                    rotation: 270,

                    /**
                     * The actual text of the axis title. Horizontal texts can contain
                     * HTML, but rotated texts are painted using vector techniques and
                     * must be clean text. The Y axis title is disabled by setting the
                     * `text` option to `null`.
                     * 
                     * @sample  {highcharts} highcharts/xaxis/title-text/
                     *          Custom HTML
                     * @default {highcharts} Values
                     * @default {highstock} null
                     * @product highcharts highstock
                     */
                    text: 'Values'
                },

                /**
                 * The stack labels show the total value for each bar in a stacked
                 * column or bar chart. The label will be placed on top of positive
                 * columns and below negative columns. In case of an inverted column
                 * chart or a bar chart the label is placed to the right of positive
                 * bars and to the left of negative bars.
                 * 
                 * @product highcharts
                 */
                stackLabels: {

                    /**
                     * Allow the stack labels to overlap.
                     * 
                     * @sample  {highcharts}
                     *          highcharts/yaxis/stacklabels-allowoverlap-false/
                     *          Default false
                     * @since   5.0.13
                     * @product highcharts
                     */
                    allowOverlap: false,

                    /**
                     * Enable or disable the stack total labels.
                     * 
                     * @sample  {highcharts} highcharts/yaxis/stacklabels-enabled/
                     *          Enabled stack total labels
                     * @since   2.1.5
                     * @product highcharts
                     */
                    enabled: false,

                    /**
                     * Callback JavaScript function to format the label. The value is
                     * given by `this.total`.
                     *
                     * @default function() { return this.total; }
                     * 
                     * @type    {Function}
                     * @sample  {highcharts} highcharts/yaxis/stacklabels-formatter/
                     *          Added units to stack total value
                     * @since   2.1.5
                     * @product highcharts
                     */
                    formatter: function() {
                        return H.numberFormat(this.total, -1);
                    },


                    /**
                     * CSS styles for the label.
                     * 
                     * In styled mode, the styles are set in the
                     * `.highcharts-stack-label` class.
                     * 
                     * @type    {CSSObject}
                     * @sample  {highcharts} highcharts/yaxis/stacklabels-style/
                     *          Red stack total labels
                     * @since   2.1.5
                     * @product highcharts
                     */
                    style: {
                        fontSize: '11px',
                        fontWeight: 'bold',
                        color: '#000000',
                        textOutline: '1px contrast'
                    }

                },

                gridLineWidth: 1,
                lineWidth: 0
                // tickWidth: 0

            },

            /**
             * These options extend the defaultOptions for left axes.
             * 
             * @private
             * @type {Object}
             */
            defaultLeftAxisOptions: {
                labels: {
                    x: -15
                },
                title: {
                    rotation: 270
                }
            },

            /**
             * These options extend the defaultOptions for right axes.
             *
             * @private
             * @type {Object}
             */
            defaultRightAxisOptions: {
                labels: {
                    x: 15
                },
                title: {
                    rotation: 90
                }
            },

            /**
             * These options extend the defaultOptions for bottom axes.
             *
             * @private
             * @type {Object}
             */
            defaultBottomAxisOptions: {
                labels: {
                    autoRotation: [-45],
                    x: 0
                    // overflow: undefined,
                    // staggerLines: null
                },
                title: {
                    rotation: 0
                }
            },
            /**
             * These options extend the defaultOptions for top axes.
             *
             * @private
             * @type {Object}
             */
            defaultTopAxisOptions: {
                labels: {
                    autoRotation: [-45],
                    x: 0
                    // overflow: undefined
                    // staggerLines: null
                },
                title: {
                    rotation: 0
                }
            },

            /**
             * Overrideable function to initialize the axis. 
             *
             * @see {@link Axis}
             */
            init: function(chart, userOptions) {


                var isXAxis = userOptions.isX,
                    axis = this;


                /**
                 * The Chart that the axis belongs to.
                 *
                 * @name     chart
                 * @memberOf Axis
                 * @type     {Chart}
                 */
                axis.chart = chart;

                /**
                 * Whether the axis is horizontal.
                 *
                 * @name     horiz
                 * @memberOf Axis
                 * @type     {Boolean}
                 */
                axis.horiz = chart.inverted && !axis.isZAxis ? !isXAxis : isXAxis;

                // Flag, isXAxis
                axis.isXAxis = isXAxis;

                /**
                 * The collection where the axis belongs, for example `xAxis`, `yAxis`
                 * or `colorAxis`. Corresponds to properties on Chart, for example
                 * {@link Chart.xAxis}.
                 *
                 * @name     coll
                 * @memberOf Axis
                 * @type     {String}
                 */
                axis.coll = axis.coll || (isXAxis ? 'xAxis' : 'yAxis');


                axis.opposite = userOptions.opposite; // needed in setOptions

                /**
                 * The side on which the axis is rendered. 0 is top, 1 is right, 2 is
                 * bottom and 3 is left.
                 *
                 * @name     side
                 * @memberOf Axis
                 * @type     {Number}
                 */
                axis.side = userOptions.side || (axis.horiz ?
                    (axis.opposite ? 0 : 2) : // top : bottom
                    (axis.opposite ? 1 : 3)); // right : left

                axis.setOptions(userOptions);


                var options = this.options,
                    type = options.type,
                    isDatetimeAxis = type === 'datetime';

                axis.labelFormatter = options.labels.formatter ||
                    axis.defaultLabelFormatter; // can be overwritten by dynamic format


                // Flag, stagger lines or not
                axis.userOptions = userOptions;

                axis.minPixelPadding = 0;


                /**
                 * Whether the axis is reversed. Based on the `axis.reversed`,
                 * option, but inverted charts have reversed xAxis by default.
                 *
                 * @name     reversed
                 * @memberOf Axis
                 * @type     {Boolean}
                 */
                axis.reversed = options.reversed;
                axis.visible = options.visible !== false;
                axis.zoomEnabled = options.zoomEnabled !== false;

                // Initial categories
                axis.hasNames = type === 'category' || options.categories === true;
                axis.categories = options.categories || axis.hasNames;
                axis.names = axis.names || []; // Preserve on update (#3830)

                // Placeholder for plotlines and plotbands groups
                axis.plotLinesAndBandsGroups = {};

                // Shorthand types
                axis.isLog = type === 'logarithmic';
                axis.isDatetimeAxis = isDatetimeAxis;
                axis.positiveValuesOnly = axis.isLog && !axis.allowNegativeLog;

                // Flag, if axis is linked to another axis
                axis.isLinked = defined(options.linkedTo);

                // Major ticks
                axis.ticks = {};
                axis.labelEdge = [];
                // Minor ticks
                axis.minorTicks = {};

                // List of plotLines/Bands
                axis.plotLinesAndBands = [];

                // Alternate bands
                axis.alternateBands = {};

                // Axis metrics
                axis.len = 0;
                axis.minRange = axis.userMinRange = options.minRange || options.maxZoom;
                axis.range = options.range;
                axis.offset = options.offset || 0;


                // Dictionary for stacks
                axis.stacks = {};
                axis.oldStacks = {};
                axis.stacksTouched = 0;


                /**
                 * The maximum value of the axis. In a logarithmic axis, this is the
                 * logarithm of the real value, and the real value can be obtained from
                 * {@link Axis#getExtremes}.
                 *
                 * @name     max
                 * @memberOf Axis
                 * @type     {Number}
                 */
                axis.max = null;
                /**
                 * The minimum value of the axis. In a logarithmic axis, this is the
                 * logarithm of the real value, and the real value can be obtained from
                 * {@link Axis#getExtremes}.
                 *
                 * @name     min
                 * @memberOf Axis
                 * @type     {Number}
                 */
                axis.min = null;


                /**
                 * The processed crosshair options.
                 *
                 * @name     crosshair
                 * @memberOf Axis
                 * @type     {AxisCrosshairOptions}
                 */
                axis.crosshair = pick(
                    options.crosshair,
                    splat(chart.options.tooltip.crosshairs)[isXAxis ? 0 : 1],
                    false
                );

                var events = axis.options.events;

                // Register. Don't add it again on Axis.update().
                if (inArray(axis, chart.axes) === -1) { // 
                    if (isXAxis) { // #2713
                        chart.axes.splice(chart.xAxis.length, 0, axis);
                    } else {
                        chart.axes.push(axis);
                    }

                    chart[axis.coll].push(axis);
                }

                /**
                 * All series associated to the axis.
                 *
                 * @name     series
                 * @memberOf Axis
                 * @type     {Array.<Series>}
                 */
                axis.series = axis.series || []; // populated by Series

                // Reversed axis
                if (
                    chart.inverted &&
                    !axis.isZAxis &&
                    isXAxis &&
                    axis.reversed === undefined
                ) {
                    axis.reversed = true;
                }

                // register event listeners
                objectEach(events, function(event, eventType) {
                    addEvent(axis, eventType, event);
                });

                // extend logarithmic axis
                axis.lin2log = options.linearToLogConverter || axis.lin2log;
                if (axis.isLog) {
                    axis.val2lin = axis.log2lin;
                    axis.lin2val = axis.lin2log;
                }
            },

            /**
             * Merge and set options.
             *
             * @private
             */
            setOptions: function(userOptions) {
                this.options = merge(
                    this.defaultOptions,
                    this.coll === 'yAxis' && this.defaultYAxisOptions, [
                        this.defaultTopAxisOptions,
                        this.defaultRightAxisOptions,
                        this.defaultBottomAxisOptions,
                        this.defaultLeftAxisOptions
                    ][this.side],
                    merge(
                        defaultOptions[this.coll], // if set in setOptions (#1053)
                        userOptions
                    )
                );
            },

            /**
             * The default label formatter. The context is a special config object for
             * the label. In apps, use the {@link
             * https://api.highcharts.com/highcharts/xAxis.labels.formatter|
             * labels.formatter} instead except when a modification is needed.
             *
             * @private
             */
            defaultLabelFormatter: function() {
                var axis = this.axis,
                    value = this.value,
                    categories = axis.categories,
                    dateTimeLabelFormat = this.dateTimeLabelFormat,
                    lang = defaultOptions.lang,
                    numericSymbols = lang.numericSymbols,
                    numSymMagnitude = lang.numericSymbolMagnitude || 1000,
                    i = numericSymbols && numericSymbols.length,
                    multi,
                    ret,
                    formatOption = axis.options.labels.format,

                    // make sure the same symbol is added for all labels on a linear
                    // axis
                    numericSymbolDetector = axis.isLog ?
                    Math.abs(value) :
                    axis.tickInterval;

                if (formatOption) {
                    ret = format(formatOption, this);

                } else if (categories) {
                    ret = value;

                } else if (dateTimeLabelFormat) { // datetime axis
                    ret = H.dateFormat(dateTimeLabelFormat, value);

                } else if (i && numericSymbolDetector >= 1000) {
                    // Decide whether we should add a numeric symbol like k (thousands)
                    // or M (millions). If we are to enable this in tooltip or other
                    // places as well, we can move this logic to the numberFormatter and
                    // enable it by a parameter.
                    while (i-- && ret === undefined) {
                        multi = Math.pow(numSymMagnitude, i + 1);
                        if (
                            // Only accept a numeric symbol when the distance is more 
                            // than a full unit. So for example if the symbol is k, we
                            // don't accept numbers like 0.5k.
                            numericSymbolDetector >= multi &&
                            // Accept one decimal before the symbol. Accepts 0.5k but
                            // not 0.25k. How does this work with the previous?
                            (value * 10) % multi === 0 &&
                            numericSymbols[i] !== null &&
                            value !== 0
                        ) { // #5480
                            ret = H.numberFormat(value / multi, -1) + numericSymbols[i];
                        }
                    }
                }

                if (ret === undefined) {
                    if (Math.abs(value) >= 10000) { // add thousands separators
                        ret = H.numberFormat(value, -1);
                    } else { // small numbers
                        ret = H.numberFormat(value, -1, undefined, ''); // #2466
                    }
                }

                return ret;
            },

            /**
             * Get the minimum and maximum for the series of each axis. The function
             * analyzes the axis series and updates `this.dataMin` and `this.dataMax`.
             *
             * @private
             */
            getSeriesExtremes: function() {
                var axis = this,
                    chart = axis.chart;
                axis.hasVisibleSeries = false;

                // Reset properties in case we're redrawing (#3353)
                axis.dataMin = axis.dataMax = axis.threshold = null;
                axis.softThreshold = !axis.isXAxis;

                if (axis.buildStacks) {
                    axis.buildStacks();
                }

                // loop through this axis' series
                each(axis.series, function(series) {

                    if (series.visible || !chart.options.chart.ignoreHiddenSeries) {

                        var seriesOptions = series.options,
                            xData,
                            threshold = seriesOptions.threshold,
                            seriesDataMin,
                            seriesDataMax;

                        axis.hasVisibleSeries = true;

                        // Validate threshold in logarithmic axes
                        if (axis.positiveValuesOnly && threshold <= 0) {
                            threshold = null;
                        }

                        // Get dataMin and dataMax for X axes
                        if (axis.isXAxis) {
                            xData = series.xData;
                            if (xData.length) {
                                // If xData contains values which is not numbers, then
                                // filter them out. To prevent performance hit, we only
                                // do this after we have already found seriesDataMin
                                // because in most cases all data is valid. #5234.
                                seriesDataMin = arrayMin(xData);
                                seriesDataMax = arrayMax(xData);

                                if (!isNumber(seriesDataMin) &&
                                    !(seriesDataMin instanceof Date) // #5010
                                ) {
                                    xData = grep(xData, isNumber);
                                    // Do it again with valid data
                                    seriesDataMin = arrayMin(xData);
                                }

                                axis.dataMin = Math.min(
                                    pick(axis.dataMin, xData[0], seriesDataMin),
                                    seriesDataMin
                                );
                                axis.dataMax = Math.max(
                                    pick(axis.dataMax, xData[0], seriesDataMax),
                                    seriesDataMax
                                );
                            }

                            // Get dataMin and dataMax for Y axes, as well as handle
                            // stacking and processed data
                        } else {

                            // Get this particular series extremes
                            series.getExtremes();
                            seriesDataMax = series.dataMax;
                            seriesDataMin = series.dataMin;

                            // Get the dataMin and dataMax so far. If percentage is
                            // used, the min and max are always 0 and 100. If
                            // seriesDataMin and seriesDataMax is null, then series
                            // doesn't have active y data, we continue with nulls
                            if (defined(seriesDataMin) && defined(seriesDataMax)) {
                                axis.dataMin = Math.min(
                                    pick(axis.dataMin, seriesDataMin),
                                    seriesDataMin
                                );
                                axis.dataMax = Math.max(
                                    pick(axis.dataMax, seriesDataMax),
                                    seriesDataMax
                                );
                            }

                            // Adjust to threshold
                            if (defined(threshold)) {
                                axis.threshold = threshold;
                            }
                            // If any series has a hard threshold, it takes precedence
                            if (!seriesOptions.softThreshold ||
                                axis.positiveValuesOnly
                            ) {
                                axis.softThreshold = false;
                            }
                        }
                    }
                });
            },

            /**
             * Translate from axis value to pixel position on the chart, or back. Use
             * the `toPixels` and `toValue` functions in applications.
             *
             * @private
             */
            translate: function(
                val,
                backwards,
                cvsCoord,
                old,
                handleLog,
                pointPlacement
            ) {
                var axis = this.linkedParent || this, // #1417
                    sign = 1,
                    cvsOffset = 0,
                    localA = old ? axis.oldTransA : axis.transA,
                    localMin = old ? axis.oldMin : axis.min,
                    returnValue,
                    minPixelPadding = axis.minPixelPadding,
                    doPostTranslate = (
                        axis.isOrdinal ||
                        axis.isBroken ||
                        (axis.isLog && handleLog)
                    ) && axis.lin2val;

                if (!localA) {
                    localA = axis.transA;
                }

                // In vertical axes, the canvas coordinates start from 0 at the top like
                // in SVG.
                if (cvsCoord) {
                    sign *= -1; // canvas coordinates inverts the value
                    cvsOffset = axis.len;
                }

                // Handle reversed axis
                if (axis.reversed) {
                    sign *= -1;
                    cvsOffset -= sign * (axis.sector || axis.len);
                }

                // From pixels to value
                if (backwards) { // reverse translation

                    val = val * sign + cvsOffset;
                    val -= minPixelPadding;
                    returnValue = val / localA + localMin; // from chart pixel to value
                    if (doPostTranslate) { // log and ordinal axes
                        returnValue = axis.lin2val(returnValue);
                    }

                    // From value to pixels
                } else {
                    if (doPostTranslate) { // log and ordinal axes
                        val = axis.val2lin(val);
                    }
                    returnValue = isNumber(localMin) ?
                        (
                            sign * (val - localMin) * localA +
                            cvsOffset +
                            (sign * minPixelPadding) +
                            (isNumber(pointPlacement) ? localA * pointPlacement : 0)
                        ) :
                        undefined;
                }

                return returnValue;
            },

            /**
             * Translate a value in terms of axis units into pixels within the chart.
             * 
             * @param  {Number} value
             *         A value in terms of axis units.
             * @param  {Boolean} paneCoordinates
             *         Whether to return the pixel coordinate relative to the chart or
             *         just the axis/pane itself.
             * @return {Number} Pixel position of the value on the chart or axis.
             */
            toPixels: function(value, paneCoordinates) {
                return this.translate(value, false, !this.horiz, null, true) +
                    (paneCoordinates ? 0 : this.pos);
            },

            /**
             * Translate a pixel position along the axis to a value in terms of axis
             * units.
             * @param  {Number} pixel
             *         The pixel value coordinate.
             * @param  {Boolean} paneCoordiantes
             *         Whether the input pixel is relative to the chart or just the
             *         axis/pane itself.
             * @return {Number} The axis value.
             */
            toValue: function(pixel, paneCoordinates) {
                return this.translate(
                    pixel - (paneCoordinates ? 0 : this.pos),
                    true, !this.horiz,
                    null,
                    true
                );
            },

            /**
             * Create the path for a plot line that goes from the given value on
             * this axis, across the plot to the opposite side. Also used internally for
             * grid lines and crosshairs.
             * 
             * @param  {Number} value
             *         Axis value.
             * @param  {Number} [lineWidth=1]
             *         Used for calculation crisp line coordinates.
             * @param  {Boolean} [old=false]
             *         Use old coordinates (for resizing and rescaling).
             * @param  {Boolean} [force=false]
             *         If `false`, the function will return null when it falls outside
             *         the axis bounds.
             * @param  {Number} [translatedValue]
             *         If given, return the plot line path of a pixel position on the
             *         axis.
             *
             * @return {Array.<String|Number>}
             *         The SVG path definition for the plot line.
             */
            getPlotLinePath: function(value, lineWidth, old, force, translatedValue) {
                var axis = this,
                    chart = axis.chart,
                    axisLeft = axis.left,
                    axisTop = axis.top,
                    x1,
                    y1,
                    x2,
                    y2,
                    cHeight = (old && chart.oldChartHeight) || chart.chartHeight,
                    cWidth = (old && chart.oldChartWidth) || chart.chartWidth,
                    skip,
                    transB = axis.transB,
                    /**
                     * Check if x is between a and b. If not, either move to a/b
                     * or skip, depending on the force parameter.
                     */
                    between = function(x, a, b) {
                        if (x < a || x > b) {
                            if (force) {
                                x = Math.min(Math.max(a, x), b);
                            } else {
                                skip = true;
                            }
                        }
                        return x;
                    };

                translatedValue = pick(
                    translatedValue,
                    axis.translate(value, null, null, old)
                );
                x1 = x2 = Math.round(translatedValue + transB);
                y1 = y2 = Math.round(cHeight - translatedValue - transB);
                if (!isNumber(translatedValue)) { // no min or max
                    skip = true;
                    force = false; // #7175, don't force it when path is invalid
                } else if (axis.horiz) {
                    y1 = axisTop;
                    y2 = cHeight - axis.bottom;
                    x1 = x2 = between(x1, axisLeft, axisLeft + axis.width);
                } else {
                    x1 = axisLeft;
                    x2 = cWidth - axis.right;
                    y1 = y2 = between(y1, axisTop, axisTop + axis.height);
                }
                return skip && !force ?
                    null :
                    chart.renderer.crispLine(
                        ['M', x1, y1, 'L', x2, y2],
                        lineWidth || 1
                    );
            },

            /**
             * Internal function to et the tick positions of a linear axis to round
             * values like whole tens or every five.
             *
             * @param  {Number} tickInterval
             *         The normalized tick interval
             * @param  {Number} min
             *         Axis minimum.
             * @param  {Number} max
             *         Axis maximum.
             *
             * @return {Array.<Number>}
             *         An array of axis values where ticks should be placed.
             */
            getLinearTickPositions: function(tickInterval, min, max) {
                var pos,
                    lastPos,
                    roundedMin =
                    correctFloat(Math.floor(min / tickInterval) * tickInterval),
                    roundedMax =
                    correctFloat(Math.ceil(max / tickInterval) * tickInterval),
                    tickPositions = [],
                    precision;

                // When the precision is higher than what we filter out in
                // correctFloat, skip it (#6183).			
                if (correctFloat(roundedMin + tickInterval) === roundedMin) {
                    precision = 20;
                }

                // For single points, add a tick regardless of the relative position
                // (#2662, #6274)
                if (this.single) {
                    return [min];
                }

                // Populate the intermediate values
                pos = roundedMin;
                while (pos <= roundedMax) {

                    // Place the tick on the rounded value
                    tickPositions.push(pos);

                    // Always add the raw tickInterval, not the corrected one.
                    pos = correctFloat(
                        pos + tickInterval,
                        precision
                    );

                    // If the interval is not big enough in the current min - max range
                    // to actually increase the loop variable, we need to break out to
                    // prevent endless loop. Issue #619
                    if (pos === lastPos) {
                        break;
                    }

                    // Record the last value
                    lastPos = pos;
                }
                return tickPositions;
            },

            /**
             * Resolve the new minorTicks/minorTickInterval options into the legacy
             * loosely typed minorTickInterval option.
             */
            getMinorTickInterval: function() {
                var options = this.options;

                if (options.minorTicks === true) {
                    return pick(options.minorTickInterval, 'auto');
                }
                if (options.minorTicks === false) {
                    return null;
                }
                return options.minorTickInterval;
            },

            /**
             * Internal function to return the minor tick positions. For logarithmic
             * axes, the same logic as for major ticks is reused.
             *
             * @return {Array.<Number>}
             *         An array of axis values where ticks should be placed.
             */
            getMinorTickPositions: function() {
                var axis = this,
                    options = axis.options,
                    tickPositions = axis.tickPositions,
                    minorTickInterval = axis.minorTickInterval,
                    minorTickPositions = [],
                    pos,
                    pointRangePadding = axis.pointRangePadding || 0,
                    min = axis.min - pointRangePadding, // #1498
                    max = axis.max + pointRangePadding, // #1498
                    range = max - min;

                // If minor ticks get too dense, they are hard to read, and may cause
                // long running script. So we don't draw them.
                if (range && range / minorTickInterval < axis.len / 3) { // #3875

                    if (axis.isLog) {
                        // For each interval in the major ticks, compute the minor ticks
                        // separately.
                        each(this.paddedTicks, function(pos, i, paddedTicks) {
                            if (i) {
                                minorTickPositions.push.apply(
                                    minorTickPositions,
                                    axis.getLogTickPositions(
                                        minorTickInterval,
                                        paddedTicks[i - 1],
                                        paddedTicks[i],
                                        true
                                    )
                                );
                            }
                        });

                    } else if (
                        axis.isDatetimeAxis &&
                        this.getMinorTickInterval() === 'auto'
                    ) { // #1314
                        minorTickPositions = minorTickPositions.concat(
                            axis.getTimeTicks(
                                axis.normalizeTimeTickInterval(minorTickInterval),
                                min,
                                max,
                                options.startOfWeek
                            )
                        );
                    } else {
                        for (
                            pos = min + (tickPositions[0] - min) % minorTickInterval; pos <= max; pos += minorTickInterval
                        ) {
                            // Very, very, tight grid lines (#5771)
                            if (pos === minorTickPositions[0]) {
                                break;
                            }
                            minorTickPositions.push(pos);
                        }
                    }
                }

                if (minorTickPositions.length !== 0) {
                    axis.trimTicks(minorTickPositions); // #3652 #3743 #1498 #6330
                }
                return minorTickPositions;
            },

            /**
             * Adjust the min and max for the minimum range. Keep in mind that the
             * series data is not yet processed, so we don't have information on data
             * cropping and grouping, or updated axis.pointRange or series.pointRange.
             * The data can't be processed until we have finally established min and
             * max.
             *
             * @private
             */
            adjustForMinRange: function() {
                var axis = this,
                    options = axis.options,
                    min = axis.min,
                    max = axis.max,
                    zoomOffset,
                    spaceAvailable,
                    closestDataRange,
                    i,
                    distance,
                    xData,
                    loopLength,
                    minArgs,
                    maxArgs,
                    minRange;

                // Set the automatic minimum range based on the closest point distance
                if (axis.isXAxis && axis.minRange === undefined && !axis.isLog) {

                    if (defined(options.min) || defined(options.max)) {
                        axis.minRange = null; // don't do this again

                    } else {

                        // Find the closest distance between raw data points, as opposed
                        // to closestPointRange that applies to processed points
                        // (cropped and grouped)
                        each(axis.series, function(series) {
                            xData = series.xData;
                            loopLength = series.xIncrement ? 1 : xData.length - 1;
                            for (i = loopLength; i > 0; i--) {
                                distance = xData[i] - xData[i - 1];
                                if (
                                    closestDataRange === undefined ||
                                    distance < closestDataRange
                                ) {
                                    closestDataRange = distance;
                                }
                            }
                        });
                        axis.minRange = Math.min(
                            closestDataRange * 5,
                            axis.dataMax - axis.dataMin
                        );
                    }
                }

                // if minRange is exceeded, adjust
                if (max - min < axis.minRange) {

                    spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange;
                    minRange = axis.minRange;
                    zoomOffset = (minRange - max + min) / 2;

                    // if min and max options have been set, don't go beyond it
                    minArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)];
                    // If space is available, stay within the data range
                    if (spaceAvailable) {
                        minArgs[2] = axis.isLog ?
                            axis.log2lin(axis.dataMin) :
                            axis.dataMin;
                    }
                    min = arrayMax(minArgs);

                    maxArgs = [min + minRange, pick(options.max, min + minRange)];
                    // If space is availabe, stay within the data range
                    if (spaceAvailable) {
                        maxArgs[2] = axis.isLog ?
                            axis.log2lin(axis.dataMax) :
                            axis.dataMax;
                    }

                    max = arrayMin(maxArgs);

                    // now if the max is adjusted, adjust the min back
                    if (max - min < minRange) {
                        minArgs[0] = max - minRange;
                        minArgs[1] = pick(options.min, max - minRange);
                        min = arrayMax(minArgs);
                    }
                }

                // Record modified extremes
                axis.min = min;
                axis.max = max;
            },

            /**
             * Find the closestPointRange across all series.
             *
             * @private
             */
            getClosest: function() {
                var ret;

                if (this.categories) {
                    ret = 1;
                } else {
                    each(this.series, function(series) {
                        var seriesClosest = series.closestPointRange,
                            visible = series.visible ||
                            !series.chart.options.chart.ignoreHiddenSeries;

                        if (!series.noSharedTooltip &&
                            defined(seriesClosest) &&
                            visible
                        ) {
                            ret = defined(ret) ?
                                Math.min(ret, seriesClosest) :
                                seriesClosest;
                        }
                    });
                }
                return ret;
            },

            /**
             * When a point name is given and no x, search for the name in the existing
             * categories, or if categories aren't provided, search names or create a
             * new category (#2522).
             *
             * @private
             *
             * @param  {Point}
             *         The point to inspect.
             *
             * @return {Number}
             *         The X value that the point is given.
             */
            nameToX: function(point) {
                var explicitCategories = isArray(this.categories),
                    names = explicitCategories ? this.categories : this.names,
                    nameX = point.options.x,
                    x;

                point.series.requireSorting = false;

                if (!defined(nameX)) {
                    nameX = this.options.uniqueNames === false ?
                        point.series.autoIncrement() :
                        inArray(point.name, names);
                }
                if (nameX === -1) { // The name is not found in currenct categories
                    if (!explicitCategories) {
                        x = names.length;
                    }
                } else {
                    x = nameX;
                }

                // Write the last point's name to the names array
                if (x !== undefined) {
                    this.names[x] = point.name;
                }

                return x;
            },

            /**
             * When changes have been done to series data, update the axis.names.
             *
             * @private
             */
            updateNames: function() {
                var axis = this;

                if (this.names.length > 0) {
                    this.names.length = 0;
                    this.minRange = this.userMinRange; // Reset
                    each(this.series || [], function(series) {

                        // Reset incrementer (#5928)
                        series.xIncrement = null;

                        // When adding a series, points are not yet generated
                        if (!series.points || series.isDirtyData) {
                            series.processData();
                            series.generatePoints();
                        }

                        each(series.points, function(point, i) {
                            var x;
                            if (point.options) {
                                x = axis.nameToX(point);
                                if (x !== undefined && x !== point.x) {
                                    point.x = x;
                                    series.xData[i] = x;
                                }
                            }
                        });
                    });
                }
            },

            /**
             * Update translation information.
             *
             * @private
             */
            setAxisTranslation: function(saveOld) {
                var axis = this,
                    range = axis.max - axis.min,
                    pointRange = axis.axisPointRange || 0,
                    closestPointRange,
                    minPointOffset = 0,
                    pointRangePadding = 0,
                    linkedParent = axis.linkedParent,
                    ordinalCorrection,
                    hasCategories = !!axis.categories,
                    transA = axis.transA,
                    isXAxis = axis.isXAxis;

                // Adjust translation for padding. Y axis with categories need to go
                // through the same (#1784).
                if (isXAxis || hasCategories || pointRange) {

                    // Get the closest points
                    closestPointRange = axis.getClosest();

                    if (linkedParent) {
                        minPointOffset = linkedParent.minPointOffset;
                        pointRangePadding = linkedParent.pointRangePadding;
                    } else {
                        each(axis.series, function(series) {
                            var seriesPointRange = hasCategories ?
                                1 :
                                (
                                    isXAxis ?
                                    pick(
                                        series.options.pointRange,
                                        closestPointRange,
                                        0
                                    ) :
                                    (axis.axisPointRange || 0)
                                ), // #2806
                                pointPlacement = series.options.pointPlacement;

                            pointRange = Math.max(pointRange, seriesPointRange);

                            if (!axis.single) {
                                // minPointOffset is the value padding to the left of
                                // the axis in order to make room for points with a
                                // pointRange, typically columns. When the
                                // pointPlacement option is 'between' or 'on', this
                                // padding does not apply.
                                minPointOffset = Math.max(
                                    minPointOffset,
                                    isString(pointPlacement) ? 0 : seriesPointRange / 2
                                );

                                // Determine the total padding needed to the length of
                                // the axis to make room for the pointRange. If the
                                // series' pointPlacement is 'on', no padding is added.
                                pointRangePadding = Math.max(
                                    pointRangePadding,
                                    pointPlacement === 'on' ? 0 : seriesPointRange
                                );
                            }
                        });
                    }

                    // Record minPointOffset and pointRangePadding
                    ordinalCorrection = axis.ordinalSlope && closestPointRange ?
                        axis.ordinalSlope / closestPointRange :
                        1; // #988, #1853
                    axis.minPointOffset = minPointOffset =
                        minPointOffset * ordinalCorrection;
                    axis.pointRangePadding =
                        pointRangePadding = pointRangePadding * ordinalCorrection;

                    // pointRange means the width reserved for each point, like in a
                    // column chart
                    axis.pointRange = Math.min(pointRange, range);

                    // closestPointRange means the closest distance between points. In
                    // columns it is mostly equal to pointRange, but in lines pointRange
                    // is 0 while closestPointRange is some other value
                    if (isXAxis) {
                        axis.closestPointRange = closestPointRange;
                    }
                }

                // Secondary values
                if (saveOld) {
                    axis.oldTransA = transA;
                }
                axis.translationSlope = axis.transA = transA =
                    axis.options.staticScale ||
                    axis.len / ((range + pointRangePadding) || 1);

                // Translation addend
                axis.transB = axis.horiz ? axis.left : axis.bottom;
                axis.minPixelPadding = transA * minPointOffset;
            },

            minFromRange: function() {
                return this.max - this.range;
            },

            /**
             * Set the tick positions to round values and optionally extend the extremes
             * to the nearest tick.
             *
             * @private
             */
            setTickInterval: function(secondPass) {
                var axis = this,
                    chart = axis.chart,
                    options = axis.options,
                    isLog = axis.isLog,
                    log2lin = axis.log2lin,
                    isDatetimeAxis = axis.isDatetimeAxis,
                    isXAxis = axis.isXAxis,
                    isLinked = axis.isLinked,
                    maxPadding = options.maxPadding,
                    minPadding = options.minPadding,
                    length,
                    linkedParentExtremes,
                    tickIntervalOption = options.tickInterval,
                    minTickInterval,
                    tickPixelIntervalOption = options.tickPixelInterval,
                    categories = axis.categories,
                    threshold = axis.threshold,
                    softThreshold = axis.softThreshold,
                    thresholdMin,
                    thresholdMax,
                    hardMin,
                    hardMax;

                if (!isDatetimeAxis && !categories && !isLinked) {
                    this.getTickAmount();
                }

                // Min or max set either by zooming/setExtremes or initial options
                hardMin = pick(axis.userMin, options.min);
                hardMax = pick(axis.userMax, options.max);

                // Linked axis gets the extremes from the parent axis
                if (isLinked) {
                    axis.linkedParent = chart[axis.coll][options.linkedTo];
                    linkedParentExtremes = axis.linkedParent.getExtremes();
                    axis.min = pick(
                        linkedParentExtremes.min,
                        linkedParentExtremes.dataMin
                    );
                    axis.max = pick(
                        linkedParentExtremes.max,
                        linkedParentExtremes.dataMax
                    );
                    if (options.type !== axis.linkedParent.options.type) {
                        H.error(11, 1); // Can't link axes of different type
                    }

                    // Initial min and max from the extreme data values
                } else {

                    // Adjust to hard threshold
                    if (!softThreshold && defined(threshold)) {
                        if (axis.dataMin >= threshold) {
                            thresholdMin = threshold;
                            minPadding = 0;
                        } else if (axis.dataMax <= threshold) {
                            thresholdMax = threshold;
                            maxPadding = 0;
                        }
                    }

                    axis.min = pick(hardMin, thresholdMin, axis.dataMin);
                    axis.max = pick(hardMax, thresholdMax, axis.dataMax);

                }

                if (isLog) {
                    if (
                        axis.positiveValuesOnly &&
                        !secondPass &&
                        Math.min(axis.min, pick(axis.dataMin, axis.min)) <= 0
                    ) { // #978
                        H.error(10, 1); // Can't plot negative values on log axis
                    }
                    // The correctFloat cures #934, float errors on full tens. But it
                    // was too aggressive for #4360 because of conversion back to lin,
                    // therefore use precision 15.
                    axis.min = correctFloat(log2lin(axis.min), 15);
                    axis.max = correctFloat(log2lin(axis.max), 15);
                }

                // handle zoomed range
                if (axis.range && defined(axis.max)) {
                    axis.userMin = axis.min = hardMin =
                        Math.max(axis.dataMin, axis.minFromRange()); // #618, #6773
                    axis.userMax = hardMax = axis.max;

                    axis.range = null; // don't use it when running setExtremes
                }

                // Hook for Highstock Scroller. Consider combining with beforePadding.
                fireEvent(axis, 'foundExtremes');

                // Hook for adjusting this.min and this.max. Used by bubble series.
                if (axis.beforePadding) {
                    axis.beforePadding();
                }

                // adjust min and max for the minimum range
                axis.adjustForMinRange();

                // Pad the values to get clear of the chart's edges. To avoid
                // tickInterval taking the padding into account, we do this after
                // computing tick interval (#1337).
                if (!categories &&
                    !axis.axisPointRange &&
                    !axis.usePercentage &&
                    !isLinked &&
                    defined(axis.min) &&
                    defined(axis.max)
                ) {
                    length = axis.max - axis.min;
                    if (length) {
                        if (!defined(hardMin) && minPadding) {
                            axis.min -= length * minPadding;
                        }
                        if (!defined(hardMax) && maxPadding) {
                            axis.max += length * maxPadding;
                        }
                    }
                }

                // Handle options for floor, ceiling, softMin and softMax (#6359)
                if (isNumber(options.softMin)) {
                    axis.min = Math.min(axis.min, options.softMin);
                }
                if (isNumber(options.softMax)) {
                    axis.max = Math.max(axis.max, options.softMax);
                }
                if (isNumber(options.floor)) {
                    axis.min = Math.max(axis.min, options.floor);
                }
                if (isNumber(options.ceiling)) {
                    axis.max = Math.min(axis.max, options.ceiling);
                }


                // When the threshold is soft, adjust the extreme value only if the data
                // extreme and the padded extreme land on either side of the threshold.
                // For example, a series of [0, 1, 2, 3] would make the yAxis add a tick
                // for -1 because of the default minPadding and startOnTick options.
                // This is prevented by the softThreshold option.
                if (softThreshold && defined(axis.dataMin)) {
                    threshold = threshold || 0;
                    if (!defined(hardMin) &&
                        axis.min < threshold &&
                        axis.dataMin >= threshold
                    ) {
                        axis.min = threshold;

                    } else if (!defined(hardMax) &&
                        axis.max > threshold &&
                        axis.dataMax <= threshold
                    ) {
                        axis.max = threshold;
                    }
                }


                // get tickInterval
                if (
                    axis.min === axis.max ||
                    axis.min === undefined ||
                    axis.max === undefined
                ) {
                    axis.tickInterval = 1;

                } else if (
                    isLinked &&
                    !tickIntervalOption &&
                    tickPixelIntervalOption ===
                    axis.linkedParent.options.tickPixelInterval
                ) {
                    axis.tickInterval = tickIntervalOption =
                        axis.linkedParent.tickInterval;

                } else {
                    axis.tickInterval = pick(
                        tickIntervalOption,
                        this.tickAmount ?
                        ((axis.max - axis.min) / Math.max(this.tickAmount - 1, 1)) :
                        undefined,
                        // For categoried axis, 1 is default, for linear axis use
                        // tickPix
                        categories ?
                        1 :
                        // don't let it be more than the data range
                        (axis.max - axis.min) * tickPixelIntervalOption /
                        Math.max(axis.len, tickPixelIntervalOption)
                    );
                }

                /**
                 * Now we're finished detecting min and max, crop and group series data.
                 * This is in turn needed in order to find tick positions in
                 * ordinal axes.
                 */
                if (isXAxis && !secondPass) {
                    each(axis.series, function(series) {
                        series.processData(
                            axis.min !== axis.oldMin || axis.max !== axis.oldMax
                        );
                    });
                }

                // set the translation factor used in translate function
                axis.setAxisTranslation(true);

                // hook for ordinal axes and radial axes
                if (axis.beforeSetTickPositions) {
                    axis.beforeSetTickPositions();
                }

                // hook for extensions, used in Highstock ordinal axes
                if (axis.postProcessTickInterval) {
                    axis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);
                }

                // In column-like charts, don't cramp in more ticks than there are
                // points (#1943, #4184)
                if (axis.pointRange && !tickIntervalOption) {
                    axis.tickInterval = Math.max(axis.pointRange, axis.tickInterval);
                }

                // Before normalizing the tick interval, handle minimum tick interval.
                // This applies only if tickInterval is not defined.
                minTickInterval = pick(
                    options.minTickInterval,
                    axis.isDatetimeAxis && axis.closestPointRange
                );
                if (!tickIntervalOption && axis.tickInterval < minTickInterval) {
                    axis.tickInterval = minTickInterval;
                }

                // for linear axes, get magnitude and normalize the interval
                if (!isDatetimeAxis && !isLog && !tickIntervalOption) {
                    axis.tickInterval = normalizeTickInterval(
                        axis.tickInterval,
                        null,
                        getMagnitude(axis.tickInterval),
                        // If the tick interval is between 0.5 and 5 and the axis max is
                        // in the order of thousands, chances are we are dealing with
                        // years. Don't allow decimals. #3363.
                        pick(
                            options.allowDecimals, !(
                                axis.tickInterval > 0.5 &&
                                axis.tickInterval < 5 &&
                                axis.max > 1000 &&
                                axis.max < 9999
                            )
                        ), !!this.tickAmount
                    );
                }

                // Prevent ticks from getting so close that we can't draw the labels
                if (!this.tickAmount) {
                    axis.tickInterval = axis.unsquish();
                }

                this.setTickPositions();
            },

            /**
             * Now we have computed the normalized tickInterval, get the tick positions
             */
            setTickPositions: function() {

                var options = this.options,
                    tickPositions,
                    tickPositionsOption = options.tickPositions,
                    minorTickIntervalOption = this.getMinorTickInterval(),
                    tickPositioner = options.tickPositioner,
                    startOnTick = options.startOnTick,
                    endOnTick = options.endOnTick;

                // Set the tickmarkOffset
                this.tickmarkOffset = (
                    this.categories &&
                    options.tickmarkPlacement === 'between' &&
                    this.tickInterval === 1
                ) ? 0.5 : 0; // #3202


                // get minorTickInterval
                this.minorTickInterval =
                    minorTickIntervalOption === 'auto' &&
                    this.tickInterval ?
                    this.tickInterval / 5 :
                    minorTickIntervalOption;

                // When there is only one point, or all points have the same value on
                // this axis, then min and max are equal and tickPositions.length is 0
                // or 1. In this case, add some padding in order to center the point,
                // but leave it with one tick. #1337.
                this.single =
                    this.min === this.max &&
                    defined(this.min) &&
                    !this.tickAmount &&
                    (
                        // Data is on integer (#6563)
                        parseInt(this.min, 10) === this.min ||

                        // Between integers and decimals are not allowed (#6274)
                        options.allowDecimals !== false
                    );

                // Find the tick positions. Work on a copy (#1565)
                this.tickPositions = tickPositions =
                    tickPositionsOption && tickPositionsOption.slice();
                if (!tickPositions) {

                    if (this.isDatetimeAxis) {
                        tickPositions = this.getTimeTicks(
                            this.normalizeTimeTickInterval(
                                this.tickInterval,
                                options.units
                            ),
                            this.min,
                            this.max,
                            options.startOfWeek,
                            this.ordinalPositions,
                            this.closestPointRange,
                            true
                        );
                    } else if (this.isLog) {
                        tickPositions = this.getLogTickPositions(
                            this.tickInterval,
                            this.min,
                            this.max
                        );
                    } else {
                        tickPositions = this.getLinearTickPositions(
                            this.tickInterval,
                            this.min,
                            this.max
                        );
                    }

                    // Too dense ticks, keep only the first and last (#4477)
                    if (tickPositions.length > this.len) {
                        tickPositions = [tickPositions[0], tickPositions.pop()];
                        // Reduce doubled value (#7339)
                        if (tickPositions[0] === tickPositions[1]) {
                            tickPositions.length = 1;
                        }
                    }

                    this.tickPositions = tickPositions;

                    // Run the tick positioner callback, that allows modifying auto tick
                    // positions.
                    if (tickPositioner) {
                        tickPositioner = tickPositioner.apply(
                            this, [this.min, this.max]
                        );
                        if (tickPositioner) {
                            this.tickPositions = tickPositions = tickPositioner;
                        }
                    }

                }

                // Reset min/max or remove extremes based on start/end on tick
                this.paddedTicks = tickPositions.slice(0); // Used for logarithmic minor
                this.trimTicks(tickPositions, startOnTick, endOnTick);
                if (!this.isLinked) {

                    // Substract half a unit (#2619, #2846, #2515, #3390),
                    // but not in case of multiple ticks (#6897)
                    if (this.single && tickPositions.length < 2) {
                        this.min -= 0.5;
                        this.max += 0.5;
                    }
                    if (!tickPositionsOption && !tickPositioner) {
                        this.adjustTickAmount();
                    }
                }
            },

            /**
             * Handle startOnTick and endOnTick by either adapting to padding min/max or
             * rounded min/max. Also handle single data points.
             *
             * @private
             */
            trimTicks: function(tickPositions, startOnTick, endOnTick) {
                var roundedMin = tickPositions[0],
                    roundedMax = tickPositions[tickPositions.length - 1],
                    minPointOffset = this.minPointOffset || 0;

                if (!this.isLinked) {
                    if (startOnTick && roundedMin !== -Infinity) { // #6502
                        this.min = roundedMin;
                    } else {
                        while (this.min - minPointOffset > tickPositions[0]) {
                            tickPositions.shift();
                        }
                    }

                    if (endOnTick) {
                        this.max = roundedMax;
                    } else {
                        while (this.max + minPointOffset <
                            tickPositions[tickPositions.length - 1]) {
                            tickPositions.pop();
                        }
                    }

                    // If no tick are left, set one tick in the middle (#3195)
                    if (tickPositions.length === 0 && defined(roundedMin)) {
                        tickPositions.push((roundedMax + roundedMin) / 2);
                    }
                }
            },

            /**
             * Check if there are multiple axes in the same pane.
             *
             * @private
             * @return {Boolean}
             *         True if there are other axes.
             */
            alignToOthers: function() {
                var others = {}, // Whether there is another axis to pair with this one
                    hasOther,
                    options = this.options;

                if (
                    // Only if alignTicks is true
                    this.chart.options.chart.alignTicks !== false &&
                    options.alignTicks !== false &&

                    // Don't try to align ticks on a log axis, they are not evenly
                    // spaced (#6021)
                    !this.isLog
                ) {
                    each(this.chart[this.coll], function(axis) {
                        var otherOptions = axis.options,
                            horiz = axis.horiz,
                            key = [
                                horiz ? otherOptions.left : otherOptions.top,
                                otherOptions.width,
                                otherOptions.height,
                                otherOptions.pane
                            ].join(',');


                        if (axis.series.length) { // #4442
                            if (others[key]) {
                                hasOther = true; // #4201
                            } else {
                                others[key] = 1;
                            }
                        }
                    });
                }
                return hasOther;
            },

            /**
             * Find the max ticks of either the x and y axis collection, and record it
             * in `this.tickAmount`.
             *
             * @private
             */
            getTickAmount: function() {
                var options = this.options,
                    tickAmount = options.tickAmount,
                    tickPixelInterval = options.tickPixelInterval;

                if (!defined(options.tickInterval) &&
                    this.len < tickPixelInterval &&
                    !this.isRadial &&
                    !this.isLog &&
                    options.startOnTick &&
                    options.endOnTick
                ) {
                    tickAmount = 2;
                }

                if (!tickAmount && this.alignToOthers()) {
                    // Add 1 because 4 tick intervals require 5 ticks (including first
                    // and last)
                    tickAmount = Math.ceil(this.len / tickPixelInterval) + 1;
                }

                // For tick amounts of 2 and 3, compute five ticks and remove the
                // intermediate ones. This prevents the axis from adding ticks that are
                // too far away from the data extremes.
                if (tickAmount < 4) {
                    this.finalTickAmt = tickAmount;
                    tickAmount = 5;
                }

                this.tickAmount = tickAmount;
            },

            /**
             * When using multiple axes, adjust the number of ticks to match the highest
             * number of ticks in that group.
             *
             * @private
             */
            adjustTickAmount: function() {
                var tickInterval = this.tickInterval,
                    tickPositions = this.tickPositions,
                    tickAmount = this.tickAmount,
                    finalTickAmt = this.finalTickAmt,
                    currentTickAmount = tickPositions && tickPositions.length,
                    i,
                    len;

                if (currentTickAmount < tickAmount) {
                    while (tickPositions.length < tickAmount) {
                        tickPositions.push(correctFloat(
                            tickPositions[tickPositions.length - 1] + tickInterval
                        ));
                    }
                    this.transA *= (currentTickAmount - 1) / (tickAmount - 1);
                    this.max = tickPositions[tickPositions.length - 1];

                    // We have too many ticks, run second pass to try to reduce ticks
                } else if (currentTickAmount > tickAmount) {
                    this.tickInterval *= 2;
                    this.setTickPositions();
                }

                // The finalTickAmt property is set in getTickAmount
                if (defined(finalTickAmt)) {
                    i = len = tickPositions.length;
                    while (i--) {
                        if (
                            // Remove every other tick
                            (finalTickAmt === 3 && i % 2 === 1) ||
                            // Remove all but first and last
                            (finalTickAmt <= 2 && i > 0 && i < len - 1)
                        ) {
                            tickPositions.splice(i, 1);
                        }
                    }
                    this.finalTickAmt = undefined;
                }
            },

            /**
             * Set the scale based on data min and max, user set min and max or options.
             * 
             * @private
             */
            setScale: function() {
                var axis = this,
                    isDirtyData,
                    isDirtyAxisLength;

                axis.oldMin = axis.min;
                axis.oldMax = axis.max;
                axis.oldAxisLength = axis.len;

                // set the new axisLength
                axis.setAxisSize();
                isDirtyAxisLength = axis.len !== axis.oldAxisLength;

                // is there new data?
                each(axis.series, function(series) {
                    if (
                        series.isDirtyData ||
                        series.isDirty ||
                        // When x axis is dirty, we need new data extremes for y as well
                        series.xAxis.isDirty
                    ) {
                        isDirtyData = true;
                    }
                });

                // do we really need to go through all this?
                if (
                    isDirtyAxisLength ||
                    isDirtyData ||
                    axis.isLinked ||
                    axis.forceRedraw ||
                    axis.userMin !== axis.oldUserMin ||
                    axis.userMax !== axis.oldUserMax ||
                    axis.alignToOthers()
                ) {

                    if (axis.resetStacks) {
                        axis.resetStacks();
                    }

                    axis.forceRedraw = false;

                    // get data extremes if needed
                    axis.getSeriesExtremes();

                    // get fixed positions based on tickInterval
                    axis.setTickInterval();

                    // record old values to decide whether a rescale is necessary later
                    // on (#540)
                    axis.oldUserMin = axis.userMin;
                    axis.oldUserMax = axis.userMax;

                    // Mark as dirty if it is not already set to dirty and extremes have
                    // changed. #595.
                    if (!axis.isDirty) {
                        axis.isDirty =
                            isDirtyAxisLength ||
                            axis.min !== axis.oldMin ||
                            axis.max !== axis.oldMax;
                    }
                } else if (axis.cleanStacks) {
                    axis.cleanStacks();
                }
            },

            /**
             * Set the minimum and maximum of the axes after render time. If the
             * `startOnTick` and `endOnTick` options are true, the minimum and maximum
             * values are rounded off to the nearest tick. To prevent this, these
             * options can be set to false before calling setExtremes. Also, setExtremes
             * will not allow a range lower than the `minRange` option, which by default
             * is the range of five points.
             * 
             * @param  {Number} [newMin]
             *         The new minimum value.
             * @param  {Number} [newMax]
             *         The new maximum value.
             * @param  {Boolean} [redraw=true]
             *         Whether to redraw the chart or wait for an explicit call to 
             *         {@link Highcharts.Chart#redraw}
             * @param  {AnimationOptions} [animation=true]
             *         Enable or modify animations.
             * @param  {Object} [eventArguments]
             *         Arguments to be accessed in event handler.
             *
             * @sample highcharts/members/axis-setextremes/
             *         Set extremes from a button
             * @sample highcharts/members/axis-setextremes-datetime/
             *         Set extremes on a datetime axis
             * @sample highcharts/members/axis-setextremes-off-ticks/
             *         Set extremes off ticks
             * @sample stock/members/axis-setextremes/
             *         Set extremes in Highstock
             * @sample maps/members/axis-setextremes/
             *         Set extremes in Highmaps
             */
            setExtremes: function(newMin, newMax, redraw, animation, eventArguments) {
                var axis = this,
                    chart = axis.chart;

                redraw = pick(redraw, true); // defaults to true

                each(axis.series, function(serie) {
                    delete serie.kdTree;
                });

                // Extend the arguments with min and max
                eventArguments = extend(eventArguments, {
                    min: newMin,
                    max: newMax
                });

                // Fire the event
                fireEvent(axis, 'setExtremes', eventArguments, function() {

                    axis.userMin = newMin;
                    axis.userMax = newMax;
                    axis.eventArgs = eventArguments;

                    if (redraw) {
                        chart.redraw(animation);
                    }
                });
            },

            /**
             * Overridable method for zooming chart. Pulled out in a separate method to
             * allow overriding in stock charts.
             *
             * @private
             */
            zoom: function(newMin, newMax) {
                var dataMin = this.dataMin,
                    dataMax = this.dataMax,
                    options = this.options,
                    min = Math.min(dataMin, pick(options.min, dataMin)),
                    max = Math.max(dataMax, pick(options.max, dataMax));

                if (newMin !== this.min || newMax !== this.max) { // #5790

                    // Prevent pinch zooming out of range. Check for defined is for
                    // #1946. #1734.
                    if (!this.allowZoomOutside) {
                        // #6014, sometimes newMax will be smaller than min (or newMin
                        // will be larger than max).
                        if (defined(dataMin)) {
                            if (newMin < min) {
                                newMin = min;
                            }
                            if (newMin > max) {
                                newMin = max;
                            }
                        }
                        if (defined(dataMax)) {
                            if (newMax < min) {
                                newMax = min;
                            }
                            if (newMax > max) {
                                newMax = max;
                            }
                        }
                    }

                    // In full view, displaying the reset zoom button is not required
                    this.displayBtn = newMin !== undefined || newMax !== undefined;

                    // Do it
                    this.setExtremes(
                        newMin,
                        newMax,
                        false,
                        undefined, {
                            trigger: 'zoom'
                        }
                    );
                }

                return true;
            },

            /**
             * Update the axis metrics.
             *
             * @private
             */
            setAxisSize: function() {
                var chart = this.chart,
                    options = this.options,
                    // [top, right, bottom, left]
                    offsets = options.offsets || [0, 0, 0, 0],
                    horiz = this.horiz,

                    // Check for percentage based input values. Rounding fixes problems
                    // with column overflow and plot line filtering (#4898, #4899)
                    width = this.width = Math.round(H.relativeLength(
                        pick(
                            options.width,
                            chart.plotWidth - offsets[3] + offsets[1]
                        ),
                        chart.plotWidth
                    )),
                    height = this.height = Math.round(H.relativeLength(
                        pick(
                            options.height,
                            chart.plotHeight - offsets[0] + offsets[2]
                        ),
                        chart.plotHeight
                    )),
                    top = this.top = Math.round(H.relativeLength(
                        pick(options.top, chart.plotTop + offsets[0]),
                        chart.plotHeight,
                        chart.plotTop
                    )),
                    left = this.left = Math.round(H.relativeLength(
                        pick(options.left, chart.plotLeft + offsets[3]),
                        chart.plotWidth,
                        chart.plotLeft
                    ));

                // Expose basic values to use in Series object and navigator
                this.bottom = chart.chartHeight - height - top;
                this.right = chart.chartWidth - width - left;

                // Direction agnostic properties
                this.len = Math.max(horiz ? width : height, 0); // Math.max fixes #905
                this.pos = horiz ? left : top; // distance from SVG origin
            },

            /**
             * The returned object literal from the {@link Highcharts.Axis#getExtremes}
             * function.
             *
             * @typedef  {Object} Extremes
             * @property {Number} dataMax
             *           The maximum value of the axis' associated series.
             * @property {Number} dataMin
             *           The minimum value of the axis' associated series.
             * @property {Number} max
             *           The maximum axis value, either automatic or set manually. If
             *           the `max` option is not set, `maxPadding` is 0 and `endOnTick`
             *           is false, this value will be the same as `dataMax`.
             * @property {Number} min
             *           The minimum axis value, either automatic or set manually. If
             *           the `min` option is not set, `minPadding` is 0 and
             *           `startOnTick` is false, this value will be the same
             *           as `dataMin`.
             */
            /**
             * Get the current extremes for the axis.
             *
             * @returns {Extremes}
             *          An object containing extremes information.
             * 
             * @sample  highcharts/members/axis-getextremes/
             *          Report extremes by click on a button
             * @sample  maps/members/axis-getextremes/
             *          Get extremes in Highmaps
             */
            getExtremes: function() {
                var axis = this,
                    isLog = axis.isLog,
                    lin2log = axis.lin2log;

                return {
                    min: isLog ? correctFloat(lin2log(axis.min)) : axis.min,
                    max: isLog ? correctFloat(lin2log(axis.max)) : axis.max,
                    dataMin: axis.dataMin,
                    dataMax: axis.dataMax,
                    userMin: axis.userMin,
                    userMax: axis.userMax
                };
            },

            /**
             * Get the zero plane either based on zero or on the min or max value.
             * Used in bar and area plots.
             *
             * @param  {Number} threshold
             *         The threshold in axis values.
             *
             * @return {Number}
             *         The translated threshold position in terms of pixels, and
             *         corrected to stay within the axis bounds.
             */
            getThreshold: function(threshold) {
                var axis = this,
                    isLog = axis.isLog,
                    lin2log = axis.lin2log,
                    realMin = isLog ? lin2log(axis.min) : axis.min,
                    realMax = isLog ? lin2log(axis.max) : axis.max;

                if (threshold === null) {
                    threshold = realMin;
                } else if (realMin > threshold) {
                    threshold = realMin;
                } else if (realMax < threshold) {
                    threshold = realMax;
                }

                return axis.translate(threshold, 0, 1, 0, 1);
            },

            /**
             * Compute auto alignment for the axis label based on which side the axis is
             * on and the given rotation for the label.
             *
             * @param  {Number} rotation
             *         The rotation in degrees as set by either the `rotation` or 
             *         `autoRotation` options.
             * @private
             */
            autoLabelAlign: function(rotation) {
                var ret,
                    angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360;

                if (angle > 15 && angle < 165) {
                    ret = 'right';
                } else if (angle > 195 && angle < 345) {
                    ret = 'left';
                } else {
                    ret = 'center';
                }
                return ret;
            },

            /**
             * Get the tick length and width for the axis based on axis options.
             *
             * @private
             * 
             * @param  {String} prefix
             *         'tick' or 'minorTick'
             * @return {Array.<Number>}
             *         An array of tickLength and tickWidth
             */
            tickSize: function(prefix) {
                var options = this.options,
                    tickLength = options[prefix + 'Length'],
                    tickWidth = pick(
                        options[prefix + 'Width'],
                        prefix === 'tick' && this.isXAxis ? 1 : 0 // X axis default 1
                    );

                if (tickWidth && tickLength) {
                    // Negate the length
                    if (options[prefix + 'Position'] === 'inside') {
                        tickLength = -tickLength;
                    }
                    return [tickLength, tickWidth];
                }

            },

            /**
             * Return the size of the labels.
             *
             * @private
             */
            labelMetrics: function() {
                var index = this.tickPositions && this.tickPositions[0] || 0;
                return this.chart.renderer.fontMetrics(
                    this.options.labels.style && this.options.labels.style.fontSize,
                    this.ticks[index] && this.ticks[index].label
                );
            },

            /**
             * Prevent the ticks from getting so close we can't draw the labels. On a
             * horizontal axis, this is handled by rotating the labels, removing ticks
             * and adding ellipsis. On a vertical axis remove ticks and add ellipsis.
             *
             * @private
             */
            unsquish: function() {
                var labelOptions = this.options.labels,
                    horiz = this.horiz,
                    tickInterval = this.tickInterval,
                    newTickInterval = tickInterval,
                    slotSize = this.len / (
                        ((this.categories ? 1 : 0) + this.max - this.min) / tickInterval
                    ),
                    rotation,
                    rotationOption = labelOptions.rotation,
                    labelMetrics = this.labelMetrics(),
                    step,
                    bestScore = Number.MAX_VALUE,
                    autoRotation,
                    // Return the multiple of tickInterval that is needed to avoid
                    // collision
                    getStep = function(spaceNeeded) {
                        var step = spaceNeeded / (slotSize || 1);
                        step = step > 1 ? Math.ceil(step) : 1;
                        return step * tickInterval;
                    };

                if (horiz) {
                    autoRotation = !labelOptions.staggerLines &&
                        !labelOptions.step &&
                        ( // #3971
                            defined(rotationOption) ? [rotationOption] :
                            slotSize < pick(labelOptions.autoRotationLimit, 80) &&
                            labelOptions.autoRotation
                        );

                    if (autoRotation) {

                        // Loop over the given autoRotation options, and determine 
                        // which gives the best score. The best score is that with
                        // the lowest number of steps and a rotation closest
                        // to horizontal.
                        each(autoRotation, function(rot) {
                            var score;

                            if (
                                rot === rotationOption ||
                                (rot && rot >= -90 && rot <= 90)
                            ) { // #3891

                                step = getStep(
                                    Math.abs(labelMetrics.h / Math.sin(deg2rad * rot))
                                );

                                score = step + Math.abs(rot / 360);

                                if (score < bestScore) {
                                    bestScore = score;
                                    rotation = rot;
                                    newTickInterval = step;
                                }
                            }
                        });
                    }

                } else if (!labelOptions.step) { // #4411
                    newTickInterval = getStep(labelMetrics.h);
                }

                this.autoRotation = autoRotation;
                this.labelRotation = pick(rotation, rotationOption);

                return newTickInterval;
            },

            /**
             * Get the general slot width for labels/categories on this axis. This may
             * change between the pre-render (from Axis.getOffset) and the final tick
             * rendering and placement.
             *
             * @private
             * @return {Number}
             *         The pixel width allocated to each axis label.
             */
            getSlotWidth: function() {
                // #5086, #1580, #1931
                var chart = this.chart,
                    horiz = this.horiz,
                    labelOptions = this.options.labels,
                    slotCount = Math.max(
                        this.tickPositions.length - (this.categories ? 0 : 1),
                        1
                    ),
                    marginLeft = chart.margin[3];

                return (
                    horiz &&
                    (labelOptions.step || 0) < 2 &&
                    !labelOptions.rotation && // #4415
                    ((this.staggerLines || 1) * this.len) / slotCount
                ) || (!horiz && (
                    // #7028
                    (
                        labelOptions.style &&
                        parseInt(labelOptions.style.width, 10)
                    ) ||
                    (
                        marginLeft &&
                        (marginLeft - chart.spacing[3])
                    ) ||
                    chart.chartWidth * 0.33
                ));

            },

            /**
             * Render the axis labels and determine whether ellipsis or rotation need
             * to be applied.
             *
             * @private
             */
            renderUnsquish: function() {
                var chart = this.chart,
                    renderer = chart.renderer,
                    tickPositions = this.tickPositions,
                    ticks = this.ticks,
                    labelOptions = this.options.labels,
                    horiz = this.horiz,
                    slotWidth = this.getSlotWidth(),
                    innerWidth = Math.max(
                        1,
                        Math.round(slotWidth - 2 * (labelOptions.padding || 5))
                    ),
                    attr = {},
                    labelMetrics = this.labelMetrics(),
                    textOverflowOption = labelOptions.style &&
                    labelOptions.style.textOverflow,
                    css,
                    maxLabelLength = 0,
                    label,
                    i,
                    pos;

                // Set rotation option unless it is "auto", like in gauges
                if (!isString(labelOptions.rotation)) {
                    attr.rotation = labelOptions.rotation || 0; // #4443
                }

                // Get the longest label length
                each(tickPositions, function(tick) {
                    tick = ticks[tick];
                    if (tick && tick.labelLength > maxLabelLength) {
                        maxLabelLength = tick.labelLength;
                    }
                });
                this.maxLabelLength = maxLabelLength;


                // Handle auto rotation on horizontal axis
                if (this.autoRotation) {

                    // Apply rotation only if the label is too wide for the slot, and
                    // the label is wider than its height.
                    if (
                        maxLabelLength > innerWidth &&
                        maxLabelLength > labelMetrics.h
                    ) {
                        attr.rotation = this.labelRotation;
                    } else {
                        this.labelRotation = 0;
                    }

                    // Handle word-wrap or ellipsis on vertical axis
                } else if (slotWidth) {
                    // For word-wrap or ellipsis
                    css = {
                        width: innerWidth + 'px'
                    };

                    if (!textOverflowOption) {
                        css.textOverflow = 'clip';

                        // On vertical axis, only allow word wrap if there is room
                        // for more lines.
                        i = tickPositions.length;
                        while (!horiz && i--) {
                            pos = tickPositions[i];
                            label = ticks[pos].label;
                            if (label) {
                                // Reset ellipsis in order to get the correct
                                // bounding box (#4070)
                                if (
                                    label.styles &&
                                    label.styles.textOverflow === 'ellipsis'
                                ) {
                                    label.css({
                                        textOverflow: 'clip'
                                    });

                                    // Set the correct width in order to read
                                    // the bounding box height (#4678, #5034)
                                } else if (ticks[pos].labelLength > slotWidth) {
                                    label.css({
                                        width: slotWidth + 'px'
                                    });
                                }

                                if (
                                    label.getBBox().height > (
                                        this.len / tickPositions.length -
                                        (labelMetrics.h - labelMetrics.f)
                                    )
                                ) {
                                    label.specCss = {
                                        textOverflow: 'ellipsis'
                                    };
                                }
                            }
                        }
                    }
                }


                // Add ellipsis if the label length is significantly longer than ideal
                if (attr.rotation) {
                    css = {
                        width: (
                            maxLabelLength > chart.chartHeight * 0.5 ?
                            chart.chartHeight * 0.33 :
                            chart.chartHeight
                        ) + 'px'
                    };
                    if (!textOverflowOption) {
                        css.textOverflow = 'ellipsis';
                    }
                }

                // Set the explicit or automatic label alignment
                this.labelAlign = labelOptions.align ||
                    this.autoLabelAlign(this.labelRotation);
                if (this.labelAlign) {
                    attr.align = this.labelAlign;
                }

                // Apply general and specific CSS
                each(tickPositions, function(pos) {
                    var tick = ticks[pos],
                        label = tick && tick.label;
                    if (label) {
                        // This needs to go before the CSS in old IE (#4502)
                        label.attr(attr);

                        if (css) {
                            label.css(merge(css, label.specCss));
                        }
                        delete label.specCss;
                        tick.rotation = attr.rotation;
                    }
                });

                // Note: Why is this not part of getLabelPosition?
                this.tickRotCorr = renderer.rotCorr(
                    labelMetrics.b,
                    this.labelRotation || 0,
                    this.side !== 0
                );
            },

            /**
             * Return true if the axis has associated data.
             *
             * @return {Boolean}
             *         True if the axis has associated visible series and those series
             *         have either valid data points or explicit `min` and `max`
             *         settings.
             */
            hasData: function() {
                return (
                    this.hasVisibleSeries ||
                    (
                        defined(this.min) &&
                        defined(this.max) &&
                        this.tickPositions &&
                        this.tickPositions.length > 0
                    )
                );
            },

            /**
             * Adds the title defined in axis.options.title.
             * @param {Boolean} display - whether or not to display the title
             */
            addTitle: function(display) {
                var axis = this,
                    renderer = axis.chart.renderer,
                    horiz = axis.horiz,
                    opposite = axis.opposite,
                    options = axis.options,
                    axisTitleOptions = options.title,
                    textAlign;

                if (!axis.axisTitle) {
                    textAlign = axisTitleOptions.textAlign;
                    if (!textAlign) {
                        textAlign = (horiz ? {
                            low: 'left',
                            middle: 'center',
                            high: 'right'
                        } : {
                            low: opposite ? 'right' : 'left',
                            middle: 'center',
                            high: opposite ? 'left' : 'right'
                        })[axisTitleOptions.align];
                    }
                    axis.axisTitle = renderer.text(
                            axisTitleOptions.text,
                            0,
                            0,
                            axisTitleOptions.useHTML
                        )
                        .attr({
                            zIndex: 7,
                            rotation: axisTitleOptions.rotation || 0,
                            align: textAlign
                        })
                        .addClass('highcharts-axis-title')

                        .css(axisTitleOptions.style)

                        .add(axis.axisGroup);
                    axis.axisTitle.isNew = true;
                }

                // Max width defaults to the length of the axis

                if (!axisTitleOptions.style.width && !axis.isRadial) {

                    axis.axisTitle.css({
                        width: axis.len
                    });

                }



                // hide or show the title depending on whether showEmpty is set
                axis.axisTitle[display ? 'show' : 'hide'](true);
            },

            /**
             * Generates a tick for initial positioning.
             *
             * @private
             * @param {number} pos
             *        The tick position in axis values.
             * @param {number} i
             *        The index of the tick in {@link Axis.tickPositions}.
             */
            generateTick: function(pos) {
                var ticks = this.ticks;

                if (!ticks[pos]) {
                    ticks[pos] = new Tick(this, pos);
                } else {
                    ticks[pos].addLabel(); // update labels depending on tick interval
                }
            },

            /**
             * Render the tick labels to a preliminary position to get their sizes.
             *
             * @private
             */
            getOffset: function() {
                var axis = this,
                    chart = axis.chart,
                    renderer = chart.renderer,
                    options = axis.options,
                    tickPositions = axis.tickPositions,
                    ticks = axis.ticks,
                    horiz = axis.horiz,
                    side = axis.side,
                    invertedSide = chart.inverted &&
                    !axis.isZAxis ? [1, 0, 3, 2][side] : side,
                    hasData,
                    showAxis,
                    titleOffset = 0,
                    titleOffsetOption,
                    titleMargin = 0,
                    axisTitleOptions = options.title,
                    labelOptions = options.labels,
                    labelOffset = 0, // reset
                    labelOffsetPadded,
                    axisOffset = chart.axisOffset,
                    clipOffset = chart.clipOffset,
                    clip,
                    directionFactor = [-1, 1, 1, -1][side],
                    className = options.className,
                    axisParent = axis.axisParent, // Used in color axis
                    lineHeightCorrection,
                    tickSize = this.tickSize('tick');

                // For reuse in Axis.render
                hasData = axis.hasData();
                axis.showAxis = showAxis = hasData || pick(options.showEmpty, true);

                // Set/reset staggerLines
                axis.staggerLines = axis.horiz && labelOptions.staggerLines;

                // Create the axisGroup and gridGroup elements on first iteration
                if (!axis.axisGroup) {
                    axis.gridGroup = renderer.g('grid')
                        .attr({
                            zIndex: options.gridZIndex || 1
                        })
                        .addClass(
                            'highcharts-' + this.coll.toLowerCase() + '-grid ' +
                            (className || '')
                        )
                        .add(axisParent);
                    axis.axisGroup = renderer.g('axis')
                        .attr({
                            zIndex: options.zIndex || 2
                        })
                        .addClass(
                            'highcharts-' + this.coll.toLowerCase() + ' ' +
                            (className || '')
                        )
                        .add(axisParent);
                    axis.labelGroup = renderer.g('axis-labels')
                        .attr({
                            zIndex: labelOptions.zIndex || 7
                        })
                        .addClass(
                            'highcharts-' + axis.coll.toLowerCase() + '-labels ' +
                            (className || '')
                        )
                        .add(axisParent);
                }

                if (hasData || axis.isLinked) {

                    // Generate ticks
                    each(tickPositions, function(pos, i) {
                        // i is not used here, but may be used in overrides
                        axis.generateTick(pos, i);
                    });

                    axis.renderUnsquish();


                    // Left side must be align: right and right side must
                    // have align: left for labels
                    if (
                        labelOptions.reserveSpace !== false &&
                        (
                            side === 0 ||
                            side === 2 || {
                                1: 'left',
                                3: 'right'
                            }[side] === axis.labelAlign ||
                            axis.labelAlign === 'center'
                        )
                    ) {
                        each(tickPositions, function(pos) {
                            // get the highest offset
                            labelOffset = Math.max(
                                ticks[pos].getLabelSize(),
                                labelOffset
                            );
                        });
                    }

                    if (axis.staggerLines) {
                        labelOffset *= axis.staggerLines;
                        axis.labelOffset = labelOffset * (axis.opposite ? -1 : 1);
                    }

                } else { // doesn't have data
                    objectEach(ticks, function(tick, n) {
                        tick.destroy();
                        delete ticks[n];
                    });
                }

                if (
                    axisTitleOptions &&
                    axisTitleOptions.text &&
                    axisTitleOptions.enabled !== false
                ) {
                    axis.addTitle(showAxis);

                    if (showAxis && axisTitleOptions.reserveSpace !== false) {
                        axis.titleOffset = titleOffset =
                            axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
                        titleOffsetOption = axisTitleOptions.offset;
                        titleMargin = defined(titleOffsetOption) ?
                            0 :
                            pick(axisTitleOptions.margin, horiz ? 5 : 10);
                    }
                }

                // Render the axis line
                axis.renderLine();

                // handle automatic or user set offset
                axis.offset = directionFactor * pick(options.offset, axisOffset[side]);

                axis.tickRotCorr = axis.tickRotCorr || {
                    x: 0,
                    y: 0
                }; // polar
                if (side === 0) {
                    lineHeightCorrection = -axis.labelMetrics().h;
                } else if (side === 2) {
                    lineHeightCorrection = axis.tickRotCorr.y;
                } else {
                    lineHeightCorrection = 0;
                }

                // Find the padded label offset
                labelOffsetPadded = Math.abs(labelOffset) + titleMargin;
                if (labelOffset) {
                    labelOffsetPadded -= lineHeightCorrection;
                    labelOffsetPadded += directionFactor * (
                        horiz ?
                        pick(
                            labelOptions.y,
                            axis.tickRotCorr.y + directionFactor * 8
                        ) :
                        labelOptions.x
                    );
                }

                axis.axisTitleMargin = pick(titleOffsetOption, labelOffsetPadded);

                axisOffset[side] = Math.max(
                    axisOffset[side],
                    axis.axisTitleMargin + titleOffset + directionFactor * axis.offset,
                    labelOffsetPadded, // #3027
                    hasData && tickPositions.length && tickSize ?
                    tickSize[0] + directionFactor * axis.offset :
                    0 // #4866
                );

                // Decide the clipping needed to keep the graph inside
                // the plot area and axis lines
                clip = options.offset ?
                    0 :
                    Math.floor(axis.axisLine.strokeWidth() / 2) * 2; // #4308, #4371
                clipOffset[invertedSide] = Math.max(clipOffset[invertedSide], clip);
            },

            /**
             * Internal function to get the path for the axis line. Extended for polar
             * charts.
             *
             * @param  {Number} lineWidth
             *         The line width in pixels.
             * @return {Array}
             *         The SVG path definition in array form.
             */
            getLinePath: function(lineWidth) {
                var chart = this.chart,
                    opposite = this.opposite,
                    offset = this.offset,
                    horiz = this.horiz,
                    lineLeft = this.left + (opposite ? this.width : 0) + offset,
                    lineTop = chart.chartHeight - this.bottom -
                    (opposite ? this.height : 0) + offset;

                if (opposite) {
                    lineWidth *= -1; // crispify the other way - #1480, #1687
                }

                return chart.renderer
                    .crispLine([
                        'M',
                        horiz ?
                        this.left :
                        lineLeft,
                        horiz ?
                        lineTop :
                        this.top,
                        'L',
                        horiz ?
                        chart.chartWidth - this.right :
                        lineLeft,
                        horiz ?
                        lineTop :
                        chart.chartHeight - this.bottom
                    ], lineWidth);
            },

            /**
             * Render the axis line. Called internally when rendering and redrawing the
             * axis.
             */
            renderLine: function() {
                if (!this.axisLine) {
                    this.axisLine = this.chart.renderer.path()
                        .addClass('highcharts-axis-line')
                        .add(this.axisGroup);


                    this.axisLine.attr({
                        stroke: this.options.lineColor,
                        'stroke-width': this.options.lineWidth,
                        zIndex: 7
                    });

                }
            },

            /**
             * Position the axis title.
             *
             * @private
             *
             * @return {Object}
             *         X and Y positions for the title.
             */
            getTitlePosition: function() {
                // compute anchor points for each of the title align options
                var horiz = this.horiz,
                    axisLeft = this.left,
                    axisTop = this.top,
                    axisLength = this.len,
                    axisTitleOptions = this.options.title,
                    margin = horiz ? axisLeft : axisTop,
                    opposite = this.opposite,
                    offset = this.offset,
                    xOption = axisTitleOptions.x || 0,
                    yOption = axisTitleOptions.y || 0,
                    axisTitle = this.axisTitle,
                    fontMetrics = this.chart.renderer.fontMetrics(
                        axisTitleOptions.style && axisTitleOptions.style.fontSize,
                        axisTitle
                    ),
                    // The part of a multiline text that is below the baseline of the
                    // first line. Subtract 1 to preserve pixel-perfectness from the 
                    // old behaviour (v5.0.12), where only one line was allowed.
                    textHeightOvershoot = Math.max(
                        axisTitle.getBBox(null, 0).height - fontMetrics.h - 1,
                        0
                    ),

                    // the position in the length direction of the axis
                    alongAxis = {
                        low: margin + (horiz ? 0 : axisLength),
                        middle: margin + axisLength / 2,
                        high: margin + (horiz ? axisLength : 0)
                    }[axisTitleOptions.align],

                    // the position in the perpendicular direction of the axis
                    offAxis = (horiz ? axisTop + this.height : axisLeft) +
                    (horiz ? 1 : -1) * // horizontal axis reverses the margin
                    (opposite ? -1 : 1) * // so does opposite axes
                    this.axisTitleMargin + [-textHeightOvershoot, // top
                        textHeightOvershoot, // right
                        fontMetrics.f, // bottom
                        -textHeightOvershoot // left
                    ][this.side];


                return {
                    x: horiz ?
                        alongAxis + xOption : offAxis + (opposite ? this.width : 0) + offset + xOption,
                    y: horiz ?
                        offAxis + yOption - (opposite ? this.height : 0) + offset : alongAxis + yOption
                };
            },

            /**
             * Render a minor tick into the given position. If a minor tick already 
             * exists in this position, move it.
             * 
             * @param  {number} pos
             *         The position in axis values.
             */
            renderMinorTick: function(pos) {
                var slideInTicks = this.chart.hasRendered && isNumber(this.oldMin),
                    minorTicks = this.minorTicks;

                if (!minorTicks[pos]) {
                    minorTicks[pos] = new Tick(this, pos, 'minor');
                }

                // Render new ticks in old position
                if (slideInTicks && minorTicks[pos].isNew) {
                    minorTicks[pos].render(null, true);
                }

                minorTicks[pos].render(null, false, 1);
            },

            /**
             * Render a major tick into the given position. If a tick already exists
             * in this position, move it.
             * 
             * @param  {number} pos
             *         The position in axis values.
             * @param  {number} i
             *         The tick index.
             */
            renderTick: function(pos, i) {
                var isLinked = this.isLinked,
                    ticks = this.ticks,
                    slideInTicks = this.chart.hasRendered && isNumber(this.oldMin);

                // Linked axes need an extra check to find out if
                if (!isLinked || (pos >= this.min && pos <= this.max)) {

                    if (!ticks[pos]) {
                        ticks[pos] = new Tick(this, pos);
                    }

                    // render new ticks in old position
                    if (slideInTicks && ticks[pos].isNew) {
                        ticks[pos].render(i, true, 0.1);
                    }

                    ticks[pos].render(i);
                }
            },

            /**
             * Render the axis.
             *
             * @private
             */
            render: function() {
                var axis = this,
                    chart = axis.chart,
                    renderer = chart.renderer,
                    options = axis.options,
                    isLog = axis.isLog,
                    lin2log = axis.lin2log,
                    isLinked = axis.isLinked,
                    tickPositions = axis.tickPositions,
                    axisTitle = axis.axisTitle,
                    ticks = axis.ticks,
                    minorTicks = axis.minorTicks,
                    alternateBands = axis.alternateBands,
                    stackLabelOptions = options.stackLabels,
                    alternateGridColor = options.alternateGridColor,
                    tickmarkOffset = axis.tickmarkOffset,
                    axisLine = axis.axisLine,
                    showAxis = axis.showAxis,
                    animation = animObject(renderer.globalAnimation),
                    from,
                    to;

                // Reset
                axis.labelEdge.length = 0;
                axis.overlap = false;

                // Mark all elements inActive before we go over and mark the active ones
                each([ticks, minorTicks, alternateBands], function(coll) {
                    objectEach(coll, function(tick) {
                        tick.isActive = false;
                    });
                });

                // If the series has data draw the ticks. Else only the line and title
                if (axis.hasData() || isLinked) {

                    // minor ticks
                    if (axis.minorTickInterval && !axis.categories) {
                        each(axis.getMinorTickPositions(), function(pos) {
                            axis.renderMinorTick(pos);
                        });
                    }

                    // Major ticks. Pull out the first item and render it last so that
                    // we can get the position of the neighbour label. #808.
                    if (tickPositions.length) { // #1300
                        each(tickPositions, function(pos, i) {
                            axis.renderTick(pos, i);
                        });
                        // In a categorized axis, the tick marks are displayed
                        // between labels. So we need to add a tick mark and
                        // grid line at the left edge of the X axis.
                        if (tickmarkOffset && (axis.min === 0 || axis.single)) {
                            if (!ticks[-1]) {
                                ticks[-1] = new Tick(axis, -1, null, true);
                            }
                            ticks[-1].render(-1);
                        }

                    }

                    // alternate grid color
                    if (alternateGridColor) {
                        each(tickPositions, function(pos, i) {
                            to = tickPositions[i + 1] !== undefined ?
                                tickPositions[i + 1] + tickmarkOffset :
                                axis.max - tickmarkOffset;

                            if (
                                i % 2 === 0 &&
                                pos < axis.max &&
                                to <= axis.max + (
                                    chart.polar ?
                                    -tickmarkOffset :
                                    tickmarkOffset
                                )
                            ) { // #2248, #4660
                                if (!alternateBands[pos]) {
                                    alternateBands[pos] = new H.PlotLineOrBand(axis);
                                }
                                from = pos + tickmarkOffset; // #949
                                alternateBands[pos].options = {
                                    from: isLog ? lin2log(from) : from,
                                    to: isLog ? lin2log(to) : to,
                                    color: alternateGridColor
                                };
                                alternateBands[pos].render();
                                alternateBands[pos].isActive = true;
                            }
                        });
                    }

                    // custom plot lines and bands
                    if (!axis._addedPlotLB) { // only first time
                        each(
                            (options.plotLines || []).concat(options.plotBands || []),
                            function(plotLineOptions) {
                                axis.addPlotBandOrLine(plotLineOptions);
                            }
                        );
                        axis._addedPlotLB = true;
                    }

                } // end if hasData

                // Remove inactive ticks
                each([ticks, minorTicks, alternateBands], function(coll) {
                    var i,
                        forDestruction = [],
                        delay = animation.duration,
                        destroyInactiveItems = function() {
                            i = forDestruction.length;
                            while (i--) {
                                // When resizing rapidly, the same items
                                // may be destroyed in different timeouts,
                                // or the may be reactivated
                                if (
                                    coll[forDestruction[i]] &&
                                    !coll[forDestruction[i]].isActive
                                ) {
                                    coll[forDestruction[i]].destroy();
                                    delete coll[forDestruction[i]];
                                }
                            }

                        };

                    objectEach(coll, function(tick, pos) {
                        if (!tick.isActive) {
                            // Render to zero opacity
                            tick.render(pos, false, 0);
                            tick.isActive = false;
                            forDestruction.push(pos);
                        }
                    });

                    // When the objects are finished fading out, destroy them
                    syncTimeout(
                        destroyInactiveItems,
                        coll === alternateBands ||
                        !chart.hasRendered ||
                        !delay ?
                        0 :
                        delay
                    );
                });

                // Set the axis line path
                if (axisLine) {
                    axisLine[axisLine.isPlaced ? 'animate' : 'attr']({
                        d: this.getLinePath(axisLine.strokeWidth())
                    });
                    axisLine.isPlaced = true;

                    // Show or hide the line depending on options.showEmpty
                    axisLine[showAxis ? 'show' : 'hide'](true);
                }

                if (axisTitle && showAxis) {
                    var titleXy = axis.getTitlePosition();
                    if (isNumber(titleXy.y)) {
                        axisTitle[axisTitle.isNew ? 'attr' : 'animate'](titleXy);
                        axisTitle.isNew = false;
                    } else {
                        axisTitle.attr('y', -9999);
                        axisTitle.isNew = true;
                    }
                }

                // Stacked totals:
                if (stackLabelOptions && stackLabelOptions.enabled) {
                    axis.renderStackTotals();
                }
                // End stacked totals

                axis.isDirty = false;
            },

            /**
             * Redraw the axis to reflect changes in the data or axis extremes. Called
             * internally from {@link Chart#redraw}.
             *
             * @private
             */
            redraw: function() {

                if (this.visible) {
                    // render the axis
                    this.render();

                    // move plot lines and bands
                    each(this.plotLinesAndBands, function(plotLine) {
                        plotLine.render();
                    });
                }

                // mark associated series as dirty and ready for redraw
                each(this.series, function(series) {
                    series.isDirty = true;
                });

            },

            // Properties to survive after destroy, needed for Axis.update (#4317,
            // #5773, #5881).
            keepProps: ['extKey', 'hcEvents', 'names', 'series', 'userMax', 'userMin'],

            /**
             * Destroys an Axis instance. See {@link Axis#remove} for the API endpoint
             * to fully remove the axis.
             *
             * @private
             * @param  {Boolean} keepEvents
             *         Whether to preserve events, used internally in Axis.update.
             */
            destroy: function(keepEvents) {
                var axis = this,
                    stacks = axis.stacks,
                    plotLinesAndBands = axis.plotLinesAndBands,
                    plotGroup,
                    i;

                // Remove the events
                if (!keepEvents) {
                    removeEvent(axis);
                }

                // Destroy each stack total
                objectEach(stacks, function(stack, stackKey) {
                    destroyObjectProperties(stack);

                    stacks[stackKey] = null;
                });

                // Destroy collections
                each(
                    [axis.ticks, axis.minorTicks, axis.alternateBands],
                    function(coll) {
                        destroyObjectProperties(coll);
                    }
                );
                if (plotLinesAndBands) {
                    i = plotLinesAndBands.length;
                    while (i--) { // #1975
                        plotLinesAndBands[i].destroy();
                    }
                }

                // Destroy local variables
                each(
                    ['stackTotalGroup', 'axisLine', 'axisTitle', 'axisGroup',
                        'gridGroup', 'labelGroup', 'cross'
                    ],
                    function(prop) {
                        if (axis[prop]) {
                            axis[prop] = axis[prop].destroy();
                        }
                    }
                );

                // Destroy each generated group for plotlines and plotbands
                for (plotGroup in axis.plotLinesAndBandsGroups) {
                    axis.plotLinesAndBandsGroups[plotGroup] =
                        axis.plotLinesAndBandsGroups[plotGroup].destroy();
                }

                // Delete all properties and fall back to the prototype.
                objectEach(axis, function(val, key) {
                    if (inArray(key, axis.keepProps) === -1) {
                        delete axis[key];
                    }
                });
            },

            /**
             * Internal function to draw a crosshair.
             *
             * @param  {PointerEvent} [e]
             *         The event arguments from the modified pointer event, extended 
             *         with `chartX` and `chartY`
             * @param  {Point} [point]
             *         The Point object if the crosshair snaps to points.
             */
            drawCrosshair: function(e, point) {

                var path,
                    options = this.crosshair,
                    snap = pick(options.snap, true),
                    pos,
                    categorized,
                    graphic = this.cross;

                // Use last available event when updating non-snapped crosshairs without
                // mouse interaction (#5287)
                if (!e) {
                    e = this.cross && this.cross.e;
                }

                if (
                    // Disabled in options
                    !this.crosshair ||
                    // Snap
                    ((defined(point) || !snap) === false)
                ) {
                    this.hideCrosshair();
                } else {

                    // Get the path
                    if (!snap) {
                        pos = e &&
                            (
                                this.horiz ?
                                e.chartX - this.pos :
                                this.len - e.chartY + this.pos
                            );
                    } else if (defined(point)) {
                        // #3834
                        pos = this.isXAxis ? point.plotX : this.len - point.plotY;
                    }

                    if (defined(pos)) {
                        path = this.getPlotLinePath(
                            // First argument, value, only used on radial
                            point && (this.isXAxis ?
                                point.x :
                                pick(point.stackY, point.y)
                            ),
                            null,
                            null,
                            null,
                            pos // Translated position
                        ) || null; // #3189
                    }

                    if (!defined(path)) {
                        this.hideCrosshair();
                        return;
                    }

                    categorized = this.categories && !this.isRadial;

                    // Draw the cross
                    if (!graphic) {
                        this.cross = graphic = this.chart.renderer
                            .path()
                            .addClass(
                                'highcharts-crosshair highcharts-crosshair-' +
                                (categorized ? 'category ' : 'thin ') +
                                options.className
                            )
                            .attr({
                                zIndex: pick(options.zIndex, 2)
                            })
                            .add();


                        // Presentational attributes
                        graphic.attr({
                            'stroke': options.color ||
                                (
                                    categorized ?
                                    color('#ccd6eb')
                                    .setOpacity(0.25).get() :
                                    '#cccccc'
                                ),
                            'stroke-width': pick(options.width, 1)
                        }).css({
                            'pointer-events': 'none'
                        });
                        if (options.dashStyle) {
                            graphic.attr({
                                dashstyle: options.dashStyle
                            });
                        }


                    }

                    graphic.show().attr({
                        d: path
                    });

                    if (categorized && !options.width) {
                        graphic.attr({
                            'stroke-width': this.transA
                        });
                    }
                    this.cross.e = e;
                }
            },

            /**
             *	Hide the crosshair if visible.
             */
            hideCrosshair: function() {
                if (this.cross) {
                    this.cross.hide();
                }
            }
        }); // end Axis

        H.Axis = Axis;
        return Axis;
    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var Axis = H.Axis,
            Date = H.Date,
            dateFormat = H.dateFormat,
            defaultOptions = H.defaultOptions,
            defined = H.defined,
            each = H.each,
            extend = H.extend,
            getMagnitude = H.getMagnitude,
            getTZOffset = H.getTZOffset,
            normalizeTickInterval = H.normalizeTickInterval,
            pick = H.pick,
            timeUnits = H.timeUnits;
        /**
         * Set the tick positions to a time unit that makes sense, for example
         * on the first of each month or on every Monday. Return an array
         * with the time positions. Used in datetime axes as well as for grouping
         * data on a datetime axis.
         *
         * @param {Object} normalizedInterval The interval in axis values (ms) and the count
         * @param {Number} min The minimum in axis values
         * @param {Number} max The maximum in axis values
         * @param {Number} startOfWeek
         */
        Axis.prototype.getTimeTicks = function(normalizedInterval, min, max, startOfWeek) {
            var tickPositions = [],
                i,
                higherRanks = {},
                useUTC = defaultOptions.global.useUTC,
                minYear, // used in months and years as a basis for Date.UTC()
                // When crossing DST, use the max. Resolves #6278.
                minDate = new Date(min - Math.max(getTZOffset(min), getTZOffset(max))),
                makeTime = Date.hcMakeTime,
                interval = normalizedInterval.unitRange,
                count = normalizedInterval.count,
                baseOffset, // #6797
                variableDayLength;

            if (defined(min)) { // #1300
                minDate[Date.hcSetMilliseconds](interval >= timeUnits.second ? 0 : // #3935
                    count * Math.floor(minDate.getMilliseconds() / count)); // #3652, #3654

                if (interval >= timeUnits.second) { // second
                    minDate[Date.hcSetSeconds](interval >= timeUnits.minute ? 0 : // #3935
                        count * Math.floor(minDate.getSeconds() / count));
                }

                if (interval >= timeUnits.minute) { // minute
                    minDate[Date.hcSetMinutes](interval >= timeUnits.hour ? 0 :
                        count * Math.floor(minDate[Date.hcGetMinutes]() / count));
                }

                if (interval >= timeUnits.hour) { // hour
                    minDate[Date.hcSetHours](interval >= timeUnits.day ? 0 :
                        count * Math.floor(minDate[Date.hcGetHours]() / count));
                }

                if (interval >= timeUnits.day) { // day
                    minDate[Date.hcSetDate](interval >= timeUnits.month ? 1 :
                        count * Math.floor(minDate[Date.hcGetDate]() / count));
                }

                if (interval >= timeUnits.month) { // month
                    minDate[Date.hcSetMonth](interval >= timeUnits.year ? 0 :
                        count * Math.floor(minDate[Date.hcGetMonth]() / count));
                    minYear = minDate[Date.hcGetFullYear]();
                }

                if (interval >= timeUnits.year) { // year
                    minYear -= minYear % count;
                    minDate[Date.hcSetFullYear](minYear);
                }

                // week is a special case that runs outside the hierarchy
                if (interval === timeUnits.week) {
                    // get start of current week, independent of count
                    minDate[Date.hcSetDate](minDate[Date.hcGetDate]() - minDate[Date.hcGetDay]() +
                        pick(startOfWeek, 1));
                }


                // Get basics for variable time spans
                minYear = minDate[Date.hcGetFullYear]();
                var minMonth = minDate[Date.hcGetMonth](),
                    minDateDate = minDate[Date.hcGetDate](),
                    minHours = minDate[Date.hcGetHours]();


                // Handle local timezone offset
                if (Date.hcTimezoneOffset || Date.hcGetTimezoneOffset) {

                    // Detect whether we need to take the DST crossover into
                    // consideration. If we're crossing over DST, the day length may be
                    // 23h or 25h and we need to compute the exact clock time for each
                    // tick instead of just adding hours. This comes at a cost, so first
                    // we found out if it is needed. #4951.
                    variableDayLength =
                        (!useUTC || !!Date.hcGetTimezoneOffset) &&
                        (
                            // Long range, assume we're crossing over.
                            max - min > 4 * timeUnits.month ||
                            // Short range, check if min and max are in different time 
                            // zones.
                            getTZOffset(min) !== getTZOffset(max)
                        );

                    // Adjust minDate to the offset date
                    minDate = minDate.getTime();
                    baseOffset = getTZOffset(minDate);
                    minDate = new Date(minDate + baseOffset);
                }


                // Iterate and add tick positions at appropriate values
                var time = minDate.getTime();
                i = 1;
                while (time < max) {
                    tickPositions.push(time);

                    // if the interval is years, use Date.UTC to increase years
                    if (interval === timeUnits.year) {
                        time = makeTime(minYear + i * count, 0);

                        // if the interval is months, use Date.UTC to increase months
                    } else if (interval === timeUnits.month) {
                        time = makeTime(minYear, minMonth + i * count);

                        // if we're using global time, the interval is not fixed as it jumps
                        // one hour at the DST crossover
                    } else if (
                        variableDayLength &&
                        (interval === timeUnits.day || interval === timeUnits.week)
                    ) {
                        time = makeTime(minYear, minMonth, minDateDate +
                            i * count * (interval === timeUnits.day ? 1 : 7));

                    } else if (variableDayLength && interval === timeUnits.hour) {
                        // corrected by the start date time zone offset (baseOffset)
                        // to hide duplicated label (#6797)
                        time = makeTime(minYear, minMonth, minDateDate, minHours +
                            i * count, 0, 0, baseOffset) - baseOffset;

                        // else, the interval is fixed and we use simple addition
                    } else {
                        time += interval * count;
                    }

                    i++;
                }

                // push the last time
                tickPositions.push(time);


                // Handle higher ranks. Mark new days if the time is on midnight
                // (#950, #1649, #1760, #3349). Use a reasonable dropout threshold to 
                // prevent looping over dense data grouping (#6156).
                if (interval <= timeUnits.hour && tickPositions.length < 10000) {
                    each(tickPositions, function(time) {
                        if (
                            // Speed optimization, no need to run dateFormat unless
                            // we're on a full or half hour
                            time % 1800000 === 0 &&
                            // Check for local or global midnight
                            dateFormat('%H%M%S%L', time) === '000000000'
                        ) {
                            higherRanks[time] = 'day';
                        }
                    });
                }
            }


            // record information on the chosen unit - for dynamic label formatter
            tickPositions.info = extend(normalizedInterval, {
                higherRanks: higherRanks,
                totalRange: interval * count
            });

            return tickPositions;
        };

        /**
         * Get a normalized tick interval for dates. Returns a configuration object with
         * unit range (interval), count and name. Used to prepare data for getTimeTicks.
         * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs
         * of segments in stock charts, the normalizing logic was extracted in order to
         * prevent it for running over again for each segment having the same interval.
         * #662, #697.
         */
        Axis.prototype.normalizeTimeTickInterval = function(tickInterval, unitsOption) {
            var units = unitsOption || [
                    [
                        'millisecond', // unit name
                        [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
                    ],
                    [
                        'second', [1, 2, 5, 10, 15, 30]
                    ],
                    [
                        'minute', [1, 2, 5, 10, 15, 30]
                    ],
                    [
                        'hour', [1, 2, 3, 4, 6, 8, 12]
                    ],
                    [
                        'day', [1, 2]
                    ],
                    [
                        'week', [1, 2]
                    ],
                    [
                        'month', [1, 2, 3, 4, 6]
                    ],
                    [
                        'year',
                        null
                    ]
                ],
                unit = units[units.length - 1], // default unit is years
                interval = timeUnits[unit[0]],
                multiples = unit[1],
                count,
                i;

            // loop through the units to find the one that best fits the tickInterval
            for (i = 0; i < units.length; i++) {
                unit = units[i];
                interval = timeUnits[unit[0]];
                multiples = unit[1];


                if (units[i + 1]) {
                    // lessThan is in the middle between the highest multiple and the next unit.
                    var lessThan = (interval * multiples[multiples.length - 1] +
                        timeUnits[units[i + 1][0]]) / 2;

                    // break and keep the current unit
                    if (tickInterval <= lessThan) {
                        break;
                    }
                }
            }

            // prevent 2.5 years intervals, though 25, 250 etc. are allowed
            if (interval === timeUnits.year && tickInterval < 5 * interval) {
                multiples = [1, 2, 5];
            }

            // get the count
            count = normalizeTickInterval(
                tickInterval / interval,
                multiples,
                unit[0] === 'year' ? Math.max(getMagnitude(tickInterval / interval), 1) : 1 // #1913, #2360
            );

            return {
                unitRange: interval,
                count: count,
                unitName: unit[0]
            };
        };

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var Axis = H.Axis,
            getMagnitude = H.getMagnitude,
            map = H.map,
            normalizeTickInterval = H.normalizeTickInterval,
            pick = H.pick;
        /**
         * Methods defined on the Axis prototype
         */

        /**
         * Set the tick positions of a logarithmic axis
         */
        Axis.prototype.getLogTickPositions = function(interval, min, max, minor) {
            var axis = this,
                options = axis.options,
                axisLength = axis.len,
                lin2log = axis.lin2log,
                log2lin = axis.log2lin,
                // Since we use this method for both major and minor ticks,
                // use a local variable and return the result
                positions = [];

            // Reset
            if (!minor) {
                axis._minorAutoInterval = null;
            }

            // First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
            if (interval >= 0.5) {
                interval = Math.round(interval);
                positions = axis.getLinearTickPositions(interval, min, max);

                // Second case: We need intermediary ticks. For example
                // 1, 2, 4, 6, 8, 10, 20, 40 etc.
            } else if (interval >= 0.08) {
                var roundedMin = Math.floor(min),
                    intermediate,
                    i,
                    j,
                    len,
                    pos,
                    lastPos,
                    break2;

                if (interval > 0.3) {
                    intermediate = [1, 2, 4];
                } else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc
                    intermediate = [1, 2, 4, 6, 8];
                } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
                    intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
                }

                for (i = roundedMin; i < max + 1 && !break2; i++) {
                    len = intermediate.length;
                    for (j = 0; j < len && !break2; j++) {
                        pos = log2lin(lin2log(i) * intermediate[j]);
                        if (pos > min && (!minor || lastPos <= max) && lastPos !== undefined) { // #1670, lastPos is #3113
                            positions.push(lastPos);
                        }

                        if (lastPos > max) {
                            break2 = true;
                        }
                        lastPos = pos;
                    }
                }

                // Third case: We are so deep in between whole logarithmic values that
                // we might as well handle the tick positions like a linear axis. For
                // example 1.01, 1.02, 1.03, 1.04.
            } else {
                var realMin = lin2log(min),
                    realMax = lin2log(max),
                    tickIntervalOption = minor ?
                    this.getMinorTickInterval() :
                    options.tickInterval,
                    filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,
                    tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),
                    totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;

                interval = pick(
                    filteredTickIntervalOption,
                    axis._minorAutoInterval,
                    (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)
                );

                interval = normalizeTickInterval(
                    interval,
                    null,
                    getMagnitude(interval)
                );

                positions = map(axis.getLinearTickPositions(
                    interval,
                    realMin,
                    realMax
                ), log2lin);

                if (!minor) {
                    axis._minorAutoInterval = interval / 5;
                }
            }

            // Set the axis-level tickInterval variable
            if (!minor) {
                axis.tickInterval = interval;
            }
            return positions;
        };

        Axis.prototype.log2lin = function(num) {
            return Math.log(num) / Math.LN10;
        };

        Axis.prototype.lin2log = function(num) {
            return Math.pow(10, num);
        };

    }(Highcharts));
    (function(H, Axis) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var arrayMax = H.arrayMax,
            arrayMin = H.arrayMin,
            defined = H.defined,
            destroyObjectProperties = H.destroyObjectProperties,
            each = H.each,
            erase = H.erase,
            merge = H.merge,
            pick = H.pick;
        /*
         * The object wrapper for plot lines and plot bands
         * @param {Object} options
         */
        H.PlotLineOrBand = function(axis, options) {
            this.axis = axis;

            if (options) {
                this.options = options;
                this.id = options.id;
            }
        };

        H.PlotLineOrBand.prototype = {

            /**
             * Render the plot line or plot band. If it is already existing,
             * move it.
             */
            render: function() {
                var plotLine = this,
                    axis = plotLine.axis,
                    horiz = axis.horiz,
                    options = plotLine.options,
                    optionsLabel = options.label,
                    label = plotLine.label,
                    to = options.to,
                    from = options.from,
                    value = options.value,
                    isBand = defined(from) && defined(to),
                    isLine = defined(value),
                    svgElem = plotLine.svgElem,
                    isNew = !svgElem,
                    path = [],
                    color = options.color,
                    zIndex = pick(options.zIndex, 0),
                    events = options.events,
                    attribs = {
                        'class': 'highcharts-plot-' + (isBand ? 'band ' : 'line ') +
                            (options.className || '')
                    },
                    groupAttribs = {},
                    renderer = axis.chart.renderer,
                    groupName = isBand ? 'bands' : 'lines',
                    group,
                    log2lin = axis.log2lin;

                // logarithmic conversion
                if (axis.isLog) {
                    from = log2lin(from);
                    to = log2lin(to);
                    value = log2lin(value);
                }


                // Set the presentational attributes
                if (isLine) {
                    attribs = {
                        stroke: color,
                        'stroke-width': options.width
                    };
                    if (options.dashStyle) {
                        attribs.dashstyle = options.dashStyle;
                    }

                } else if (isBand) { // plot band
                    if (color) {
                        attribs.fill = color;
                    }
                    if (options.borderWidth) {
                        attribs.stroke = options.borderColor;
                        attribs['stroke-width'] = options.borderWidth;
                    }
                }


                // Grouping and zIndex
                groupAttribs.zIndex = zIndex;
                groupName += '-' + zIndex;

                group = axis.plotLinesAndBandsGroups[groupName];
                if (!group) {
                    axis.plotLinesAndBandsGroups[groupName] = group =
                        renderer.g('plot-' + groupName)
                        .attr(groupAttribs).add();
                }

                // Create the path
                if (isNew) {
                    plotLine.svgElem = svgElem =
                        renderer
                        .path()
                        .attr(attribs).add(group);
                }


                // Set the path or return
                if (isLine) {
                    path = axis.getPlotLinePath(value, svgElem.strokeWidth());
                } else if (isBand) { // plot band
                    path = axis.getPlotBandPath(from, to, options);
                } else {
                    return;
                }


                // common for lines and bands
                if (isNew && path && path.length) {
                    svgElem.attr({
                        d: path
                    });

                    // events
                    if (events) {
                        H.objectEach(events, function(event, eventType) {
                            svgElem.on(eventType, function(e) {
                                events[eventType].apply(plotLine, [e]);
                            });
                        });
                    }
                } else if (svgElem) {
                    if (path) {
                        svgElem.show();
                        svgElem.animate({
                            d: path
                        });
                    } else {
                        svgElem.hide();
                        if (label) {
                            plotLine.label = label = label.destroy();
                        }
                    }
                }

                // the plot band/line label
                if (
                    optionsLabel &&
                    defined(optionsLabel.text) &&
                    path &&
                    path.length &&
                    axis.width > 0 &&
                    axis.height > 0 &&
                    !path.flat
                ) {
                    // apply defaults
                    optionsLabel = merge({
                        align: horiz && isBand && 'center',
                        x: horiz ? !isBand && 4 : 10,
                        verticalAlign: !horiz && isBand && 'middle',
                        y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4,
                        rotation: horiz && !isBand && 90
                    }, optionsLabel);

                    this.renderLabel(optionsLabel, path, isBand, zIndex);

                } else if (label) { // move out of sight
                    label.hide();
                }

                // chainable
                return plotLine;
            },

            /**
             * Render and align label for plot line or band.
             */
            renderLabel: function(optionsLabel, path, isBand, zIndex) {
                var plotLine = this,
                    label = plotLine.label,
                    renderer = plotLine.axis.chart.renderer,
                    attribs,
                    xBounds,
                    yBounds,
                    x,
                    y;

                // add the SVG element
                if (!label) {
                    attribs = {
                        align: optionsLabel.textAlign || optionsLabel.align,
                        rotation: optionsLabel.rotation,
                        'class': 'highcharts-plot-' + (isBand ? 'band' : 'line') +
                            '-label ' + (optionsLabel.className || '')
                    };

                    attribs.zIndex = zIndex;

                    plotLine.label = label = renderer.text(
                            optionsLabel.text,
                            0,
                            0,
                            optionsLabel.useHTML
                        )
                        .attr(attribs)
                        .add();


                    label.css(optionsLabel.style);

                }

                // get the bounding box and align the label
                // #3000 changed to better handle choice between plotband or plotline
                xBounds = path.xBounds || [path[1], path[4], (isBand ? path[6] : path[1])];
                yBounds = path.yBounds || [path[2], path[5], (isBand ? path[7] : path[2])];

                x = arrayMin(xBounds);
                y = arrayMin(yBounds);

                label.align(optionsLabel, false, {
                    x: x,
                    y: y,
                    width: arrayMax(xBounds) - x,
                    height: arrayMax(yBounds) - y
                });
                label.show();
            },

            /**
             * Remove the plot line or band
             */
            destroy: function() {
                // remove it from the lookup
                erase(this.axis.plotLinesAndBands, this);

                delete this.axis;
                destroyObjectProperties(this);
            }
        };

        /**
         * Object with members for extending the Axis prototype
         * @todo Extend directly instead of adding object to Highcharts first
         */

        H.extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {

            /**
             * Internal function to create the SVG path definition for a plot band.
             *
             * @param  {Number} from
             *         The axis value to start from.
             * @param  {Number} to
             *         The axis value to end on.
             *
             * @return {Array.<String|Number>}
             *         The SVG path definition in array form.
             */
            getPlotBandPath: function(from, to) {
                var toPath = this.getPlotLinePath(to, null, null, true),
                    path = this.getPlotLinePath(from, null, null, true),
                    result = [],
                    i,
                    // #4964 check if chart is inverted or plotband is on yAxis 
                    horiz = this.horiz,
                    plus = 1,
                    flat,
                    outside =
                    (from < this.min && to < this.min) ||
                    (from > this.max && to > this.max);

                if (path && toPath) {

                    // Flat paths don't need labels (#3836)
                    if (outside) {
                        flat = path.toString() === toPath.toString();
                        plus = 0;
                    }

                    // Go over each subpath - for panes in Highstock
                    for (i = 0; i < path.length; i += 6) {

                        // Add 1 pixel when coordinates are the same
                        if (horiz && toPath[i + 1] === path[i + 1]) {
                            toPath[i + 1] += plus;
                            toPath[i + 4] += plus;
                        } else if (!horiz && toPath[i + 2] === path[i + 2]) {
                            toPath[i + 2] += plus;
                            toPath[i + 5] += plus;
                        }

                        result.push(
                            'M',
                            path[i + 1],
                            path[i + 2],
                            'L',
                            path[i + 4],
                            path[i + 5],
                            toPath[i + 4],
                            toPath[i + 5],
                            toPath[i + 1],
                            toPath[i + 2],
                            'z'
                        );
                        result.flat = flat;
                    }

                } else { // outside the axis area
                    path = null;
                }

                return result;
            },

            /**
             * Add a plot band after render time.
             *
             * @param  {AxisPlotBandsOptions} options
             *         A configuration object for the plot band, as defined in {@link
             *         https://api.highcharts.com/highcharts/xAxis.plotBands|
             *         xAxis.plotBands}.
             * @return {Object}
             *         The added plot band.
             * @sample highcharts/members/axis-addplotband/
             *         Toggle the plot band from a button
             */
            addPlotBand: function(options) {
                return this.addPlotBandOrLine(options, 'plotBands');
            },

            /**
             * Add a plot line after render time.
             * 
             * @param  {AxisPlotLinesOptions} options
             *         A configuration object for the plot line, as defined in {@link
             *         https://api.highcharts.com/highcharts/xAxis.plotLines|
             *         xAxis.plotLines}.
             * @return {Object}
             *         The added plot line.
             * @sample highcharts/members/axis-addplotline/
             *         Toggle the plot line from a button
             */
            addPlotLine: function(options) {
                return this.addPlotBandOrLine(options, 'plotLines');
            },

            /**
             * Add a plot band or plot line after render time. Called from addPlotBand
             * and addPlotLine internally.
             *
             * @private
             * @param  options {AxisPlotLinesOptions|AxisPlotBandsOptions}
             *         The plotBand or plotLine configuration object.
             */
            addPlotBandOrLine: function(options, coll) {
                var obj = new H.PlotLineOrBand(this, options).render(),
                    userOptions = this.userOptions;

                if (obj) { // #2189
                    // Add it to the user options for exporting and Axis.update
                    if (coll) {
                        userOptions[coll] = userOptions[coll] || [];
                        userOptions[coll].push(options);
                    }
                    this.plotLinesAndBands.push(obj);
                }

                return obj;
            },

            /**
             * Remove a plot band or plot line from the chart by id. Called internally
             * from `removePlotBand` and `removePlotLine`.
             *
             * @private
             * @param {String} id
             */
            removePlotBandOrLine: function(id) {
                var plotLinesAndBands = this.plotLinesAndBands,
                    options = this.options,
                    userOptions = this.userOptions,
                    i = plotLinesAndBands.length;
                while (i--) {
                    if (plotLinesAndBands[i].id === id) {
                        plotLinesAndBands[i].destroy();
                    }
                }
                each([
                    options.plotLines || [],
                    userOptions.plotLines || [],
                    options.plotBands || [],
                    userOptions.plotBands || []
                ], function(arr) {
                    i = arr.length;
                    while (i--) {
                        if (arr[i].id === id) {
                            erase(arr, arr[i]);
                        }
                    }
                });
            },

            /**
             * Remove a plot band by its id.
             * 
             * @param  {String} id
             *         The plot band's `id` as given in the original configuration
             *         object or in the `addPlotBand` option.
             * @sample highcharts/members/axis-removeplotband/
             *         Remove plot band by id
             * @sample highcharts/members/axis-addplotband/
             *         Toggle the plot band from a button
             */
            removePlotBand: function(id) {
                this.removePlotBandOrLine(id);
            },

            /**
             * Remove a plot line by its id.
             * @param  {String} id
             *         The plot line's `id` as given in the original configuration
             *         object or in the `addPlotLine` option.
             * @sample highcharts/xaxis/plotlines-id/
             *         Remove plot line by id
             * @sample highcharts/members/axis-addplotline/
             *         Toggle the plot line from a button
             */
            removePlotLine: function(id) {
                this.removePlotBandOrLine(id);
            }
        });

    }(Highcharts, Axis));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var dateFormat = H.dateFormat,
            each = H.each,
            extend = H.extend,
            format = H.format,
            isNumber = H.isNumber,
            map = H.map,
            merge = H.merge,
            pick = H.pick,
            splat = H.splat,
            syncTimeout = H.syncTimeout,
            timeUnits = H.timeUnits;
        /**
         * The tooltip object
         * @param {Object} chart The chart instance
         * @param {Object} options Tooltip options
         */
        H.Tooltip = function() {
            this.init.apply(this, arguments);
        };

        H.Tooltip.prototype = {

            init: function(chart, options) {

                // Save the chart and options
                this.chart = chart;
                this.options = options;

                // List of crosshairs
                this.crosshairs = [];

                // Current values of x and y when animating
                this.now = {
                    x: 0,
                    y: 0
                };

                // The tooltip is initially hidden
                this.isHidden = true;



                // Public property for getting the shared state.
                this.split = options.split && !chart.inverted;
                this.shared = options.shared || this.split;

            },

            /**
             * Destroy the single tooltips in a split tooltip.
             * If the tooltip is active then it is not destroyed, unless forced to.
             * @param  {boolean} force Force destroy all tooltips.
             * @return {undefined}
             */
            cleanSplit: function(force) {
                each(this.chart.series, function(series) {
                    var tt = series && series.tt;
                    if (tt) {
                        if (!tt.isActive || force) {
                            series.tt = tt.destroy();
                        } else {
                            tt.isActive = false;
                        }
                    }
                });
            },




            /**
             * Create the Tooltip label element if it doesn't exist, then return the
             * label.
             */
            getLabel: function() {

                var renderer = this.chart.renderer,
                    options = this.options;

                if (!this.label) {
                    // Create the label
                    if (this.split) {
                        this.label = renderer.g('tooltip');
                    } else {
                        this.label = renderer.label(
                                '',
                                0,
                                0,
                                options.shape || 'callout',
                                null,
                                null,
                                options.useHTML,
                                null,
                                'tooltip'
                            )
                            .attr({
                                padding: options.padding,
                                r: options.borderRadius
                            });


                        this.label
                            .attr({
                                'fill': options.backgroundColor,
                                'stroke-width': options.borderWidth
                            })
                            // #2301, #2657
                            .css(options.style)
                            .shadow(options.shadow);

                    }



                    this.label
                        .attr({
                            zIndex: 8
                        })
                        .add();
                }
                return this.label;
            },

            update: function(options) {
                this.destroy();
                // Update user options (#6218)
                merge(true, this.chart.options.tooltip.userOptions, options);
                this.init(this.chart, merge(true, this.options, options));
            },

            /**
             * Destroy the tooltip and its elements.
             */
            destroy: function() {
                // Destroy and clear local variables
                if (this.label) {
                    this.label = this.label.destroy();
                }
                if (this.split && this.tt) {
                    this.cleanSplit(this.chart, true);
                    this.tt = this.tt.destroy();
                }
                clearTimeout(this.hideTimer);
                clearTimeout(this.tooltipTimeout);
            },

            /**
             * Provide a soft movement for the tooltip
             *
             * @param {Number} x
             * @param {Number} y
             * @private
             */
            move: function(x, y, anchorX, anchorY) {
                var tooltip = this,
                    now = tooltip.now,
                    animate = tooltip.options.animation !== false &&
                    !tooltip.isHidden &&
                    // When we get close to the target position, abort animation and
                    // land on the right place (#3056)
                    (Math.abs(x - now.x) > 1 || Math.abs(y - now.y) > 1),
                    skipAnchor = tooltip.followPointer || tooltip.len > 1;

                // Get intermediate values for animation
                extend(now, {
                    x: animate ? (2 * now.x + x) / 3 : x,
                    y: animate ? (now.y + y) / 2 : y,
                    anchorX: skipAnchor ?
                        undefined : animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,
                    anchorY: skipAnchor ?
                        undefined : animate ? (now.anchorY + anchorY) / 2 : anchorY
                });

                // Move to the intermediate value
                tooltip.getLabel().attr(now);


                // Run on next tick of the mouse tracker
                if (animate) {

                    // Never allow two timeouts
                    clearTimeout(this.tooltipTimeout);

                    // Set the fixed interval ticking for the smooth tooltip
                    this.tooltipTimeout = setTimeout(function() {
                        // The interval function may still be running during destroy,
                        // so check that the chart is really there before calling.
                        if (tooltip) {
                            tooltip.move(x, y, anchorX, anchorY);
                        }
                    }, 32);

                }
            },

            /**
             * Hide the tooltip
             */
            hide: function(delay) {
                var tooltip = this;
                // disallow duplicate timers (#1728, #1766)
                clearTimeout(this.hideTimer);
                delay = pick(delay, this.options.hideDelay, 500);
                if (!this.isHidden) {
                    this.hideTimer = syncTimeout(function() {
                        tooltip.getLabel()[delay ? 'fadeOut' : 'hide']();
                        tooltip.isHidden = true;
                    }, delay);
                }
            },

            /**
             * Extendable method to get the anchor position of the tooltip
             * from a point or set of points
             */
            getAnchor: function(points, mouseEvent) {
                var ret,
                    chart = this.chart,
                    inverted = chart.inverted,
                    plotTop = chart.plotTop,
                    plotLeft = chart.plotLeft,
                    plotX = 0,
                    plotY = 0,
                    yAxis,
                    xAxis;

                points = splat(points);

                // Pie uses a special tooltipPos
                ret = points[0].tooltipPos;

                // When tooltip follows mouse, relate the position to the mouse
                if (this.followPointer && mouseEvent) {
                    if (mouseEvent.chartX === undefined) {
                        mouseEvent = chart.pointer.normalize(mouseEvent);
                    }
                    ret = [
                        mouseEvent.chartX - chart.plotLeft,
                        mouseEvent.chartY - plotTop
                    ];
                }
                // When shared, use the average position
                if (!ret) {
                    each(points, function(point) {
                        yAxis = point.series.yAxis;
                        xAxis = point.series.xAxis;
                        plotX += point.plotX +
                            (!inverted && xAxis ? xAxis.left - plotLeft : 0);
                        plotY +=
                            (
                                point.plotLow ?
                                (point.plotLow + point.plotHigh) / 2 :
                                point.plotY
                            ) +
                            (!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151
                    });

                    plotX /= points.length;
                    plotY /= points.length;

                    ret = [
                        inverted ? chart.plotWidth - plotY : plotX,
                        this.shared && !inverted && points.length > 1 && mouseEvent ?
                        // place shared tooltip next to the mouse (#424)
                        mouseEvent.chartY - plotTop :
                        inverted ? chart.plotHeight - plotX : plotY
                    ];
                }

                return map(ret, Math.round);
            },

            /**
             * Place the tooltip in a chart without spilling over
             * and not covering the point it self.
             */
            getPosition: function(boxWidth, boxHeight, point) {

                var chart = this.chart,
                    distance = this.distance,
                    ret = {},
                    // Don't use h if chart isn't inverted (#7242)
                    h = (chart.inverted && point.h) || 0, // #4117
                    swapped,
                    first = ['y', chart.chartHeight, boxHeight,
                        point.plotY + chart.plotTop, chart.plotTop,
                        chart.plotTop + chart.plotHeight
                    ],
                    second = ['x', chart.chartWidth, boxWidth,
                        point.plotX + chart.plotLeft, chart.plotLeft,
                        chart.plotLeft + chart.plotWidth
                    ],
                    // The far side is right or bottom
                    preferFarSide = !this.followPointer && pick(
                        point.ttBelow, !chart.inverted === !!point.negative
                    ), // #4984

                    /**
                     * Handle the preferred dimension. When the preferred dimension is
                     * tooltip on top or bottom of the point, it will look for space
                     * there.
                     */
                    firstDimension = function(
                        dim,
                        outerSize,
                        innerSize,
                        point,
                        min,
                        max
                    ) {
                        var roomLeft = innerSize < point - distance,
                            roomRight = point + distance + innerSize < outerSize,
                            alignedLeft = point - distance - innerSize,
                            alignedRight = point + distance;

                        if (preferFarSide && roomRight) {
                            ret[dim] = alignedRight;
                        } else if (!preferFarSide && roomLeft) {
                            ret[dim] = alignedLeft;
                        } else if (roomLeft) {
                            ret[dim] = Math.min(
                                max - innerSize,
                                alignedLeft - h < 0 ? alignedLeft : alignedLeft - h
                            );
                        } else if (roomRight) {
                            ret[dim] = Math.max(
                                min,
                                alignedRight + h + innerSize > outerSize ?
                                alignedRight :
                                alignedRight + h
                            );
                        } else {
                            return false;
                        }
                    },
                    /**
                     * Handle the secondary dimension. If the preferred dimension is
                     * tooltip on top or bottom of the point, the second dimension is to
                     * align the tooltip above the point, trying to align center but
                     * allowing left or right align within the chart box.
                     */
                    secondDimension = function(dim, outerSize, innerSize, point) {
                        var retVal;

                        // Too close to the edge, return false and swap dimensions
                        if (point < distance || point > outerSize - distance) {
                            retVal = false;
                            // Align left/top
                        } else if (point < innerSize / 2) {
                            ret[dim] = 1;
                            // Align right/bottom
                        } else if (point > outerSize - innerSize / 2) {
                            ret[dim] = outerSize - innerSize - 2;
                            // Align center
                        } else {
                            ret[dim] = point - innerSize / 2;
                        }
                        return retVal;
                    },
                    /**
                     * Swap the dimensions
                     */
                    swap = function(count) {
                        var temp = first;
                        first = second;
                        second = temp;
                        swapped = count;
                    },
                    run = function() {
                        if (firstDimension.apply(0, first) !== false) {
                            if (
                                secondDimension.apply(0, second) === false &&
                                !swapped
                            ) {
                                swap(true);
                                run();
                            }
                        } else if (!swapped) {
                            swap(true);
                            run();
                        } else {
                            ret.x = ret.y = 0;
                        }
                    };

                // Under these conditions, prefer the tooltip on the side of the point
                if (chart.inverted || this.len > 1) {
                    swap();
                }
                run();

                return ret;

            },

            /**
             * In case no user defined formatter is given, this will be used. Note that
             * the context here is an object holding point, series, x, y etc.
             *
             * @returns {String|Array<String>}
             */
            defaultFormatter: function(tooltip) {
                var items = this.points || splat(this),
                    s;

                // Build the header
                s = [tooltip.tooltipFooterHeaderFormatter(items[0])];

                // build the values
                s = s.concat(tooltip.bodyFormatter(items));

                // footer
                s.push(tooltip.tooltipFooterHeaderFormatter(items[0], true));

                return s;
            },

            /**
             * Refresh the tooltip's text and position.
             * @param {Object|Array} pointOrPoints Rither a point or an array of points
             */
            refresh: function(pointOrPoints, mouseEvent) {
                var tooltip = this,
                    label,
                    options = tooltip.options,
                    x,
                    y,
                    point = pointOrPoints,
                    anchor,
                    textConfig = {},
                    text,
                    pointConfig = [],
                    formatter = options.formatter || tooltip.defaultFormatter,
                    shared = tooltip.shared,
                    currentSeries;

                if (!options.enabled) {
                    return;
                }

                clearTimeout(this.hideTimer);

                // get the reference point coordinates (pie charts use tooltipPos)
                tooltip.followPointer = splat(point)[0].series.tooltipOptions
                    .followPointer;
                anchor = tooltip.getAnchor(point, mouseEvent);
                x = anchor[0];
                y = anchor[1];

                // shared tooltip, array is sent over
                if (shared && !(point.series && point.series.noSharedTooltip)) {
                    each(point, function(item) {
                        item.setState('hover');

                        pointConfig.push(item.getLabelConfig());
                    });

                    textConfig = {
                        x: point[0].category,
                        y: point[0].y
                    };
                    textConfig.points = pointConfig;
                    point = point[0];

                    // single point tooltip
                } else {
                    textConfig = point.getLabelConfig();
                }
                this.len = pointConfig.length; // #6128
                text = formatter.call(textConfig, tooltip);

                // register the current series
                currentSeries = point.series;
                this.distance = pick(currentSeries.tooltipOptions.distance, 16);

                // update the inner HTML
                if (text === false) {
                    this.hide();
                } else {

                    label = tooltip.getLabel();

                    // show it
                    if (tooltip.isHidden) {
                        label.attr({
                            opacity: 1
                        }).show();
                    }

                    // update text
                    if (tooltip.split) {
                        this.renderSplit(text, splat(pointOrPoints));
                    } else {

                        // Prevent the tooltip from flowing over the chart box (#6659)

                        if (!options.style.width) {

                            label.css({
                                width: this.chart.spacingBox.width
                            });

                        }


                        label.attr({
                            text: text && text.join ? text.join('') : text
                        });

                        // Set the stroke color of the box to reflect the point
                        label.removeClass(/highcharts-color-[\d]+/g)
                            .addClass(
                                'highcharts-color-' +
                                pick(point.colorIndex, currentSeries.colorIndex)
                            );


                        label.attr({
                            stroke: (
                                options.borderColor ||
                                point.color ||
                                currentSeries.color ||
                                '#666666'
                            )
                        });


                        tooltip.updatePosition({
                            plotX: x,
                            plotY: y,
                            negative: point.negative,
                            ttBelow: point.ttBelow,
                            h: anchor[2] || 0
                        });
                    }

                    this.isHidden = false;
                }
            },

            /**
             * Render the split tooltip. Loops over each point's text and adds
             * a label next to the point, then uses the distribute function to 
             * find best non-overlapping positions.
             */
            renderSplit: function(labels, points) {
                var tooltip = this,
                    boxes = [],
                    chart = this.chart,
                    ren = chart.renderer,
                    rightAligned = true,
                    options = this.options,
                    headerHeight = 0,
                    tooltipLabel = this.getLabel();

                // Graceful degradation for legacy formatters
                if (H.isString(labels)) {
                    labels = [false, labels];
                }
                // Create the individual labels for header and points, ignore footer
                each(labels.slice(0, points.length + 1), function(str, i) {
                    if (str !== false) {
                        var point = points[i - 1] ||
                            // Item 0 is the header. Instead of this, we could also
                            // use the crosshair label
                            {
                                isHeader: true,
                                plotX: points[0].plotX
                            },
                            owner = point.series || tooltip,
                            tt = owner.tt,
                            series = point.series || {},
                            colorClass = 'highcharts-color-' + pick(
                                point.colorIndex,
                                series.colorIndex,
                                'none'
                            ),
                            target,
                            x,
                            bBox,
                            boxWidth;

                        // Store the tooltip referance on the series
                        if (!tt) {
                            owner.tt = tt = ren.label(
                                    null,
                                    null,
                                    null,
                                    'callout',
                                    null,
                                    null,
                                    options.useHTML
                                )
                                .addClass('highcharts-tooltip-box ' + colorClass)
                                .attr({
                                    'padding': options.padding,
                                    'r': options.borderRadius,

                                    'fill': options.backgroundColor,
                                    'stroke': (
                                        options.borderColor ||
                                        point.color ||
                                        series.color ||
                                        '#333333'
                                    ),
                                    'stroke-width': options.borderWidth

                                })
                                .add(tooltipLabel);
                        }

                        tt.isActive = true;
                        tt.attr({
                            text: str
                        });

                        tt.css(options.style)
                            .shadow(options.shadow);


                        // Get X position now, so we can move all to the other side in
                        // case of overflow
                        bBox = tt.getBBox();
                        boxWidth = bBox.width + tt.strokeWidth();
                        if (point.isHeader) {
                            headerHeight = bBox.height;
                            x = Math.max(
                                0, // No left overflow
                                Math.min(
                                    point.plotX + chart.plotLeft - boxWidth / 2,
                                    // No right overflow (#5794)
                                    chart.chartWidth - boxWidth
                                )
                            );
                        } else {
                            x = point.plotX + chart.plotLeft -
                                pick(options.distance, 16) - boxWidth;
                        }


                        // If overflow left, we don't use this x in the next loop
                        if (x < 0) {
                            rightAligned = false;
                        }

                        // Prepare for distribution
                        target = (point.series && point.series.yAxis &&
                            point.series.yAxis.pos) + (point.plotY || 0);
                        target -= chart.plotTop;
                        boxes.push({
                            target: point.isHeader ?
                                chart.plotHeight + headerHeight : target,
                            rank: point.isHeader ? 1 : 0,
                            size: owner.tt.getBBox().height + 1,
                            point: point,
                            x: x,
                            tt: tt
                        });
                    }
                });

                // Clean previous run (for missing points)
                this.cleanSplit();

                // Distribute and put in place
                H.distribute(boxes, chart.plotHeight + headerHeight);
                each(boxes, function(box) {
                    var point = box.point,
                        series = point.series;

                    // Put the label in place
                    box.tt.attr({
                        visibility: box.pos === undefined ? 'hidden' : 'inherit',
                        x: (rightAligned || point.isHeader ?
                            box.x :
                            point.plotX + chart.plotLeft + pick(options.distance, 16)),
                        y: box.pos + chart.plotTop,
                        anchorX: point.isHeader ?
                            point.plotX + chart.plotLeft : point.plotX + series.xAxis.pos,
                        anchorY: point.isHeader ?
                            box.pos + chart.plotTop - 15 : point.plotY + series.yAxis.pos
                    });
                });
            },

            /**
             * Find the new position and perform the move
             */
            updatePosition: function(point) {
                var chart = this.chart,
                    label = this.getLabel(),
                    pos = (this.options.positioner || this.getPosition).call(
                        this,
                        label.width,
                        label.height,
                        point
                    );

                // do the move
                this.move(
                    Math.round(pos.x),
                    Math.round(pos.y || 0), // can be undefined (#3977) 
                    point.plotX + chart.plotLeft,
                    point.plotY + chart.plotTop
                );
            },

            /**
             * Get the optimal date format for a point, based on a range.
             * @param  {number} range - The time range
             * @param  {number|Date} date - The date of the point in question
             * @param  {number} startOfWeek - An integer representing the first day of
             * the week, where 0 is Sunday
             * @param  {Object} dateTimeLabelFormats - A map of time units to formats
             * @return {string} - the optimal date format for a point
             */
            getDateFormat: function(range, date, startOfWeek, dateTimeLabelFormats) {
                var dateStr = dateFormat('%m-%d %H:%M:%S.%L', date),
                    format,
                    n,
                    blank = '01-01 00:00:00.000',
                    strpos = {
                        millisecond: 15,
                        second: 12,
                        minute: 9,
                        hour: 6,
                        day: 3
                    },
                    lastN = 'millisecond'; // for sub-millisecond data, #4223
                for (n in timeUnits) {

                    // If the range is exactly one week and we're looking at a
                    // Sunday/Monday, go for the week format
                    if (
                        range === timeUnits.week &&
                        +dateFormat('%w', date) === startOfWeek &&
                        dateStr.substr(6) === blank.substr(6)
                    ) {
                        n = 'week';
                        break;
                    }

                    // The first format that is too great for the range
                    if (timeUnits[n] > range) {
                        n = lastN;
                        break;
                    }

                    // If the point is placed every day at 23:59, we need to show
                    // the minutes as well. #2637.
                    if (
                        strpos[n] &&
                        dateStr.substr(strpos[n]) !== blank.substr(strpos[n])
                    ) {
                        break;
                    }

                    // Weeks are outside the hierarchy, only apply them on
                    // Mondays/Sundays like in the first condition
                    if (n !== 'week') {
                        lastN = n;
                    }
                }

                if (n) {
                    format = dateTimeLabelFormats[n];
                }

                return format;
            },

            /**
             * Get the best X date format based on the closest point range on the axis.
             */
            getXDateFormat: function(point, options, xAxis) {
                var xDateFormat,
                    dateTimeLabelFormats = options.dateTimeLabelFormats,
                    closestPointRange = xAxis && xAxis.closestPointRange;

                if (closestPointRange) {
                    xDateFormat = this.getDateFormat(
                        closestPointRange,
                        point.x,
                        xAxis.options.startOfWeek,
                        dateTimeLabelFormats
                    );
                } else {
                    xDateFormat = dateTimeLabelFormats.day;
                }

                return xDateFormat || dateTimeLabelFormats.year; // #2546, 2581
            },

            /**
             * Format the footer/header of the tooltip
             * #3397: abstraction to enable formatting of footer and header
             */
            tooltipFooterHeaderFormatter: function(labelConfig, isFooter) {
                var footOrHead = isFooter ? 'footer' : 'header',
                    series = labelConfig.series,
                    tooltipOptions = series.tooltipOptions,
                    xDateFormat = tooltipOptions.xDateFormat,
                    xAxis = series.xAxis,
                    isDateTime = (
                        xAxis &&
                        xAxis.options.type === 'datetime' &&
                        isNumber(labelConfig.key)
                    ),
                    formatString = tooltipOptions[footOrHead + 'Format'];

                // Guess the best date format based on the closest point distance (#568,
                // #3418)
                if (isDateTime && !xDateFormat) {
                    xDateFormat = this.getXDateFormat(
                        labelConfig,
                        tooltipOptions,
                        xAxis
                    );
                }

                // Insert the footer date format if any
                if (isDateTime && xDateFormat) {
                    each(
                        (labelConfig.point && labelConfig.point.tooltipDateKeys) || ['key'],
                        function(key) {
                            formatString = formatString.replace(
                                '{point.' + key + '}',
                                '{point.' + key + ':' + xDateFormat + '}'
                            );
                        }
                    );
                }

                return format(formatString, {
                    point: labelConfig,
                    series: series
                });
            },

            /**
             * Build the body (lines) of the tooltip by iterating over the items and
             * returning one entry for each item, abstracting this functionality allows
             * to easily overwrite and extend it.
             */
            bodyFormatter: function(items) {
                return map(items, function(item) {
                    var tooltipOptions = item.series.tooltipOptions;
                    return (
                        tooltipOptions[
                            (item.point.formatPrefix || 'point') + 'Formatter'
                        ] ||
                        item.point.tooltipFormatter
                    ).call(
                        item.point,
                        tooltipOptions[(item.point.formatPrefix || 'point') + 'Format']
                    );
                });
            }

        };

    }(Highcharts));
    (function(Highcharts) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var H = Highcharts,
            addEvent = H.addEvent,
            attr = H.attr,
            charts = H.charts,
            color = H.color,
            css = H.css,
            defined = H.defined,
            each = H.each,
            extend = H.extend,
            find = H.find,
            fireEvent = H.fireEvent,
            isObject = H.isObject,
            offset = H.offset,
            pick = H.pick,
            splat = H.splat,
            Tooltip = H.Tooltip;

        /**
         * The mouse and touch tracker object. Each {@link Chart} item has one
         * assosiated Pointer item that can be accessed from the  {@link Chart.pointer}
         * property.
         *
         * @class
         * @param  {Chart} chart
         *         The Chart instance.
         * @param  {Options} options
         *         The root options object. The pointer uses options from the chart and
         *         tooltip structures.
         */
        Highcharts.Pointer = function(chart, options) {
            this.init(chart, options);
        };

        Highcharts.Pointer.prototype = {
            /**
             * Initialize the Pointer.
             *
             * @private
             */
            init: function(chart, options) {

                // Store references
                this.options = options;
                this.chart = chart;

                // Do we need to handle click on a touch device?
                this.runChartClick = options.chart.events && !!options.chart.events.click;

                this.pinchDown = [];
                this.lastValidTouch = {};

                if (Tooltip) {
                    chart.tooltip = new Tooltip(chart, options.tooltip);
                    this.followTouchMove = pick(options.tooltip.followTouchMove, true);
                }

                this.setDOMEvents();
            },

            /**
             * Resolve the zoomType option, this is reset on all touch start and mouse
             * down events.
             *
             * @private
             */
            zoomOption: function(e) {
                var chart = this.chart,
                    options = chart.options.chart,
                    zoomType = options.zoomType || '',
                    inverted = chart.inverted,
                    zoomX,
                    zoomY;

                // Look for the pinchType option
                if (/touch/.test(e.type)) {
                    zoomType = pick(options.pinchType, zoomType);
                }

                this.zoomX = zoomX = /x/.test(zoomType);
                this.zoomY = zoomY = /y/.test(zoomType);
                this.zoomHor = (zoomX && !inverted) || (zoomY && inverted);
                this.zoomVert = (zoomY && !inverted) || (zoomX && inverted);
                this.hasZoom = zoomX || zoomY;
            },

            /**
             * @typedef  {Object} PointerEvent
             *           A native browser mouse or touch event, extended with position
             *           information relative to the {@link Chart.container}.
             * @property {Number} chartX
             *           The X coordinate of the pointer interaction relative to the
             *           chart.
             * @property {Number} chartY
             *           The Y coordinate of the pointer interaction relative to the 
             *           chart.
             * 
             */
            /**
             * Takes a browser event object and extends it with custom Highcharts
             * properties `chartX` and `chartY` in order to work on the internal 
             * coordinate system.
             * 
             * @param  {Object} e
             *         The event object in standard browsers.
             *
             * @return {PointerEvent}
             *         A browser event with extended properties `chartX` and `chartY`.
             */
            normalize: function(e, chartPosition) {
                var ePos;

                // iOS (#2757)
                ePos = e.touches ? (e.touches.length ? e.touches.item(0) : e.changedTouches[0]) : e;

                // Get mouse position
                if (!chartPosition) {
                    this.chartPosition = chartPosition = offset(this.chart.container);
                }

                return extend(e, {
                    chartX: Math.round(ePos.pageX - chartPosition.left),
                    chartY: Math.round(ePos.pageY - chartPosition.top)
                });
            },

            /**
             * Get the click position in terms of axis values.
             *
             * @param  {PointerEvent} e
             *         A pointer event, extended with `chartX` and `chartY`
             *         properties.
             */
            getCoordinates: function(e) {
                var coordinates = {
                    xAxis: [],
                    yAxis: []
                };

                each(this.chart.axes, function(axis) {
                    coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({
                        axis: axis,
                        value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY'])
                    });
                });
                return coordinates;
            },
            /**
             * Finds the closest point to a set of coordinates, using the k-d-tree
             * algorithm.
             *
             * @param  {Array.<Series>} series
             *         All the series to search in.
             * @param  {boolean} shared
             *         Whether it is a shared tooltip or not.
             * @param  {object} coordinates
             *         Chart coordinates of the pointer.
             * @param  {number} coordinates.chartX
             * @param  {number} coordinates.chartY
             *
             * @return {Point|undefined} The point closest to given coordinates.
             */
            findNearestKDPoint: function(series, shared, coordinates) {
                var closest,
                    sort = function(p1, p2) {
                        var isCloserX = p1.distX - p2.distX,
                            isCloser = p1.dist - p2.dist,
                            isAbove =
                            (p2.series.group && p2.series.group.zIndex) -
                            (p1.series.group && p1.series.group.zIndex),
                            result;

                        // We have two points which are not in the same place on xAxis
                        // and shared tooltip:
                        if (isCloserX !== 0 && shared) { // #5721
                            result = isCloserX;
                            // Points are not exactly in the same place on x/yAxis:
                        } else if (isCloser !== 0) {
                            result = isCloser;
                            // The same xAxis and yAxis position, sort by z-index:
                        } else if (isAbove !== 0) {
                            result = isAbove;
                            // The same zIndex, sort by array index:
                        } else {
                            result = p1.series.index > p2.series.index ? -1 : 1;
                        }
                        return result;
                    };
                each(series, function(s) {
                    var noSharedTooltip = s.noSharedTooltip && shared,
                        compareX = (!noSharedTooltip &&
                            s.options.findNearestPointBy.indexOf('y') < 0
                        ),
                        point = s.searchPoint(
                            coordinates,
                            compareX
                        );
                    if (
                        // Check that we actually found a point on the series.
                        isObject(point, true) &&
                        // Use the new point if it is closer.
                        (!isObject(closest, true) || (sort(closest, point) > 0))
                    ) {
                        closest = point;
                    }
                });
                return closest;
            },
            getPointFromEvent: function(e) {
                var target = e.target,
                    point;

                while (target && !point) {
                    point = target.point;
                    target = target.parentNode;
                }
                return point;
            },

            getChartCoordinatesFromPoint: function(point, inverted) {
                var series = point.series,
                    xAxis = series.xAxis,
                    yAxis = series.yAxis,
                    plotX = pick(point.clientX, point.plotX);

                if (xAxis && yAxis) {
                    return inverted ? {
                        chartX: xAxis.len + xAxis.pos - plotX,
                        chartY: yAxis.len + yAxis.pos - point.plotY
                    } : {
                        chartX: plotX + xAxis.pos,
                        chartY: point.plotY + yAxis.pos
                    };
                }
            },

            /**
             * Calculates what is the current hovered point/points and series.
             *
             * @private
             *
             * @param  {undefined|Point} existingHoverPoint
             *         The point currrently beeing hovered.
             * @param  {undefined|Series} existingHoverSeries
             *         The series currently beeing hovered.
             * @param  {Array.<Series>} series
             *         All the series in the chart.
             * @param  {boolean} isDirectTouch
             *         Is the pointer directly hovering the point.
             * @param  {boolean} shared
             *         Whether it is a shared tooltip or not.
             * @param  {object} coordinates
             *         Chart coordinates of the pointer.
             * @param  {number} coordinates.chartX
             * @param  {number} coordinates.chartY
             * 
             * @return {object}
             *         Object containing resulting hover data.
             */
            getHoverData: function(
                existingHoverPoint,
                existingHoverSeries,
                series,
                isDirectTouch,
                shared,
                coordinates,
                params
            ) {
                var hoverPoint,
                    hoverPoints = [],
                    hoverSeries = existingHoverSeries,
                    isBoosting = params && params.isBoosting,
                    useExisting = !!(isDirectTouch && existingHoverPoint),
                    notSticky = hoverSeries && !hoverSeries.stickyTracking,
                    filter = function(s) {
                        return (
                            s.visible &&
                            !(!shared && s.directTouch) && // #3821
                            pick(s.options.enableMouseTracking, true)
                        );
                    },
                    // Which series to look in for the hover point
                    searchSeries = notSticky ?
                    // Only search on hovered series if it has stickyTracking false
                    [hoverSeries] :
                    // Filter what series to look in.
                    H.grep(series, function(s) {
                        return filter(s) && s.stickyTracking;
                    });

                // Use existing hovered point or find the one closest to coordinates.
                hoverPoint = useExisting ?
                    existingHoverPoint :
                    this.findNearestKDPoint(searchSeries, shared, coordinates);

                // Assign hover series
                hoverSeries = hoverPoint && hoverPoint.series;

                // If we have a hoverPoint, assign hoverPoints.
                if (hoverPoint) {
                    // When tooltip is shared, it displays more than one point
                    if (shared && !hoverSeries.noSharedTooltip) {
                        searchSeries = H.grep(series, function(s) {
                            return filter(s) && !s.noSharedTooltip;
                        });

                        // Get all points with the same x value as the hoverPoint
                        each(searchSeries, function(s) {
                            var point = find(s.points, function(p) {
                                return p.x === hoverPoint.x && !p.isNull;
                            });
                            if (isObject(point)) {
                                /*
                                 * Boost returns a minimal point. Convert it to a usable
                                 * point for tooltip and states.
                                 */
                                if (isBoosting) {
                                    point = s.getPoint(point);
                                }
                                hoverPoints.push(point);
                            }
                        });
                    } else {
                        hoverPoints.push(hoverPoint);
                    }
                }
                return {
                    hoverPoint: hoverPoint,
                    hoverSeries: hoverSeries,
                    hoverPoints: hoverPoints
                };
            },
            /**
             * With line type charts with a single tracker, get the point closest to the
             * mouse. Run Point.onMouseOver and display tooltip for the point or points.
             *
             * @private
             */
            runPointActions: function(e, p) {
                var pointer = this,
                    chart = pointer.chart,
                    series = chart.series,
                    tooltip = chart.tooltip && chart.tooltip.options.enabled ?
                    chart.tooltip :
                    undefined,
                    shared = tooltip ? tooltip.shared : false,
                    hoverPoint = p || chart.hoverPoint,
                    hoverSeries = hoverPoint && hoverPoint.series || chart.hoverSeries,
                    // onMouseOver or already hovering a series with directTouch
                    isDirectTouch = !!p || (
                        (hoverSeries && hoverSeries.directTouch) &&
                        pointer.isDirectTouch
                    ),
                    hoverData = this.getHoverData(
                        hoverPoint,
                        hoverSeries,
                        series,
                        isDirectTouch,
                        shared,
                        e, {
                            isBoosting: chart.isBoosting
                        }
                    ),
                    useSharedTooltip,
                    followPointer,
                    anchor,
                    points;

                // Update variables from hoverData.
                hoverPoint = hoverData.hoverPoint;
                points = hoverData.hoverPoints;
                hoverSeries = hoverData.hoverSeries;
                followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer;
                useSharedTooltip = shared && hoverSeries && !hoverSeries.noSharedTooltip;

                // Refresh tooltip for kdpoint if new hover point or tooltip was hidden
                // #3926, #4200
                if (
                    hoverPoint &&
                    // !(hoverSeries && hoverSeries.directTouch) &&
                    (hoverPoint !== chart.hoverPoint || (tooltip && tooltip.isHidden))
                ) {
                    each(chart.hoverPoints || [], function(p) {
                        if (H.inArray(p, points) === -1) {
                            p.setState();
                        }
                    });
                    // Do mouseover on all points (#3919, #3985, #4410, #5622)
                    each(points || [], function(p) {
                        p.setState('hover');
                    });
                    // set normal state to previous series
                    if (chart.hoverSeries !== hoverSeries) {
                        hoverSeries.onMouseOver();
                    }

                    // If tracking is on series in stead of on each point, 
                    // fire mouseOver on hover point. // #4448
                    if (chart.hoverPoint) {
                        chart.hoverPoint.firePointEvent('mouseOut');
                    }

                    // Hover point may have been destroyed in the event handlers (#7127)
                    if (!hoverPoint.series) {
                        return;
                    }

                    hoverPoint.firePointEvent('mouseOver');
                    chart.hoverPoints = points;
                    chart.hoverPoint = hoverPoint;
                    // Draw tooltip if necessary
                    if (tooltip) {
                        tooltip.refresh(useSharedTooltip ? points : hoverPoint, e);
                    }
                    // Update positions (regardless of kdpoint or hoverPoint)
                } else if (followPointer && tooltip && !tooltip.isHidden) {
                    anchor = tooltip.getAnchor([{}], e);
                    tooltip.updatePosition({
                        plotX: anchor[0],
                        plotY: anchor[1]
                    });
                }

                // Start the event listener to pick up the tooltip and crosshairs
                if (!pointer.unDocMouseMove) {
                    pointer.unDocMouseMove = addEvent(
                        chart.container.ownerDocument,
                        'mousemove',
                        function(e) {
                            var chart = charts[H.hoverChartIndex];
                            if (chart) {
                                chart.pointer.onDocumentMouseMove(e);
                            }
                        }
                    );
                }

                // Issues related to crosshair #4927, #5269 #5066, #5658
                each(chart.axes, function drawAxisCrosshair(axis) {
                    var snap = pick(axis.crosshair.snap, true),
                        point = !snap ?
                        undefined :
                        H.find(points, function(p) {
                            return p.series[axis.coll] === axis;
                        });

                    // Axis has snapping crosshairs, and one of the hover points belongs
                    // to axis. Always call drawCrosshair when it is not snap.
                    if (point || !snap) {
                        axis.drawCrosshair(e, point);
                        // Axis has snapping crosshairs, but no hover point belongs to axis
                    } else {
                        axis.hideCrosshair();
                    }
                });
            },

            /**
             * Reset the tracking by hiding the tooltip, the hover series state and the
             * hover point
             *
             * @param allowMove {Boolean}
             *        Instead of destroying the tooltip altogether, allow moving it if
             *        possible.
             */
            reset: function(allowMove, delay) {
                var pointer = this,
                    chart = pointer.chart,
                    hoverSeries = chart.hoverSeries,
                    hoverPoint = chart.hoverPoint,
                    hoverPoints = chart.hoverPoints,
                    tooltip = chart.tooltip,
                    tooltipPoints = tooltip && tooltip.shared ? hoverPoints : hoverPoint;

                // Check if the points have moved outside the plot area (#1003, #4736, #5101)
                if (allowMove && tooltipPoints) {
                    each(splat(tooltipPoints), function(point) {
                        if (point.series.isCartesian && point.plotX === undefined) {
                            allowMove = false;
                        }
                    });
                }

                // Just move the tooltip, #349
                if (allowMove) {
                    if (tooltip && tooltipPoints) {
                        tooltip.refresh(tooltipPoints);
                        if (hoverPoint) { // #2500
                            hoverPoint.setState(hoverPoint.state, true);
                            each(chart.axes, function(axis) {
                                if (axis.crosshair) {
                                    axis.drawCrosshair(null, hoverPoint);
                                }
                            });
                        }
                    }

                    // Full reset
                } else {

                    if (hoverPoint) {
                        hoverPoint.onMouseOut();
                    }

                    if (hoverPoints) {
                        each(hoverPoints, function(point) {
                            point.setState();
                        });
                    }

                    if (hoverSeries) {
                        hoverSeries.onMouseOut();
                    }

                    if (tooltip) {
                        tooltip.hide(delay);
                    }

                    if (pointer.unDocMouseMove) {
                        pointer.unDocMouseMove = pointer.unDocMouseMove();
                    }

                    // Remove crosshairs
                    each(chart.axes, function(axis) {
                        axis.hideCrosshair();
                    });

                    pointer.hoverX = chart.hoverPoints = chart.hoverPoint = null;
                }
            },

            /**
             * Scale series groups to a certain scale and translation.
             *
             * @private
             */
            scaleGroups: function(attribs, clip) {

                var chart = this.chart,
                    seriesAttribs;

                // Scale each series
                each(chart.series, function(series) {
                    seriesAttribs = attribs || series.getPlotBox(); // #1701
                    if (series.xAxis && series.xAxis.zoomEnabled && series.group) {
                        series.group.attr(seriesAttribs);
                        if (series.markerGroup) {
                            series.markerGroup.attr(seriesAttribs);
                            series.markerGroup.clip(clip ? chart.clipRect : null);
                        }
                        if (series.dataLabelsGroup) {
                            series.dataLabelsGroup.attr(seriesAttribs);
                        }
                    }
                });

                // Clip
                chart.clipRect.attr(clip || chart.clipBox);
            },

            /**
             * Start a drag operation.
             *
             * @private
             */
            dragStart: function(e) {
                var chart = this.chart;

                // Record the start position
                chart.mouseIsDown = e.type;
                chart.cancelClick = false;
                chart.mouseDownX = this.mouseDownX = e.chartX;
                chart.mouseDownY = this.mouseDownY = e.chartY;
            },

            /**
             * Perform a drag operation in response to a mousemove event while the mouse
             * is down.
             *
             * @private
             */
            drag: function(e) {

                var chart = this.chart,
                    chartOptions = chart.options.chart,
                    chartX = e.chartX,
                    chartY = e.chartY,
                    zoomHor = this.zoomHor,
                    zoomVert = this.zoomVert,
                    plotLeft = chart.plotLeft,
                    plotTop = chart.plotTop,
                    plotWidth = chart.plotWidth,
                    plotHeight = chart.plotHeight,
                    clickedInside,
                    size,
                    selectionMarker = this.selectionMarker,
                    mouseDownX = this.mouseDownX,
                    mouseDownY = this.mouseDownY,
                    panKey = chartOptions.panKey && e[chartOptions.panKey + 'Key'];

                // If the device supports both touch and mouse (like IE11), and we are touch-dragging
                // inside the plot area, don't handle the mouse event. #4339.
                if (selectionMarker && selectionMarker.touch) {
                    return;
                }

                // If the mouse is outside the plot area, adjust to cooordinates
                // inside to prevent the selection marker from going outside
                if (chartX < plotLeft) {
                    chartX = plotLeft;
                } else if (chartX > plotLeft + plotWidth) {
                    chartX = plotLeft + plotWidth;
                }

                if (chartY < plotTop) {
                    chartY = plotTop;
                } else if (chartY > plotTop + plotHeight) {
                    chartY = plotTop + plotHeight;
                }

                // determine if the mouse has moved more than 10px
                this.hasDragged = Math.sqrt(
                    Math.pow(mouseDownX - chartX, 2) +
                    Math.pow(mouseDownY - chartY, 2)
                );

                if (this.hasDragged > 10) {
                    clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);

                    // make a selection
                    if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside && !panKey) {
                        if (!selectionMarker) {
                            this.selectionMarker = selectionMarker = chart.renderer.rect(
                                    plotLeft,
                                    plotTop,
                                    zoomHor ? 1 : plotWidth,
                                    zoomVert ? 1 : plotHeight,
                                    0
                                )
                                .attr({

                                    fill: chartOptions.selectionMarkerFill || color('#335cad').setOpacity(0.25).get(),

                                    'class': 'highcharts-selection-marker',
                                    'zIndex': 7
                                })
                                .add();
                        }
                    }

                    // adjust the width of the selection marker
                    if (selectionMarker && zoomHor) {
                        size = chartX - mouseDownX;
                        selectionMarker.attr({
                            width: Math.abs(size),
                            x: (size > 0 ? 0 : size) + mouseDownX
                        });
                    }
                    // adjust the height of the selection marker
                    if (selectionMarker && zoomVert) {
                        size = chartY - mouseDownY;
                        selectionMarker.attr({
                            height: Math.abs(size),
                            y: (size > 0 ? 0 : size) + mouseDownY
                        });
                    }

                    // panning
                    if (clickedInside && !selectionMarker && chartOptions.panning) {
                        chart.pan(e, chartOptions.panning);
                    }
                }
            },

            /**
             * On mouse up or touch end across the entire document, drop the selection.
             *
             * @private
             */
            drop: function(e) {
                var pointer = this,
                    chart = this.chart,
                    hasPinched = this.hasPinched;

                if (this.selectionMarker) {
                    var selectionData = {
                            originalEvent: e, // #4890
                            xAxis: [],
                            yAxis: []
                        },
                        selectionBox = this.selectionMarker,
                        selectionLeft = selectionBox.attr ? selectionBox.attr('x') : selectionBox.x,
                        selectionTop = selectionBox.attr ? selectionBox.attr('y') : selectionBox.y,
                        selectionWidth = selectionBox.attr ? selectionBox.attr('width') : selectionBox.width,
                        selectionHeight = selectionBox.attr ? selectionBox.attr('height') : selectionBox.height,
                        runZoom;

                    // a selection has been made
                    if (this.hasDragged || hasPinched) {

                        // record each axis' min and max
                        each(chart.axes, function(axis) {
                            if (axis.zoomEnabled && defined(axis.min) && (hasPinched || pointer[{
                                    xAxis: 'zoomX',
                                    yAxis: 'zoomY'
                                }[axis.coll]])) { // #859, #3569
                                var horiz = axis.horiz,
                                    minPixelPadding = e.type === 'touchend' ? axis.minPixelPadding : 0, // #1207, #3075
                                    selectionMin = axis.toValue((horiz ? selectionLeft : selectionTop) + minPixelPadding),
                                    selectionMax = axis.toValue((horiz ? selectionLeft + selectionWidth : selectionTop + selectionHeight) - minPixelPadding);

                                selectionData[axis.coll].push({
                                    axis: axis,
                                    min: Math.min(selectionMin, selectionMax), // for reversed axes
                                    max: Math.max(selectionMin, selectionMax)
                                });
                                runZoom = true;
                            }
                        });
                        if (runZoom) {
                            fireEvent(chart, 'selection', selectionData, function(args) {
                                chart.zoom(extend(args, hasPinched ? {
                                    animation: false
                                } : null));
                            });
                        }

                    }
                    this.selectionMarker = this.selectionMarker.destroy();

                    // Reset scaling preview
                    if (hasPinched) {
                        this.scaleGroups();
                    }
                }

                // Reset all
                if (chart) { // it may be destroyed on mouse up - #877
                    css(chart.container, {
                        cursor: chart._cursor
                    });
                    chart.cancelClick = this.hasDragged > 10; // #370
                    chart.mouseIsDown = this.hasDragged = this.hasPinched = false;
                    this.pinchDown = [];
                }
            },

            onContainerMouseDown: function(e) {

                e = this.normalize(e);

                this.zoomOption(e);

                // issue #295, dragging not always working in Firefox
                if (e.preventDefault) {
                    e.preventDefault();
                }

                this.dragStart(e);
            },



            onDocumentMouseUp: function(e) {
                if (charts[H.hoverChartIndex]) {
                    charts[H.hoverChartIndex].pointer.drop(e);
                }
            },

            /**
             * Special handler for mouse move that will hide the tooltip when the mouse
             * leaves the plotarea. Issue #149 workaround. The mouseleave event does not
             * always fire.
             *
             * @private
             */
            onDocumentMouseMove: function(e) {
                var chart = this.chart,
                    chartPosition = this.chartPosition;

                e = this.normalize(e, chartPosition);

                // If we're outside, hide the tooltip
                if (chartPosition && !this.inClass(e.target, 'highcharts-tracker') &&
                    !chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {
                    this.reset();
                }
            },

            /**
             * When mouse leaves the container, hide the tooltip.
             *
             * @private
             */
            onContainerMouseLeave: function(e) {
                var chart = charts[H.hoverChartIndex];
                if (chart && (e.relatedTarget || e.toElement)) { // #4886, MS Touch end fires mouseleave but with no related target
                    chart.pointer.reset();
                    chart.pointer.chartPosition = null; // also reset the chart position, used in #149 fix
                }
            },

            // The mousemove, touchmove and touchstart event handler
            onContainerMouseMove: function(e) {

                var chart = this.chart;

                if (!defined(H.hoverChartIndex) || !charts[H.hoverChartIndex] || !charts[H.hoverChartIndex].mouseIsDown) {
                    H.hoverChartIndex = chart.index;
                }

                e = this.normalize(e);
                e.returnValue = false; // #2251, #3224

                if (chart.mouseIsDown === 'mousedown') {
                    this.drag(e);
                }

                // Show the tooltip and run mouse over events (#977)
                if ((this.inClass(e.target, 'highcharts-tracker') ||
                        chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) {
                    this.runPointActions(e);
                }
            },

            /**
             * Utility to detect whether an element has, or has a parent with, a specific
             * class name. Used on detection of tracker objects and on deciding whether
             * hovering the tooltip should cause the active series to mouse out.
             *
             * @param  {SVGDOMElement|HTMLDOMElement} element
             *         The element to investigate.
             * @param  {String} className
             *         The class name to look for.
             *
             * @return {Boolean}
             *         True if either the element or one of its parents has the given
             *         class name.
             */
            inClass: function(element, className) {
                var elemClassName;
                while (element) {
                    elemClassName = attr(element, 'class');
                    if (elemClassName) {
                        if (elemClassName.indexOf(className) !== -1) {
                            return true;
                        }
                        if (elemClassName.indexOf('highcharts-container') !== -1) {
                            return false;
                        }
                    }
                    element = element.parentNode;
                }
            },

            onTrackerMouseOut: function(e) {
                var series = this.chart.hoverSeries,
                    relatedTarget = e.relatedTarget || e.toElement;

                this.isDirectTouch = false;

                if (
                    series &&
                    relatedTarget &&
                    !series.stickyTracking &&
                    !this.inClass(relatedTarget, 'highcharts-tooltip') &&
                    (!this.inClass(
                            relatedTarget,
                            'highcharts-series-' + series.index
                        ) || // #2499, #4465
                        !this.inClass(relatedTarget, 'highcharts-tracker') // #5553
                    )
                ) {
                    series.onMouseOut();
                }
            },

            onContainerClick: function(e) {
                var chart = this.chart,
                    hoverPoint = chart.hoverPoint,
                    plotLeft = chart.plotLeft,
                    plotTop = chart.plotTop;

                e = this.normalize(e);

                if (!chart.cancelClick) {

                    // On tracker click, fire the series and point events. #783, #1583
                    if (hoverPoint && this.inClass(e.target, 'highcharts-tracker')) {

                        // the series click event
                        fireEvent(hoverPoint.series, 'click', extend(e, {
                            point: hoverPoint
                        }));

                        // the point click event
                        if (chart.hoverPoint) { // it may be destroyed (#1844)
                            hoverPoint.firePointEvent('click', e);
                        }

                        // When clicking outside a tracker, fire a chart event
                    } else {
                        extend(e, this.getCoordinates(e));

                        // fire a click event in the chart
                        if (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {
                            fireEvent(chart, 'click', e);
                        }
                    }


                }
            },

            /**
             * Set the JS DOM events on the container and document. This method should contain
             * a one-to-one assignment between methods and their handlers. Any advanced logic should
             * be moved to the handler reflecting the event's name.
             *
             * @private
             */
            setDOMEvents: function() {

                var pointer = this,
                    container = pointer.chart.container,
                    ownerDoc = container.ownerDocument;

                container.onmousedown = function(e) {
                    pointer.onContainerMouseDown(e);
                };
                container.onmousemove = function(e) {
                    pointer.onContainerMouseMove(e);
                };
                container.onclick = function(e) {
                    pointer.onContainerClick(e);
                };
                this.unbindContainerMouseLeave = addEvent(
                    container,
                    'mouseleave',
                    pointer.onContainerMouseLeave
                );
                if (!H.unbindDocumentMouseUp) {
                    H.unbindDocumentMouseUp = addEvent(
                        ownerDoc,
                        'mouseup',
                        pointer.onDocumentMouseUp
                    );
                }
                if (H.hasTouch) {
                    container.ontouchstart = function(e) {
                        pointer.onContainerTouchStart(e);
                    };
                    container.ontouchmove = function(e) {
                        pointer.onContainerTouchMove(e);
                    };
                    if (!H.unbindDocumentTouchEnd) {
                        H.unbindDocumentTouchEnd = addEvent(
                            ownerDoc,
                            'touchend',
                            pointer.onDocumentTouchEnd
                        );
                    }
                }

            },

            /**
             * Destroys the Pointer object and disconnects DOM events.
             */
            destroy: function() {
                var pointer = this;

                if (pointer.unDocMouseMove) {
                    pointer.unDocMouseMove();
                }

                this.unbindContainerMouseLeave();

                if (!H.chartCount) {
                    if (H.unbindDocumentMouseUp) {
                        H.unbindDocumentMouseUp = H.unbindDocumentMouseUp();
                    }
                    if (H.unbindDocumentTouchEnd) {
                        H.unbindDocumentTouchEnd = H.unbindDocumentTouchEnd();
                    }
                }

                // memory and CPU leak
                clearInterval(pointer.tooltipTimeout);

                H.objectEach(pointer, function(val, prop) {
                    pointer[prop] = null;
                });
            }
        };

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var charts = H.charts,
            each = H.each,
            extend = H.extend,
            map = H.map,
            noop = H.noop,
            pick = H.pick,
            Pointer = H.Pointer;

        /* Support for touch devices */
        extend(Pointer.prototype, /** @lends Pointer.prototype */ {

            /**
             * Run translation operations
             */
            pinchTranslate: function(
                pinchDown,
                touches,
                transform,
                selectionMarker,
                clip,
                lastValidTouch
            ) {
                if (this.zoomHor) {
                    this.pinchTranslateDirection(
                        true,
                        pinchDown,
                        touches,
                        transform,
                        selectionMarker,
                        clip,
                        lastValidTouch
                    );
                }
                if (this.zoomVert) {
                    this.pinchTranslateDirection(
                        false,
                        pinchDown,
                        touches,
                        transform,
                        selectionMarker,
                        clip,
                        lastValidTouch
                    );
                }
            },

            /**
             * Run translation operations for each direction (horizontal and vertical)
             * independently
             */
            pinchTranslateDirection: function(horiz, pinchDown, touches, transform,
                selectionMarker, clip, lastValidTouch, forcedScale) {
                var chart = this.chart,
                    xy = horiz ? 'x' : 'y',
                    XY = horiz ? 'X' : 'Y',
                    sChartXY = 'chart' + XY,
                    wh = horiz ? 'width' : 'height',
                    plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')],
                    selectionWH,
                    selectionXY,
                    clipXY,
                    scale = forcedScale || 1,
                    inverted = chart.inverted,
                    bounds = chart.bounds[horiz ? 'h' : 'v'],
                    singleTouch = pinchDown.length === 1,
                    touch0Start = pinchDown[0][sChartXY],
                    touch0Now = touches[0][sChartXY],
                    touch1Start = !singleTouch && pinchDown[1][sChartXY],
                    touch1Now = !singleTouch && touches[1][sChartXY],
                    outOfBounds,
                    transformScale,
                    scaleKey,
                    setScale = function() {
                        // Don't zoom if fingers are too close on this axis
                        if (!singleTouch && Math.abs(touch0Start - touch1Start) > 20) {
                            scale = forcedScale ||
                                Math.abs(touch0Now - touch1Now) /
                                Math.abs(touch0Start - touch1Start);
                        }

                        clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start;
                        selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] /
                            scale;
                    };

                // Set the scale, first pass
                setScale();

                // The clip position (x or y) is altered if out of bounds, the selection
                // position is not
                selectionXY = clipXY;

                // Out of bounds
                if (selectionXY < bounds.min) {
                    selectionXY = bounds.min;
                    outOfBounds = true;
                } else if (selectionXY + selectionWH > bounds.max) {
                    selectionXY = bounds.max - selectionWH;
                    outOfBounds = true;
                }

                // Is the chart dragged off its bounds, determined by dataMin and
                // dataMax?
                if (outOfBounds) {

                    // Modify the touchNow position in order to create an elastic drag
                    // movement. This indicates to the user that the chart is responsive
                    // but can't be dragged further.
                    touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]);
                    if (!singleTouch) {
                        touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]);
                    }

                    // Set the scale, second pass to adapt to the modified touchNow
                    // positions
                    setScale();

                } else {
                    lastValidTouch[xy] = [touch0Now, touch1Now];
                }

                // Set geometry for clipping, selection and transformation
                if (!inverted) {
                    clip[xy] = clipXY - plotLeftTop;
                    clip[wh] = selectionWH;
                }
                scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY;
                transformScale = inverted ? 1 / scale : scale;

                selectionMarker[wh] = selectionWH;
                selectionMarker[xy] = selectionXY;
                transform[scaleKey] = scale;
                transform['translate' + XY] = (transformScale * plotLeftTop) +
                    (touch0Now - (transformScale * touch0Start));
            },

            /**
             * Handle touch events with two touches
             */
            pinch: function(e) {

                var self = this,
                    chart = self.chart,
                    pinchDown = self.pinchDown,
                    touches = e.touches,
                    touchesLength = touches.length,
                    lastValidTouch = self.lastValidTouch,
                    hasZoom = self.hasZoom,
                    selectionMarker = self.selectionMarker,
                    transform = {},
                    fireClickEvent = touchesLength === 1 &&
                    ((self.inClass(e.target, 'highcharts-tracker') &&
                        chart.runTrackerClick) || self.runChartClick),
                    clip = {};

                // Don't initiate panning until the user has pinched. This prevents us
                // from blocking page scrolling as users scroll down a long page
                // (#4210).
                if (touchesLength > 1) {
                    self.initiated = true;
                }

                // On touch devices, only proceed to trigger click if a handler is
                // defined
                if (hasZoom && self.initiated && !fireClickEvent) {
                    e.preventDefault();
                }

                // Normalize each touch
                map(touches, function(e) {
                    return self.normalize(e);
                });

                // Register the touch start position
                if (e.type === 'touchstart') {
                    each(touches, function(e, i) {
                        pinchDown[i] = {
                            chartX: e.chartX,
                            chartY: e.chartY
                        };
                    });
                    lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] &&
                        pinchDown[1].chartX
                    ];
                    lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] &&
                        pinchDown[1].chartY
                    ];

                    // Identify the data bounds in pixels
                    each(chart.axes, function(axis) {
                        if (axis.zoomEnabled) {
                            var bounds = chart.bounds[axis.horiz ? 'h' : 'v'],
                                minPixelPadding = axis.minPixelPadding,
                                min = axis.toPixels(
                                    pick(axis.options.min, axis.dataMin)
                                ),
                                max = axis.toPixels(
                                    pick(axis.options.max, axis.dataMax)
                                ),
                                absMin = Math.min(min, max),
                                absMax = Math.max(min, max);

                            // Store the bounds for use in the touchmove handler
                            bounds.min = Math.min(axis.pos, absMin - minPixelPadding);
                            bounds.max = Math.max(
                                axis.pos + axis.len,
                                absMax + minPixelPadding
                            );
                        }
                    });
                    self.res = true; // reset on next move

                    // Optionally move the tooltip on touchmove
                } else if (self.followTouchMove && touchesLength === 1) {
                    this.runPointActions(self.normalize(e));

                    // Event type is touchmove, handle panning and pinching
                } else if (pinchDown.length) { // can be 0 when releasing, if touchend
                    // fires first


                    // Set the marker
                    if (!selectionMarker) {
                        self.selectionMarker = selectionMarker = extend({
                            destroy: noop,
                            touch: true
                        }, chart.plotBox);
                    }

                    self.pinchTranslate(
                        pinchDown,
                        touches,
                        transform,
                        selectionMarker,
                        clip,
                        lastValidTouch
                    );

                    self.hasPinched = hasZoom;

                    // Scale and translate the groups to provide visual feedback during
                    // pinching
                    self.scaleGroups(transform, clip);

                    if (self.res) {
                        self.res = false;
                        this.reset(false, 0);
                    }
                }
            },

            /**
             * General touch handler shared by touchstart and touchmove.
             */
            touch: function(e, start) {
                var chart = this.chart,
                    hasMoved,
                    pinchDown,
                    isInside;

                if (chart.index !== H.hoverChartIndex) {
                    this.onContainerMouseLeave({
                        relatedTarget: true
                    });
                }
                H.hoverChartIndex = chart.index;

                if (e.touches.length === 1) {

                    e = this.normalize(e);

                    isInside = chart.isInsidePlot(
                        e.chartX - chart.plotLeft,
                        e.chartY - chart.plotTop
                    );
                    if (isInside && !chart.openMenu) {

                        // Run mouse events and display tooltip etc
                        if (start) {
                            this.runPointActions(e);
                        }

                        // Android fires touchmove events after the touchstart even if
                        // the finger hasn't moved, or moved only a pixel or two. In iOS
                        // however, the touchmove doesn't fire unless the finger moves
                        // more than ~4px. So we emulate this behaviour in Android by
                        // checking how much it moved, and cancelling on small
                        // distances. #3450.
                        if (e.type === 'touchmove') {
                            pinchDown = this.pinchDown;
                            hasMoved = pinchDown[0] ? Math.sqrt( // #5266
                                Math.pow(pinchDown[0].chartX - e.chartX, 2) +
                                Math.pow(pinchDown[0].chartY - e.chartY, 2)
                            ) >= 4 : false;
                        }

                        if (pick(hasMoved, true)) {
                            this.pinch(e);
                        }

                    } else if (start) {
                        // Hide the tooltip on touching outside the plot area (#1203)
                        this.reset();
                    }

                } else if (e.touches.length === 2) {
                    this.pinch(e);
                }
            },

            onContainerTouchStart: function(e) {
                this.zoomOption(e);
                this.touch(e, true);
            },

            onContainerTouchMove: function(e) {
                this.touch(e);
            },

            onDocumentTouchEnd: function(e) {
                if (charts[H.hoverChartIndex]) {
                    charts[H.hoverChartIndex].pointer.drop(e);
                }
            }

        });

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var addEvent = H.addEvent,
            charts = H.charts,
            css = H.css,
            doc = H.doc,
            extend = H.extend,
            hasTouch = H.hasTouch,
            noop = H.noop,
            Pointer = H.Pointer,
            removeEvent = H.removeEvent,
            win = H.win,
            wrap = H.wrap;

        if (!hasTouch && (win.PointerEvent || win.MSPointerEvent)) {

            // The touches object keeps track of the points being touched at all times
            var touches = {},
                hasPointerEvent = !!win.PointerEvent,
                getWebkitTouches = function() {
                    var fake = [];
                    fake.item = function(i) {
                        return this[i];
                    };
                    H.objectEach(touches, function(touch) {
                        fake.push({
                            pageX: touch.pageX,
                            pageY: touch.pageY,
                            target: touch.target
                        });
                    });
                    return fake;
                },
                translateMSPointer = function(e, method, wktype, func) {
                    var p;
                    if ((e.pointerType === 'touch' || e.pointerType === e.MSPOINTER_TYPE_TOUCH) && charts[H.hoverChartIndex]) {
                        func(e);
                        p = charts[H.hoverChartIndex].pointer;
                        p[method]({
                            type: wktype,
                            target: e.currentTarget,
                            preventDefault: noop,
                            touches: getWebkitTouches()
                        });
                    }
                };

            /**
             * Extend the Pointer prototype with methods for each event handler and more
             */
            extend(Pointer.prototype, /** @lends Pointer.prototype */ {
                onContainerPointerDown: function(e) {
                    translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function(e) {
                        touches[e.pointerId] = {
                            pageX: e.pageX,
                            pageY: e.pageY,
                            target: e.currentTarget
                        };
                    });
                },
                onContainerPointerMove: function(e) {
                    translateMSPointer(e, 'onContainerTouchMove', 'touchmove', function(e) {
                        touches[e.pointerId] = {
                            pageX: e.pageX,
                            pageY: e.pageY
                        };
                        if (!touches[e.pointerId].target) {
                            touches[e.pointerId].target = e.currentTarget;
                        }
                    });
                },
                onDocumentPointerUp: function(e) {
                    translateMSPointer(e, 'onDocumentTouchEnd', 'touchend', function(e) {
                        delete touches[e.pointerId];
                    });
                },

                /**
                 * Add or remove the MS Pointer specific events
                 */
                batchMSEvents: function(fn) {
                    fn(this.chart.container, hasPointerEvent ? 'pointerdown' : 'MSPointerDown', this.onContainerPointerDown);
                    fn(this.chart.container, hasPointerEvent ? 'pointermove' : 'MSPointerMove', this.onContainerPointerMove);
                    fn(doc, hasPointerEvent ? 'pointerup' : 'MSPointerUp', this.onDocumentPointerUp);
                }
            });

            // Disable default IE actions for pinch and such on chart element
            wrap(Pointer.prototype, 'init', function(proceed, chart, options) {
                proceed.call(this, chart, options);
                if (this.hasZoom) { // #4014
                    css(chart.container, {
                        '-ms-touch-action': 'none',
                        'touch-action': 'none'
                    });
                }
            });

            // Add IE specific touch events to chart
            wrap(Pointer.prototype, 'setDOMEvents', function(proceed) {
                proceed.apply(this);
                if (this.hasZoom || this.followTouchMove) {
                    this.batchMSEvents(addEvent);
                }
            });
            // Destroy MS events also
            wrap(Pointer.prototype, 'destroy', function(proceed) {
                this.batchMSEvents(removeEvent);
                proceed.call(this);
            });
        }

    }(Highcharts));
    (function(Highcharts) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var H = Highcharts,

            addEvent = H.addEvent,
            css = H.css,
            discardElement = H.discardElement,
            defined = H.defined,
            each = H.each,
            isFirefox = H.isFirefox,
            marginNames = H.marginNames,
            merge = H.merge,
            pick = H.pick,
            setAnimation = H.setAnimation,
            stableSort = H.stableSort,
            win = H.win,
            wrap = H.wrap;

        /**
         * The overview of the chart's series. The legend object is instanciated
         * internally in the chart constructor, and available from `chart.legend`. Each
         * chart has only one legend.
         * 
         * @class
         */
        Highcharts.Legend = function(chart, options) {
            this.init(chart, options);
        };

        Highcharts.Legend.prototype = {

            /**
             * Initialize the legend.
             *
             * @private
             */
            init: function(chart, options) {

                this.chart = chart;

                this.setOptions(options);

                if (options.enabled) {

                    // Render it
                    this.render();

                    // move checkboxes
                    addEvent(this.chart, 'endResize', function() {
                        this.legend.positionCheckboxes();
                    });
                }
            },

            setOptions: function(options) {

                var padding = pick(options.padding, 8);

                this.options = options;


                this.itemStyle = options.itemStyle;
                this.itemHiddenStyle = merge(this.itemStyle, options.itemHiddenStyle);

                this.itemMarginTop = options.itemMarginTop || 0;
                this.padding = padding;
                this.initialItemY = padding - 5; // 5 is pixels above the text
                this.maxItemWidth = 0;
                this.itemHeight = 0;
                this.symbolWidth = pick(options.symbolWidth, 16);
                this.pages = [];

            },

            /**
             * Update the legend with new options. Equivalent to running `chart.update`
             * with a legend configuration option.
             * @param  {LegendOptions} options
             *         Legend options.
             * @param  {Boolean} [redraw=true]
             *         Whether to redraw the chart.
             *
             * @sample highcharts/legend/legend-update/
             *         Legend update
             */
            update: function(options, redraw) {
                var chart = this.chart;

                this.setOptions(merge(true, this.options, options));
                this.destroy();
                chart.isDirtyLegend = chart.isDirtyBox = true;
                if (pick(redraw, true)) {
                    chart.redraw();
                }
            },

            /**
             * Set the colors for the legend item.
             *
             * @private
             * @param  {Series|Point} item
             *         A Series or Point instance
             * @param  {Boolean} visible
             *         Dimmed or colored
             */
            colorizeItem: function(item, visible) {
                item.legendGroup[visible ? 'removeClass' : 'addClass'](
                    'highcharts-legend-item-hidden'
                );


                var legend = this,
                    options = legend.options,
                    legendItem = item.legendItem,
                    legendLine = item.legendLine,
                    legendSymbol = item.legendSymbol,
                    hiddenColor = legend.itemHiddenStyle.color,
                    textColor = visible ? options.itemStyle.color : hiddenColor,
                    symbolColor = visible ? (item.color || hiddenColor) : hiddenColor,
                    markerOptions = item.options && item.options.marker,
                    symbolAttr = {
                        fill: symbolColor
                    };

                if (legendItem) {
                    legendItem.css({
                        fill: textColor,
                        color: textColor // #1553, oldIE
                    });
                }
                if (legendLine) {
                    legendLine.attr({
                        stroke: symbolColor
                    });
                }

                if (legendSymbol) {

                    // Apply marker options
                    if (markerOptions && legendSymbol.isMarker) { // #585
                        symbolAttr = item.pointAttribs();
                        if (!visible) {
                            symbolAttr.stroke = symbolAttr.fill = hiddenColor; // #6769
                        }
                    }

                    legendSymbol.attr(symbolAttr);
                }

            },

            /**
             * Position the legend item.
             *
             * @private
             * @param {Series|Point} item
             *        The item to position
             */
            positionItem: function(item) {
                var legend = this,
                    options = legend.options,
                    symbolPadding = options.symbolPadding,
                    ltr = !options.rtl,
                    legendItemPos = item._legendItemPos,
                    itemX = legendItemPos[0],
                    itemY = legendItemPos[1],
                    checkbox = item.checkbox,
                    legendGroup = item.legendGroup;

                if (legendGroup && legendGroup.element) {
                    legendGroup.translate(
                        ltr ?
                        itemX :
                        legend.legendWidth - itemX - 2 * symbolPadding - 4,
                        itemY
                    );
                }

                if (checkbox) {
                    checkbox.x = itemX;
                    checkbox.y = itemY;
                }
            },

            /**
             * Destroy a single legend item, used internally on removing series items.
             * 
             * @param {Series|Point} item
             *        The item to remove
             */
            destroyItem: function(item) {
                var checkbox = item.checkbox;

                // destroy SVG elements
                each(
                    ['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'],
                    function(key) {
                        if (item[key]) {
                            item[key] = item[key].destroy();
                        }
                    }
                );

                if (checkbox) {
                    discardElement(item.checkbox);
                }
            },

            /**
             * Destroy the legend. Used internally. To reflow objects, `chart.redraw`
             * must be called after destruction.
             */
            destroy: function() {
                function destroyItems(key) {
                    if (this[key]) {
                        this[key] = this[key].destroy();
                    }
                }

                // Destroy items
                each(this.getAllItems(), function(item) {
                    each(['legendItem', 'legendGroup'], destroyItems, item);
                });

                // Destroy legend elements
                each([
                    'clipRect',
                    'up',
                    'down',
                    'pager',
                    'nav',
                    'box',
                    'title',
                    'group'
                ], destroyItems, this);
                this.display = null; // Reset in .render on update.
            },

            /**
             * Position the checkboxes after the width is determined.
             *
             * @private
             */
            positionCheckboxes: function() {
                var alignAttr = this.group && this.group.alignAttr,
                    translateY,
                    clipHeight = this.clipHeight || this.legendHeight,
                    titleHeight = this.titleHeight;

                if (alignAttr) {
                    translateY = alignAttr.translateY;
                    each(this.allItems, function(item) {
                        var checkbox = item.checkbox,
                            top;

                        if (checkbox) {
                            top = translateY + titleHeight + checkbox.y +
                                (this.scrollOffset || 0) + 3;
                            css(checkbox, {
                                left: (alignAttr.translateX + item.checkboxOffset +
                                    checkbox.x - 20) + 'px',
                                top: top + 'px',
                                display: top > translateY - 6 && top < translateY +
                                    clipHeight - 6 ? '' : 'none'
                            });
                        }
                    }, this);
                }
            },

            /**
             * Render the legend title on top of the legend.
             *
             * @private
             */
            renderTitle: function() {
                var options = this.options,
                    padding = this.padding,
                    titleOptions = options.title,
                    titleHeight = 0,
                    bBox;

                if (titleOptions.text) {
                    if (!this.title) {
                        this.title = this.chart.renderer.label(
                                titleOptions.text,
                                padding - 3,
                                padding - 4,
                                null,
                                null,
                                null,
                                options.useHTML,
                                null,
                                'legend-title'
                            )
                            .attr({
                                zIndex: 1
                            })

                            .css(titleOptions.style)

                            .add(this.group);
                    }
                    bBox = this.title.getBBox();
                    titleHeight = bBox.height;
                    this.offsetWidth = bBox.width; // #1717
                    this.contentGroup.attr({
                        translateY: titleHeight
                    });
                }
                this.titleHeight = titleHeight;
            },

            /**
             * Set the legend item text.
             *
             * @param  {Series|Point} item
             *         The item for which to update the text in the legend.
             */
            setText: function(item) {
                var options = this.options;
                item.legendItem.attr({
                    text: options.labelFormat ?
                        H.format(options.labelFormat, item) : options.labelFormatter.call(item)
                });
            },

            /**
             * Render a single specific legend item. Called internally from the `render`
             * function.
             *
             * @private
             * @param {Series|Point} item
             *        The item to render.
             */
            renderItem: function(item) {
                var legend = this,
                    chart = legend.chart,
                    renderer = chart.renderer,
                    options = legend.options,
                    horizontal = options.layout === 'horizontal',
                    symbolWidth = legend.symbolWidth,
                    symbolPadding = options.symbolPadding,

                    itemStyle = legend.itemStyle,
                    itemHiddenStyle = legend.itemHiddenStyle,

                    padding = legend.padding,
                    itemDistance = horizontal ? pick(options.itemDistance, 20) : 0,
                    ltr = !options.rtl,
                    itemHeight,
                    widthOption = options.width,
                    itemMarginBottom = options.itemMarginBottom || 0,
                    itemMarginTop = legend.itemMarginTop,
                    bBox,
                    itemWidth,
                    li = item.legendItem,
                    isSeries = !item.series,
                    series = !isSeries && item.series.drawLegendSymbol ?
                    item.series :
                    item,
                    seriesOptions = series.options,
                    showCheckbox = legend.createCheckboxForItem &&
                    seriesOptions &&
                    seriesOptions.showCheckbox,
                    // full width minus text width
                    itemExtraWidth = symbolWidth + symbolPadding + itemDistance +
                    (showCheckbox ? 20 : 0),
                    useHTML = options.useHTML,
                    fontSize = 12,
                    itemClassName = item.options.className;

                if (!li) { // generate it once, later move it

                    // Generate the group box, a group to hold the symbol and text. Text
                    // is to be appended in Legend class.
                    item.legendGroup = renderer.g('legend-item')
                        .addClass(
                            'highcharts-' + series.type + '-series ' +
                            'highcharts-color-' + item.colorIndex +
                            (itemClassName ? ' ' + itemClassName : '') +
                            (isSeries ? ' highcharts-series-' + item.index : '')
                        )
                        .attr({
                            zIndex: 1
                        })
                        .add(legend.scrollGroup);

                    // Generate the list item text and add it to the group
                    item.legendItem = li = renderer.text(
                            '',
                            ltr ? symbolWidth + symbolPadding : -symbolPadding,
                            legend.baseline || 0,
                            useHTML
                        )

                        // merge to prevent modifying original (#1021)
                        .css(merge(item.visible ? itemStyle : itemHiddenStyle))

                        .attr({
                            align: ltr ? 'left' : 'right',
                            zIndex: 2
                        })
                        .add(item.legendGroup);

                    // Get the baseline for the first item - the font size is equal for
                    // all
                    if (!legend.baseline) {

                        fontSize = itemStyle.fontSize;

                        legend.fontMetrics = renderer.fontMetrics(
                            fontSize,
                            li
                        );
                        legend.baseline = legend.fontMetrics.f + 3 + itemMarginTop;
                        li.attr('y', legend.baseline);
                    }

                    // Draw the legend symbol inside the group box
                    legend.symbolHeight = options.symbolHeight || legend.fontMetrics.f;
                    series.drawLegendSymbol(legend, item);

                    if (legend.setItemEvents) {
                        legend.setItemEvents(item, li, useHTML);
                    }

                    // add the HTML checkbox on top
                    if (showCheckbox) {
                        legend.createCheckboxForItem(item);
                    }
                }

                // Colorize the items
                legend.colorizeItem(item, item.visible);

                // Take care of max width and text overflow (#6659)

                if (!itemStyle.width) {

                    li.css({
                        width: (
                            options.itemWidth ||
                            options.width ||
                            chart.spacingBox.width
                        ) - itemExtraWidth
                    });

                }


                // Always update the text
                legend.setText(item);

                // calculate the positions for the next line
                bBox = li.getBBox();

                itemWidth = item.checkboxOffset =
                    options.itemWidth ||
                    item.legendItemWidth ||
                    bBox.width + itemExtraWidth;
                legend.itemHeight = itemHeight = Math.round(
                    item.legendItemHeight || bBox.height || legend.symbolHeight
                );

                // If the item exceeds the width, start a new line
                if (
                    horizontal &&
                    legend.itemX - padding + itemWidth > (
                        widthOption || (
                            chart.spacingBox.width - 2 * padding - options.x
                        )
                    )
                ) {
                    legend.itemX = padding;
                    legend.itemY += itemMarginTop + legend.lastLineHeight +
                        itemMarginBottom;
                    legend.lastLineHeight = 0; // reset for next line (#915, #3976)
                }

                // If the item exceeds the height, start a new column
                /*
                if (!horizontal && legend.itemY + options.y +
                		itemHeight > chart.chartHeight - spacingTop - spacingBottom) {
                	legend.itemY = legend.initialItemY;
                	legend.itemX += legend.maxItemWidth;
                	legend.maxItemWidth = 0;
                }
                */

                // Set the edge positions
                legend.maxItemWidth = Math.max(legend.maxItemWidth, itemWidth);
                legend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom;
                legend.lastLineHeight = Math.max( // #915
                    itemHeight,
                    legend.lastLineHeight
                );

                // cache the position of the newly generated or reordered items
                item._legendItemPos = [legend.itemX, legend.itemY];

                // advance
                if (horizontal) {
                    legend.itemX += itemWidth;

                } else {
                    legend.itemY += itemMarginTop + itemHeight + itemMarginBottom;
                    legend.lastLineHeight = itemHeight;
                }

                // the width of the widest item
                legend.offsetWidth = widthOption || Math.max(
                    (
                        horizontal ? legend.itemX - padding - (item.checkbox ?
                            // decrease by itemDistance only when no checkbox #4853
                            0 :
                            itemDistance
                        ) : itemWidth
                    ) + padding,
                    legend.offsetWidth
                );
            },

            /**
             * Get all items, which is one item per series for most series and one
             * item per point for pie series and its derivatives.
             *
             * @return {Array.<Series|Point>}
             *         The current items in the legend.
             */
            getAllItems: function() {
                var allItems = [];
                each(this.chart.series, function(series) {
                    var seriesOptions = series && series.options;

                    // Handle showInLegend. If the series is linked to another series,
                    // defaults to false.
                    if (series && pick(
                            seriesOptions.showInLegend, !defined(seriesOptions.linkedTo) ? undefined : false, true
                        )) {

                        // Use points or series for the legend item depending on
                        // legendType
                        allItems = allItems.concat(
                            series.legendItems ||
                            (
                                seriesOptions.legendType === 'point' ?
                                series.data :
                                series
                            )
                        );
                    }
                });
                return allItems;
            },

            /**
             * Adjust the chart margins by reserving space for the legend on only one
             * side of the chart. If the position is set to a corner, top or bottom is
             * reserved for horizontal legends and left or right for vertical ones.
             *
             * @private
             */
            adjustMargins: function(margin, spacing) {
                var chart = this.chart,
                    options = this.options,
                    // Use the first letter of each alignment option in order to detect
                    // the side. (#4189 - use charAt(x) notation instead of [x] for IE7)
                    alignment = options.align.charAt(0) +
                    options.verticalAlign.charAt(0) +
                    options.layout.charAt(0);

                if (!options.floating) {

                    each([
                        /(lth|ct|rth)/,
                        /(rtv|rm|rbv)/,
                        /(rbh|cb|lbh)/,
                        /(lbv|lm|ltv)/
                    ], function(alignments, side) {
                        if (alignments.test(alignment) && !defined(margin[side])) {
                            // Now we have detected on which side of the chart we should
                            // reserve space for the legend
                            chart[marginNames[side]] = Math.max(
                                chart[marginNames[side]],
                                (
                                    chart.legend[
                                        (side + 1) % 2 ? 'legendHeight' : 'legendWidth'
                                    ] + [1, -1, -1, 1][side] * options[
                                        (side % 2) ? 'x' : 'y'
                                    ] +
                                    pick(options.margin, 12) +
                                    spacing[side]
                                )
                            );
                        }
                    });
                }
            },

            /**
             * Render the legend. This method can be called both before and after
             * `chart.render`. If called after, it will only rearrange items instead
             * of creating new ones. Called internally on initial render and after
             * redraws.
             */
            render: function() {
                var legend = this,
                    chart = legend.chart,
                    renderer = chart.renderer,
                    legendGroup = legend.group,
                    allItems,
                    display,
                    legendWidth,
                    legendHeight,
                    box = legend.box,
                    options = legend.options,
                    padding = legend.padding;

                legend.itemX = padding;
                legend.itemY = legend.initialItemY;
                legend.offsetWidth = 0;
                legend.lastItemY = 0;

                if (!legendGroup) {
                    legend.group = legendGroup = renderer.g('legend')
                        .attr({
                            zIndex: 7
                        })
                        .add();
                    legend.contentGroup = renderer.g()
                        .attr({
                            zIndex: 1
                        }) // above background
                        .add(legendGroup);
                    legend.scrollGroup = renderer.g()
                        .add(legend.contentGroup);
                }

                legend.renderTitle();

                // add each series or point
                allItems = legend.getAllItems();

                // sort by legendIndex
                stableSort(allItems, function(a, b) {
                    return ((a.options && a.options.legendIndex) || 0) -
                        ((b.options && b.options.legendIndex) || 0);
                });

                // reversed legend
                if (options.reversed) {
                    allItems.reverse();
                }

                legend.allItems = allItems;
                legend.display = display = !!allItems.length;

                // render the items
                legend.lastLineHeight = 0;
                each(allItems, function(item) {
                    legend.renderItem(item);
                });

                // Get the box
                legendWidth = (options.width || legend.offsetWidth) + padding;
                legendHeight = legend.lastItemY + legend.lastLineHeight +
                    legend.titleHeight;
                legendHeight = legend.handleOverflow(legendHeight);
                legendHeight += padding;

                // Draw the border and/or background
                if (!box) {
                    legend.box = box = renderer.rect()
                        .addClass('highcharts-legend-box')
                        .attr({
                            r: options.borderRadius
                        })
                        .add(legendGroup);
                    box.isNew = true;
                }


                // Presentational
                box
                    .attr({
                        stroke: options.borderColor,
                        'stroke-width': options.borderWidth || 0,
                        fill: options.backgroundColor || 'none'
                    })
                    .shadow(options.shadow);


                if (legendWidth > 0 && legendHeight > 0) {
                    box[box.isNew ? 'attr' : 'animate'](
                        box.crisp.call({}, { // #7260
                            x: 0,
                            y: 0,
                            width: legendWidth,
                            height: legendHeight
                        }, box.strokeWidth())
                    );
                    box.isNew = false;
                }

                // hide the border if no items
                box[display ? 'show' : 'hide']();



                legend.legendWidth = legendWidth;
                legend.legendHeight = legendHeight;

                // Now that the legend width and height are established, put the items
                // in the final position
                each(allItems, function(item) {
                    legend.positionItem(item);
                });

                if (display) {
                    legendGroup.align(merge(options, {
                        width: legendWidth,
                        height: legendHeight
                    }), true, 'spacingBox');
                }

                if (!chart.isResizing) {
                    this.positionCheckboxes();
                }
            },

            /**
             * Set up the overflow handling by adding navigation with up and down arrows
             * below the legend.
             *
             * @private
             */
            handleOverflow: function(legendHeight) {
                var legend = this,
                    chart = this.chart,
                    renderer = chart.renderer,
                    options = this.options,
                    optionsY = options.y,
                    alignTop = options.verticalAlign === 'top',
                    padding = this.padding,
                    spaceHeight = chart.spacingBox.height +
                    (alignTop ? -optionsY : optionsY) - padding,
                    maxHeight = options.maxHeight,
                    clipHeight,
                    clipRect = this.clipRect,
                    navOptions = options.navigation,
                    animation = pick(navOptions.animation, true),
                    arrowSize = navOptions.arrowSize || 12,
                    nav = this.nav,
                    pages = this.pages,
                    lastY,
                    allItems = this.allItems,
                    clipToHeight = function(height) {
                        if (typeof height === 'number') {
                            clipRect.attr({
                                height: height
                            });
                        } else if (clipRect) { // Reset (#5912)
                            legend.clipRect = clipRect.destroy();
                            legend.contentGroup.clip();
                        }

                        // useHTML
                        if (legend.contentGroup.div) {
                            legend.contentGroup.div.style.clip = height ?
                                'rect(' + padding + 'px,9999px,' +
                                (padding + height) + 'px,0)' :
                                'auto';
                        }
                    };


                // Adjust the height
                if (
                    options.layout === 'horizontal' &&
                    options.verticalAlign !== 'middle' &&
                    !options.floating
                ) {
                    spaceHeight /= 2;
                }
                if (maxHeight) {
                    spaceHeight = Math.min(spaceHeight, maxHeight);
                }

                // Reset the legend height and adjust the clipping rectangle
                pages.length = 0;
                if (legendHeight > spaceHeight && navOptions.enabled !== false) {

                    this.clipHeight = clipHeight =
                        Math.max(spaceHeight - 20 - this.titleHeight - padding, 0);
                    this.currentPage = pick(this.currentPage, 1);
                    this.fullHeight = legendHeight;

                    // Fill pages with Y positions so that the top of each a legend item
                    // defines the scroll top for each page (#2098)
                    each(allItems, function(item, i) {
                        var y = item._legendItemPos[1],
                            h = Math.round(item.legendItem.getBBox().height),
                            len = pages.length;

                        if (!len || (y - pages[len - 1] > clipHeight &&
                                (lastY || y) !== pages[len - 1])) {
                            pages.push(lastY || y);
                            len++;
                        }

                        if (i === allItems.length - 1 &&
                            y + h - pages[len - 1] > clipHeight) {
                            pages.push(y);
                        }
                        if (y !== lastY) {
                            lastY = y;
                        }
                    });

                    // Only apply clipping if needed. Clipping causes blurred legend in
                    // PDF export (#1787)
                    if (!clipRect) {
                        clipRect = legend.clipRect =
                            renderer.clipRect(0, padding, 9999, 0);
                        legend.contentGroup.clip(clipRect);
                    }

                    clipToHeight(clipHeight);

                    // Add navigation elements
                    if (!nav) {
                        this.nav = nav = renderer.g()
                            .attr({
                                zIndex: 1
                            })
                            .add(this.group);

                        this.up = renderer
                            .symbol(
                                'triangle',
                                0,
                                0,
                                arrowSize,
                                arrowSize
                            )
                            .on('click', function() {
                                legend.scroll(-1, animation);
                            })
                            .add(nav);

                        this.pager = renderer.text('', 15, 10)
                            .addClass('highcharts-legend-navigation')

                            .css(navOptions.style)

                            .add(nav);

                        this.down = renderer
                            .symbol(
                                'triangle-down',
                                0,
                                0,
                                arrowSize,
                                arrowSize
                            )
                            .on('click', function() {
                                legend.scroll(1, animation);
                            })
                            .add(nav);
                    }

                    // Set initial position
                    legend.scroll(0);

                    legendHeight = spaceHeight;

                    // Reset
                } else if (nav) {
                    clipToHeight();
                    this.nav = nav.destroy(); // #6322
                    this.scrollGroup.attr({
                        translateY: 1
                    });
                    this.clipHeight = 0; // #1379
                }

                return legendHeight;
            },

            /**
             * Scroll the legend by a number of pages.
             * @param  {Number} scrollBy
             *         The number of pages to scroll.
             * @param  {AnimationOptions} animation
             *         Whether and how to apply animation.
             */
            scroll: function(scrollBy, animation) {
                var pages = this.pages,
                    pageCount = pages.length,
                    currentPage = this.currentPage + scrollBy,
                    clipHeight = this.clipHeight,
                    navOptions = this.options.navigation,
                    pager = this.pager,
                    padding = this.padding;

                // When resizing while looking at the last page
                if (currentPage > pageCount) {
                    currentPage = pageCount;
                }

                if (currentPage > 0) {

                    if (animation !== undefined) {
                        setAnimation(animation, this.chart);
                    }

                    this.nav.attr({
                        translateX: padding,
                        translateY: clipHeight + this.padding + 7 + this.titleHeight,
                        visibility: 'visible'
                    });
                    this.up.attr({
                        'class': currentPage === 1 ?
                            'highcharts-legend-nav-inactive' : 'highcharts-legend-nav-active'
                    });
                    pager.attr({
                        text: currentPage + '/' + pageCount
                    });
                    this.down.attr({
                        'x': 18 + this.pager.getBBox().width, // adjust to text width
                        'class': currentPage === pageCount ?
                            'highcharts-legend-nav-inactive' : 'highcharts-legend-nav-active'
                    });


                    this.up
                        .attr({
                            fill: currentPage === 1 ?
                                navOptions.inactiveColor : navOptions.activeColor
                        })
                        .css({
                            cursor: currentPage === 1 ? 'default' : 'pointer'
                        });
                    this.down
                        .attr({
                            fill: currentPage === pageCount ?
                                navOptions.inactiveColor : navOptions.activeColor
                        })
                        .css({
                            cursor: currentPage === pageCount ? 'default' : 'pointer'
                        });


                    this.scrollOffset = -pages[currentPage - 1] + this.initialItemY;

                    this.scrollGroup.animate({
                        translateY: this.scrollOffset
                    });

                    this.currentPage = currentPage;
                    this.positionCheckboxes();
                }

            }

        };

        /*
         * LegendSymbolMixin
         */

        H.LegendSymbolMixin = {

            /**
             * Get the series' symbol in the legend
             *
             * @param {Object} legend The legend object
             * @param {Object} item The series (this) or point
             */
            drawRectangle: function(legend, item) {
                var options = legend.options,
                    symbolHeight = legend.symbolHeight,
                    square = options.squareSymbol,
                    symbolWidth = square ? symbolHeight : legend.symbolWidth;

                item.legendSymbol = this.chart.renderer.rect(
                        square ? (legend.symbolWidth - symbolHeight) / 2 : 0,
                        legend.baseline - symbolHeight + 1, // #3988
                        symbolWidth,
                        symbolHeight,
                        pick(legend.options.symbolRadius, symbolHeight / 2)
                    )
                    .addClass('highcharts-point')
                    .attr({
                        zIndex: 3
                    }).add(item.legendGroup);

            },

            /**
             * Get the series' symbol in the legend. This method should be overridable
             * to create custom symbols through
             * Highcharts.seriesTypes[type].prototype.drawLegendSymbols.
             *
             * @param {Object} legend The legend object
             */
            drawLineMarker: function(legend) {

                var options = this.options,
                    markerOptions = options.marker,
                    radius,
                    legendSymbol,
                    symbolWidth = legend.symbolWidth,
                    symbolHeight = legend.symbolHeight,
                    generalRadius = symbolHeight / 2,
                    renderer = this.chart.renderer,
                    legendItemGroup = this.legendGroup,
                    verticalCenter = legend.baseline -
                    Math.round(legend.fontMetrics.b * 0.3),
                    attr = {};

                // Draw the line

                attr = {
                    'stroke-width': options.lineWidth || 0
                };
                if (options.dashStyle) {
                    attr.dashstyle = options.dashStyle;
                }


                this.legendLine = renderer.path([
                        'M',
                        0,
                        verticalCenter,
                        'L',
                        symbolWidth,
                        verticalCenter
                    ])
                    .addClass('highcharts-graph')
                    .attr(attr)
                    .add(legendItemGroup);

                // Draw the marker
                if (markerOptions && markerOptions.enabled !== false) {

                    // Do not allow the marker to be larger than the symbolHeight
                    radius = Math.min(
                        pick(markerOptions.radius, generalRadius),
                        generalRadius
                    );

                    // Restrict symbol markers size
                    if (this.symbol.indexOf('url') === 0) {
                        markerOptions = merge(markerOptions, {
                            width: symbolHeight,
                            height: symbolHeight
                        });
                        radius = 0;
                    }

                    this.legendSymbol = legendSymbol = renderer.symbol(
                            this.symbol,
                            (symbolWidth / 2) - radius,
                            verticalCenter - radius,
                            2 * radius,
                            2 * radius,
                            markerOptions
                        )
                        .addClass('highcharts-point')
                        .add(legendItemGroup);
                    legendSymbol.isMarker = true;
                }
            }
        };

        // Workaround for #2030, horizontal legend items not displaying in IE11 Preview,
        // and for #2580, a similar drawing flaw in Firefox 26.
        // Explore if there's a general cause for this. The problem may be related
        // to nested group elements, as the legend item texts are within 4 group
        // elements.
        if (/Trident\/7\.0/.test(win.navigator.userAgent) || isFirefox) {
            wrap(Highcharts.Legend.prototype, 'positionItem', function(proceed, item) {
                var legend = this,
                    // If chart destroyed in sync, this is undefined (#2030)
                    runPositionItem = function() {
                        if (item._legendItemPos) {
                            proceed.call(legend, item);
                        }
                    };

                // Do it now, for export and to get checkbox placement
                runPositionItem();

                // Do it after to work around the core issue
                setTimeout(runPositionItem);
            });
        }

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var addEvent = H.addEvent,
            animate = H.animate,
            animObject = H.animObject,
            attr = H.attr,
            doc = H.doc,
            Axis = H.Axis, // @todo add as requirement
            createElement = H.createElement,
            defaultOptions = H.defaultOptions,
            discardElement = H.discardElement,
            charts = H.charts,
            css = H.css,
            defined = H.defined,
            each = H.each,
            extend = H.extend,
            find = H.find,
            fireEvent = H.fireEvent,
            grep = H.grep,
            isNumber = H.isNumber,
            isObject = H.isObject,
            isString = H.isString,
            Legend = H.Legend, // @todo add as requirement
            marginNames = H.marginNames,
            merge = H.merge,
            objectEach = H.objectEach,
            Pointer = H.Pointer, // @todo add as requirement
            pick = H.pick,
            pInt = H.pInt,
            removeEvent = H.removeEvent,
            seriesTypes = H.seriesTypes,
            splat = H.splat,
            svg = H.svg,
            syncTimeout = H.syncTimeout,
            win = H.win;
        /**
         * The Chart class. The recommended constructor is {@link Highcharts#chart}.
         * @class Highcharts.Chart
         * @param  {String|HTMLDOMElement} renderTo
         *         The DOM element to render to, or its id.
         * @param  {Options} options
         *         The chart options structure.
         * @param  {Function} [callback]
         *         Function to run when the chart has loaded and and all external images
         *         are loaded. Defining a {@link
         *         https://api.highcharts.com/highcharts/chart.events.load|chart.event.load}
         *         handler is equivalent.
         *
         * @example
         * var chart = Highcharts.chart('container', {
         * 	   title: {
         * 	   	   text: 'My chart'
         * 	   },
         * 	   series: [{
         * 	       data: [1, 3, 2, 4]
         * 	   }]
         * })
         */
        var Chart = H.Chart = function() {
            this.getArgs.apply(this, arguments);
        };

        /**
         * Factory function for basic charts. 
         *
         * @function #chart
         * @memberOf Highcharts
         * @param  {String|HTMLDOMElement} renderTo - The DOM element to render to, or
         * its id.
         * @param  {Options} options - The chart options structure.
         * @param  {Function} [callback] - Function to run when the chart has loaded and
         * and all external images are loaded. Defining a {@link
         * https://api.highcharts.com/highcharts/chart.events.load|chart.event.load}
         * handler is equivalent.
         * @return {Highcharts.Chart} - Returns the Chart object.
         *
         * @example
         * // Render a chart in to div#container
         * var chart = Highcharts.chart('container', {
         *     title: {
         *         text: 'My chart'
         *     },
         *     series: [{
         *         data: [1, 3, 2, 4]
         *     }]
         * });
         */
        H.chart = function(a, b, c) {
            return new Chart(a, b, c);
        };

        extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {

            // Hook for adding callbacks in modules
            callbacks: [],

            /**
             * Handle the arguments passed to the constructor.
             *
             * @private
             * @returns {Array} Arguments without renderTo
             */
            getArgs: function() {
                var args = [].slice.call(arguments);

                // Remove the optional first argument, renderTo, and
                // set it on this.
                if (isString(args[0]) || args[0].nodeName) {
                    this.renderTo = args.shift();
                }
                this.init(args[0], args[1]);
            },

            /**
             * Overridable function that initializes the chart. The constructor's
             * arguments are passed on directly.
             */
            init: function(userOptions, callback) {

                // Handle regular options
                var options,
                    type,
                    seriesOptions = userOptions.series, // skip merging data points to increase performance
                    userPlotOptions = userOptions.plotOptions || {};

                userOptions.series = null;
                options = merge(defaultOptions, userOptions); // do the merge

                // Override (by copy of user options) or clear tooltip options
                // in chart.options.plotOptions (#6218)
                for (type in options.plotOptions) {
                    options.plotOptions[type].tooltip = (
                        userPlotOptions[type] &&
                        merge(userPlotOptions[type].tooltip) // override by copy
                    ) || undefined; // or clear
                }
                // User options have higher priority than default options (#6218).
                // In case of exporting: path is changed
                options.tooltip.userOptions = (userOptions.chart &&
                        userOptions.chart.forExport && userOptions.tooltip.userOptions) ||
                    userOptions.tooltip;

                options.series = userOptions.series = seriesOptions; // set back the series data
                this.userOptions = userOptions;

                var optionsChart = options.chart;

                var chartEvents = optionsChart.events;

                this.margin = [];
                this.spacing = [];

                this.bounds = {
                    h: {},
                    v: {}
                }; // Pixel data bounds for touch zoom

                // An array of functions that returns labels that should be considered
                // for anti-collision
                this.labelCollectors = [];

                this.callback = callback;
                this.isResizing = 0;

                /**
                 * The options structure for the chart. It contains members for the sub
                 * elements like series, legend, tooltip etc.
                 *
                 * @memberof Highcharts.Chart
                 * @name options
                 * @type {Options}
                 */
                this.options = options;
                /**
                 * All the axes in the chart.
                 *
                 * @memberof Highcharts.Chart
                 * @name axes
                 * @see  Highcharts.Chart.xAxis
                 * @see  Highcharts.Chart.yAxis
                 * @type {Array.<Highcharts.Axis>}
                 */
                this.axes = [];

                /**
                 * All the current series in the chart.
                 *
                 * @memberof Highcharts.Chart
                 * @name series
                 * @type {Array.<Highcharts.Series>}
                 */
                this.series = [];

                /**
                 * The chart title. The title has an `update` method that allows
                 * modifying the options directly or indirectly via `chart.update`.
                 *
                 * @memberof Highcharts.Chart
                 * @name title
                 * @type Object
                 *
                 * @sample highcharts/members/title-update/
                 *         Updating titles
                 */

                /**
                 * The chart subtitle. The subtitle has an `update` method that allows
                 * modifying the options directly or indirectly via `chart.update`.
                 *
                 * @memberof Highcharts.Chart
                 * @name subtitle
                 * @type Object
                 */



                this.hasCartesianSeries = optionsChart.showAxes;

                var chart = this;

                // Add the chart to the global lookup
                chart.index = charts.length;

                charts.push(chart);
                H.chartCount++;

                // Chart event handlers
                if (chartEvents) {
                    objectEach(chartEvents, function(event, eventType) {
                        addEvent(chart, eventType, event);
                    });
                }

                /**
                 * A collection of the X axes in the chart.
                 * @type {Array.<Highcharts.Axis>}
                 * @name xAxis
                 * @memberOf Highcharts.Chart
                 */
                chart.xAxis = [];
                /**
                 * A collection of the Y axes in the chart.
                 * @type {Array.<Highcharts.Axis>}
                 * @name yAxis
                 * @memberOf Highcharts.Chart
                 */
                chart.yAxis = [];

                chart.pointCount = chart.colorCounter = chart.symbolCounter = 0;

                chart.firstRender();
            },

            /**
             * Internal function to unitialize an individual series.
             *
             * @private
             */
            initSeries: function(options) {
                var chart = this,
                    optionsChart = chart.options.chart,
                    type = options.type || optionsChart.type || optionsChart.defaultSeriesType,
                    series,
                    Constr = seriesTypes[type];

                // No such series type
                if (!Constr) {
                    H.error(17, true);
                }

                series = new Constr();
                series.init(this, options);
                return series;
            },

            /**
             * Order all series above a given index. When series are added and ordered
             * by configuration, only the last series is handled (#248, #1123, #2456,
             * #6112). This function is called on series initialization and destroy.
             *
             * @private
             *
             * @param  {number} fromIndex
             *         If this is given, only the series above this index are handled.
             */
            orderSeries: function(fromIndex) {
                var series = this.series,
                    i = fromIndex || 0;
                for (; i < series.length; i++) {
                    if (series[i]) {
                        series[i].index = i;
                        series[i].name = series[i].name ||
                            'Series ' + (series[i].index + 1);
                    }
                }
            },

            /**
             * Check whether a given point is within the plot area.
             *
             * @param  {Number} plotX
             *         Pixel x relative to the plot area.
             * @param  {Number} plotY
             *         Pixel y relative to the plot area.
             * @param  {Boolean} inverted
             *         Whether the chart is inverted.
             *
             * @return {Boolean}
             *         Returns true if the given point is inside the plot area.
             */
            isInsidePlot: function(plotX, plotY, inverted) {
                var x = inverted ? plotY : plotX,
                    y = inverted ? plotX : plotY;

                return x >= 0 &&
                    x <= this.plotWidth &&
                    y >= 0 &&
                    y <= this.plotHeight;
            },

            /**
             * Redraw the chart after changes have been done to the data, axis extremes
             * chart size or chart elements. All methods for updating axes, series or
             * points have a parameter for redrawing the chart. This is `true` by
             * default. But in many cases you want to do more than one operation on the
             * chart before redrawing, for example add a number of points. In those
             * cases it is a waste of resources to redraw the chart for each new point
             * added. So you add the points and call `chart.redraw()` after.
             *
             * @param  {AnimationOptions} animation
             *         If or how to apply animation to the redraw.
             */
            redraw: function(animation) {
                var chart = this,
                    axes = chart.axes,
                    series = chart.series,
                    pointer = chart.pointer,
                    legend = chart.legend,
                    redrawLegend = chart.isDirtyLegend,
                    hasStackedSeries,
                    hasDirtyStacks,
                    hasCartesianSeries = chart.hasCartesianSeries,
                    isDirtyBox = chart.isDirtyBox,
                    i,
                    serie,
                    renderer = chart.renderer,
                    isHiddenChart = renderer.isHidden(),
                    afterRedraw = [];

                // Handle responsive rules, not only on resize (#6130)
                if (chart.setResponsive) {
                    chart.setResponsive(false);
                }

                H.setAnimation(animation, chart);

                if (isHiddenChart) {
                    chart.temporaryDisplay();
                }

                // Adjust title layout (reflow multiline text)
                chart.layOutTitles();

                // link stacked series
                i = series.length;
                while (i--) {
                    serie = series[i];

                    if (serie.options.stacking) {
                        hasStackedSeries = true;

                        if (serie.isDirty) {
                            hasDirtyStacks = true;
                            break;
                        }
                    }
                }
                if (hasDirtyStacks) { // mark others as dirty
                    i = series.length;
                    while (i--) {
                        serie = series[i];
                        if (serie.options.stacking) {
                            serie.isDirty = true;
                        }
                    }
                }

                // Handle updated data in the series
                each(series, function(serie) {
                    if (serie.isDirty) {
                        if (serie.options.legendType === 'point') {
                            if (serie.updateTotals) {
                                serie.updateTotals();
                            }
                            redrawLegend = true;
                        }
                    }
                    if (serie.isDirtyData) {
                        fireEvent(serie, 'updatedData');
                    }
                });

                // handle added or removed series
                if (redrawLegend && legend.options.enabled) { // series or pie points are added or removed
                    // draw legend graphics
                    legend.render();

                    chart.isDirtyLegend = false;
                }

                // reset stacks
                if (hasStackedSeries) {
                    chart.getStacks();
                }


                if (hasCartesianSeries) {
                    // set axes scales
                    each(axes, function(axis) {
                        axis.updateNames();
                        axis.setScale();
                    });
                }

                chart.getMargins(); // #3098

                if (hasCartesianSeries) {
                    // If one axis is dirty, all axes must be redrawn (#792, #2169)
                    each(axes, function(axis) {
                        if (axis.isDirty) {
                            isDirtyBox = true;
                        }
                    });

                    // redraw axes
                    each(axes, function(axis) {

                        // Fire 'afterSetExtremes' only if extremes are set
                        var key = axis.min + ',' + axis.max;
                        if (axis.extKey !== key) { // #821, #4452
                            axis.extKey = key;
                            afterRedraw.push(function() { // prevent a recursive call to chart.redraw() (#1119)
                                fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751
                                delete axis.eventArgs;
                            });
                        }
                        if (isDirtyBox || hasStackedSeries) {
                            axis.redraw();
                        }
                    });
                }

                // the plot areas size has changed
                if (isDirtyBox) {
                    chart.drawChartBox();
                }

                // Fire an event before redrawing series, used by the boost module to
                // clear previous series renderings.
                fireEvent(chart, 'predraw');

                // redraw affected series
                each(series, function(serie) {
                    if ((isDirtyBox || serie.isDirty) && serie.visible) {
                        serie.redraw();
                    }
                    // Set it here, otherwise we will have unlimited 'updatedData' calls
                    // for a hidden series after setData(). Fixes #6012
                    serie.isDirtyData = false;
                });

                // move tooltip or reset
                if (pointer) {
                    pointer.reset(true);
                }

                // redraw if canvas
                renderer.draw();

                // Fire the events
                fireEvent(chart, 'redraw');
                fireEvent(chart, 'render');

                if (isHiddenChart) {
                    chart.temporaryDisplay(true);
                }

                // Fire callbacks that are put on hold until after the redraw
                each(afterRedraw, function(callback) {
                    callback.call();
                });
            },

            /**
             * Get an axis, series or point object by `id` as given in the configuration
             * options. Returns `undefined` if no item is found.
             * @param id {String} The id as given in the configuration options.
             * @return {Highcharts.Axis|Highcharts.Series|Highcharts.Point|undefined}
             *         The retrieved item.
             * @sample highcharts/plotoptions/series-id/
             *         Get series by id
             */
            get: function(id) {

                var ret,
                    series = this.series,
                    i;

                function itemById(item) {
                    return item.id === id || (item.options && item.options.id === id);
                }

                ret =
                    // Search axes
                    find(this.axes, itemById) ||

                    // Search series
                    find(this.series, itemById);

                // Search points
                for (i = 0; !ret && i < series.length; i++) {
                    ret = find(series[i].points || [], itemById);
                }

                return ret;
            },

            /**
             * Create the Axis instances based on the config options.
             *
             * @private
             */
            getAxes: function() {
                var chart = this,
                    options = this.options,
                    xAxisOptions = options.xAxis = splat(options.xAxis || {}),
                    yAxisOptions = options.yAxis = splat(options.yAxis || {}),
                    optionsArray;

                // make sure the options are arrays and add some members
                each(xAxisOptions, function(axis, i) {
                    axis.index = i;
                    axis.isX = true;
                });

                each(yAxisOptions, function(axis, i) {
                    axis.index = i;
                });

                // concatenate all axis options into one array
                optionsArray = xAxisOptions.concat(yAxisOptions);

                each(optionsArray, function(axisOptions) {
                    new Axis(chart, axisOptions); // eslint-disable-line no-new
                });
            },


            /**
             * Returns an array of all currently selected points in the chart. Points
             * can be selected by clicking or programmatically by the {@link
             * Highcharts.Point#select} function.
             *
             * @return {Array.<Highcharts.Point>}
             *         The currently selected points.
             *
             * @sample highcharts/plotoptions/series-allowpointselect-line/
             *         Get selected points
             */
            getSelectedPoints: function() {
                var points = [];
                each(this.series, function(serie) {
                    // series.data - for points outside of viewed range (#6445)
                    points = points.concat(grep(serie.data || [], function(point) {
                        return point.selected;
                    }));
                });
                return points;
            },

            /**
             * Returns an array of all currently selected series in the chart. Series
             * can be selected either programmatically by the {@link
             * Highcharts.Series#select} function or by checking the checkbox next to
             * the legend item if {@link
             * https://api.highcharts.com/highcharts/plotOptions.series.showCheckbox|
             * series.showCheckBox} is true.
             * 
             * @return {Array.<Highcharts.Series>}
             *         The currently selected series.
             *
             * @sample highcharts/members/chart-getselectedseries/
             *         Get selected series
             */
            getSelectedSeries: function() {
                return grep(this.series, function(serie) {
                    return serie.selected;
                });
            },

            /**
             * Set a new title or subtitle for the chart.
             *
             * @param  titleOptions {TitleOptions}
             *         New title options. The title text itself is set by the
             *         `titleOptions.text` property.
             * @param  subtitleOptions {SubtitleOptions}
             *         New subtitle options. The subtitle text itself is set by the
             *         `subtitleOptions.text` property.
             * @param  redraw {Boolean}
             *         Whether to redraw the chart or wait for a later call to 
             *         `chart.redraw()`.
             *
             * @sample highcharts/members/chart-settitle/ Set title text and styles
             *
             */
            setTitle: function(titleOptions, subtitleOptions, redraw) {
                var chart = this,
                    options = chart.options,
                    chartTitleOptions,
                    chartSubtitleOptions;

                chartTitleOptions = options.title = merge(

                    // Default styles
                    {
                        style: {
                            color: '#333333',
                            fontSize: options.isStock ? '16px' : '18px' // #2944
                        }
                    },

                    options.title,
                    titleOptions
                );
                chartSubtitleOptions = options.subtitle = merge(

                    // Default styles
                    {
                        style: {
                            color: '#666666'
                        }
                    },

                    options.subtitle,
                    subtitleOptions
                );

                // add title and subtitle
                each([
                    ['title', titleOptions, chartTitleOptions],
                    ['subtitle', subtitleOptions, chartSubtitleOptions]
                ], function(arr, i) {
                    var name = arr[0],
                        title = chart[name],
                        titleOptions = arr[1],
                        chartTitleOptions = arr[2];

                    if (title && titleOptions) {
                        chart[name] = title = title.destroy(); // remove old
                    }

                    if (chartTitleOptions && !title) {
                        chart[name] = chart.renderer.text(
                                chartTitleOptions.text,
                                0,
                                0,
                                chartTitleOptions.useHTML
                            )
                            .attr({
                                align: chartTitleOptions.align,
                                'class': 'highcharts-' + name,
                                zIndex: chartTitleOptions.zIndex || 4
                            })
                            .add();

                        // Update methods, shortcut to Chart.setTitle
                        chart[name].update = function(o) {
                            chart.setTitle(!i && o, i && o);
                        };


                        // Presentational
                        chart[name].css(chartTitleOptions.style);


                    }
                });
                chart.layOutTitles(redraw);
            },

            /**
             * Internal function to lay out the chart titles and cache the full offset
             * height for use in `getMargins`. The result is stored in 
             * `this.titleOffset`.
             *
             * @private
             */
            layOutTitles: function(redraw) {
                var titleOffset = 0,
                    requiresDirtyBox,
                    renderer = this.renderer,
                    spacingBox = this.spacingBox;

                // Lay out the title and the subtitle respectively
                each(['title', 'subtitle'], function(key) {
                    var title = this[key],
                        titleOptions = this.options[key],
                        offset = key === 'title' ? -3 :
                        // Floating subtitle (#6574)
                        titleOptions.verticalAlign ? 0 : titleOffset + 2,
                        titleSize;

                    if (title) {

                        titleSize = titleOptions.style.fontSize;

                        titleSize = renderer.fontMetrics(titleSize, title).b;

                        title
                            .css({
                                width: (titleOptions.width ||
                                    spacingBox.width + titleOptions.widthAdjust) + 'px'
                            })
                            .align(extend({
                                y: offset + titleSize
                            }, titleOptions), false, 'spacingBox');

                        if (!titleOptions.floating && !titleOptions.verticalAlign) {
                            titleOffset = Math.ceil(
                                titleOffset +
                                // Skip the cache for HTML (#3481)
                                title.getBBox(titleOptions.useHTML).height
                            );
                        }
                    }
                }, this);

                requiresDirtyBox = this.titleOffset !== titleOffset;
                this.titleOffset = titleOffset; // used in getMargins

                if (!this.isDirtyBox && requiresDirtyBox) {
                    this.isDirtyBox = requiresDirtyBox;
                    // Redraw if necessary (#2719, #2744)
                    if (this.hasRendered && pick(redraw, true) && this.isDirtyBox) {
                        this.redraw();
                    }
                }
            },

            /**
             * Internal function to get the chart width and height according to options
             * and container size. Sets {@link Chart.chartWidth} and {@link
             * Chart.chartHeight}.
             */
            getChartSize: function() {
                var chart = this,
                    optionsChart = chart.options.chart,
                    widthOption = optionsChart.width,
                    heightOption = optionsChart.height,
                    renderTo = chart.renderTo;

                // Get inner width and height
                if (!defined(widthOption)) {
                    chart.containerWidth = H.getStyle(renderTo, 'width');
                }
                if (!defined(heightOption)) {
                    chart.containerHeight = H.getStyle(renderTo, 'height');
                }

                /**
                 * The current pixel width of the chart.
                 *
                 * @name chartWidth
                 * @memberOf Chart
                 * @type {Number}
                 */
                chart.chartWidth = Math.max( // #1393
                    0,
                    widthOption || chart.containerWidth || 600 // #1460
                );
                /**
                 * The current pixel height of the chart.
                 *
                 * @name chartHeight
                 * @memberOf Chart
                 * @type {Number}
                 */
                chart.chartHeight = Math.max(
                    0,
                    H.relativeLength(
                        heightOption,
                        chart.chartWidth
                    ) ||
                    (chart.containerHeight > 1 ? chart.containerHeight : 400)
                );
            },

            /**
             * If the renderTo element has no offsetWidth, most likely one or more of
             * its parents are hidden. Loop up the DOM tree to temporarily display the
             * parents, then save the original display properties, and when the true
             * size is retrieved, reset them. Used on first render and on redraws.
             *
             * @private
             * 
             * @param  {Boolean} revert
             *         Revert to the saved original styles.
             */
            temporaryDisplay: function(revert) {
                var node = this.renderTo,
                    tempStyle;
                if (!revert) {
                    while (node && node.style) {

                        // When rendering to a detached node, it needs to be temporarily
                        // attached in order to read styling and bounding boxes (#5783,
                        // #7024).
                        if (!doc.body.contains(node) && !node.parentNode) {
                            node.hcOrigDetached = true;
                            doc.body.appendChild(node);
                        }
                        if (
                            H.getStyle(node, 'display', false) === 'none' ||
                            node.hcOricDetached
                        ) {
                            node.hcOrigStyle = {
                                display: node.style.display,
                                height: node.style.height,
                                overflow: node.style.overflow
                            };
                            tempStyle = {
                                display: 'block',
                                overflow: 'hidden'
                            };
                            if (node !== this.renderTo) {
                                tempStyle.height = 0;
                            }

                            H.css(node, tempStyle);

                            // If it still doesn't have an offset width after setting
                            // display to block, it probably has an !important priority
                            // #2631, 6803
                            if (!node.offsetWidth) {
                                node.style.setProperty('display', 'block', 'important');
                            }
                        }
                        node = node.parentNode;

                        if (node === doc.body) {
                            break;
                        }
                    }
                } else {
                    while (node && node.style) {
                        if (node.hcOrigStyle) {
                            H.css(node, node.hcOrigStyle);
                            delete node.hcOrigStyle;
                        }
                        if (node.hcOrigDetached) {
                            doc.body.removeChild(node);
                            node.hcOrigDetached = false;
                        }
                        node = node.parentNode;
                    }
                }
            },

            /**
             * Set the {@link Chart.container|chart container's} class name, in
             * addition to `highcharts-container`. 
             */
            setClassName: function(className) {
                this.container.className = 'highcharts-container ' + (className || '');
            },

            /**
             * Get the containing element, determine the size and create the inner
             * container div to hold the chart.
             *
             * @private
             */
            getContainer: function() {
                var chart = this,
                    container,
                    options = chart.options,
                    optionsChart = options.chart,
                    chartWidth,
                    chartHeight,
                    renderTo = chart.renderTo,
                    indexAttrName = 'data-highcharts-chart',
                    oldChartIndex,
                    Ren,
                    containerId = H.uniqueKey(),
                    containerStyle,
                    key;

                if (!renderTo) {
                    chart.renderTo = renderTo = optionsChart.renderTo;
                }

                if (isString(renderTo)) {
                    chart.renderTo = renderTo = doc.getElementById(renderTo);
                }

                // Display an error if the renderTo is wrong
                if (!renderTo) {
                    H.error(13, true);
                }

                // If the container already holds a chart, destroy it. The check for
                // hasRendered is there because web pages that are saved to disk from
                // the browser, will preserve the data-highcharts-chart attribute and
                // the SVG contents, but not an interactive chart. So in this case,
                // charts[oldChartIndex] will point to the wrong chart if any (#2609).
                oldChartIndex = pInt(attr(renderTo, indexAttrName));
                if (
                    isNumber(oldChartIndex) &&
                    charts[oldChartIndex] &&
                    charts[oldChartIndex].hasRendered
                ) {
                    charts[oldChartIndex].destroy();
                }

                // Make a reference to the chart from the div
                attr(renderTo, indexAttrName, chart.index);

                // remove previous chart
                renderTo.innerHTML = '';

                // If the container doesn't have an offsetWidth, it has or is a child of
                // a node that has display:none. We need to temporarily move it out to a
                // visible state to determine the size, else the legend and tooltips
                // won't render properly. The skipClone option is used in sparklines as
                // a micro optimization, saving about 1-2 ms each chart.
                if (!optionsChart.skipClone && !renderTo.offsetWidth) {
                    chart.temporaryDisplay();
                }

                // get the width and height
                chart.getChartSize();
                chartWidth = chart.chartWidth;
                chartHeight = chart.chartHeight;

                // Create the inner container

                containerStyle = extend({
                    position: 'relative',
                    overflow: 'hidden', // needed for context menu (avoid scrollbars)
                    // and content overflow in IE
                    width: chartWidth + 'px',
                    height: chartHeight + 'px',
                    textAlign: 'left',
                    lineHeight: 'normal', // #427
                    zIndex: 0, // #1072
                    '-webkit-tap-highlight-color': 'rgba(0,0,0,0)'
                }, optionsChart.style);


                /**
                 * The containing HTML element of the chart. The container is
                 * dynamically inserted into the element given as the `renderTo`
                 * parameterin the {@link Highcharts#chart} constructor.
                 *
                 * @memberOf Highcharts.Chart
                 * @type {HTMLDOMElement}
                 */
                container = createElement(
                    'div', {
                        id: containerId
                    },
                    containerStyle,
                    renderTo
                );
                chart.container = container;

                // cache the cursor (#1650)
                chart._cursor = container.style.cursor;

                // Initialize the renderer
                Ren = H[optionsChart.renderer] || H.Renderer;

                /**
                 * The renderer instance of the chart. Each chart instance has only one
                 * associated renderer.
                 * @type {SVGRenderer}
                 * @name renderer
                 * @memberOf Chart
                 */
                chart.renderer = new Ren(
                    container,
                    chartWidth,
                    chartHeight,
                    null,
                    optionsChart.forExport,
                    options.exporting && options.exporting.allowHTML
                );


                chart.setClassName(optionsChart.className);

                chart.renderer.setStyle(optionsChart.style);


                // Add a reference to the charts index
                chart.renderer.chartIndex = chart.index;
            },

            /**
             * Calculate margins by rendering axis labels in a preliminary position.
             * Title, subtitle and legend have already been rendered at this stage, but
             * will be moved into their final positions.
             *
             * @private
             */
            getMargins: function(skipAxes) {
                var chart = this,
                    spacing = chart.spacing,
                    margin = chart.margin,
                    titleOffset = chart.titleOffset;

                chart.resetMargins();

                // Adjust for title and subtitle
                if (titleOffset && !defined(margin[0])) {
                    chart.plotTop = Math.max(
                        chart.plotTop,
                        titleOffset + chart.options.title.margin + spacing[0]
                    );
                }

                // Adjust for legend
                if (chart.legend && chart.legend.display) {
                    chart.legend.adjustMargins(margin, spacing);
                }

                // adjust for scroller
                if (chart.extraMargin) {
                    chart[chart.extraMargin.type] =
                        (chart[chart.extraMargin.type] || 0) + chart.extraMargin.value;
                }

                // adjust for rangeSelector 
                if (chart.adjustPlotArea) {
                    chart.adjustPlotArea();
                }

                if (!skipAxes) {
                    this.getAxisMargins();
                }
            },

            getAxisMargins: function() {

                var chart = this,
                    // [top, right, bottom, left]
                    axisOffset = chart.axisOffset = [0, 0, 0, 0],
                    margin = chart.margin;

                // pre-render axes to get labels offset width
                if (chart.hasCartesianSeries) {
                    each(chart.axes, function(axis) {
                        if (axis.visible) {
                            axis.getOffset();
                        }
                    });
                }

                // Add the axis offsets
                each(marginNames, function(m, side) {
                    if (!defined(margin[side])) {
                        chart[m] += axisOffset[side];
                    }
                });

                chart.setChartSize();

            },

            /**
             * Reflows the chart to its container. By default, the chart reflows
             * automatically to its container following a `window.resize` event, as per
             * the {@link https://api.highcharts/highcharts/chart.reflow|chart.reflow}
             * option. However, there are no reliable events for div resize, so if the
             * container is resized without a window resize event, this must be called
             * explicitly.
             *
             * @param  {Object} e
             *         Event arguments. Used primarily when the function is called
             *         internally as a response to window resize.
             *
             * @sample highcharts/members/chart-reflow/
             *         Resize div and reflow
             * @sample highcharts/chart/events-container/
             *         Pop up and reflow
             */
            reflow: function(e) {
                var chart = this,
                    optionsChart = chart.options.chart,
                    renderTo = chart.renderTo,
                    hasUserSize = (
                        defined(optionsChart.width) &&
                        defined(optionsChart.height)
                    ),
                    width = optionsChart.width || H.getStyle(renderTo, 'width'),
                    height = optionsChart.height || H.getStyle(renderTo, 'height'),
                    target = e ? e.target : win;

                // Width and height checks for display:none. Target is doc in IE8 and
                // Opera, win in Firefox, Chrome and IE9.
                if (!hasUserSize &&
                    !chart.isPrinting &&
                    width &&
                    height &&
                    (target === win || target === doc)
                ) {
                    if (
                        width !== chart.containerWidth ||
                        height !== chart.containerHeight
                    ) {
                        clearTimeout(chart.reflowTimeout);
                        // When called from window.resize, e is set, else it's called
                        // directly (#2224)
                        chart.reflowTimeout = syncTimeout(function() {
                            // Set size, it may have been destroyed in the meantime
                            // (#1257)
                            if (chart.container) {
                                chart.setSize(undefined, undefined, false);
                            }
                        }, e ? 100 : 0);
                    }
                    chart.containerWidth = width;
                    chart.containerHeight = height;
                }
            },

            /**
             * Add the event handlers necessary for auto resizing, depending on the 
             * `chart.events.reflow` option.
             *
             * @private
             */
            initReflow: function() {
                var chart = this,
                    unbind;

                unbind = addEvent(win, 'resize', function(e) {
                    chart.reflow(e);
                });
                addEvent(chart, 'destroy', unbind);

                // The following will add listeners to re-fit the chart before and after
                // printing (#2284). However it only works in WebKit. Should have worked
                // in Firefox, but not supported in IE.
                /*
                if (win.matchMedia) {
                	win.matchMedia('print').addListener(function reflow() {
                		chart.reflow();
                	});
                }
                */
            },

            /**
             * Resize the chart to a given width and height. In order to set the width
             * only, the height argument may be skipped. To set the height only, pass
             * `undefined for the width.
             * @param  {Number|undefined|null} [width]
             *         The new pixel width of the chart. Since v4.2.6, the argument can
             *         be `undefined` in order to preserve the current value (when
             *         setting height only), or `null` to adapt to the width of the
             *         containing element.
             * @param  {Number|undefined|null} [height]
             *         The new pixel height of the chart. Since v4.2.6, the argument can
             *         be `undefined` in order to preserve the current value, or `null`
             *         in order to adapt to the height of the containing element.
             * @param  {AnimationOptions} [animation=true]
             *         Whether and how to apply animation.
             *
             * @sample highcharts/members/chart-setsize-button/
             *         Test resizing from buttons
             * @sample highcharts/members/chart-setsize-jquery-resizable/
             *         Add a jQuery UI resizable
             * @sample stock/members/chart-setsize/
             *         Highstock with UI resizable
             */
            setSize: function(width, height, animation) {
                var chart = this,
                    renderer = chart.renderer,
                    globalAnimation;

                // Handle the isResizing counter
                chart.isResizing += 1;

                // set the animation for the current process
                H.setAnimation(animation, chart);

                chart.oldChartHeight = chart.chartHeight;
                chart.oldChartWidth = chart.chartWidth;
                if (width !== undefined) {
                    chart.options.chart.width = width;
                }
                if (height !== undefined) {
                    chart.options.chart.height = height;
                }
                chart.getChartSize();

                // Resize the container with the global animation applied if enabled
                // (#2503)

                globalAnimation = renderer.globalAnimation;
                (globalAnimation ? animate : css)(chart.container, {
                    width: chart.chartWidth + 'px',
                    height: chart.chartHeight + 'px'
                }, globalAnimation);


                chart.setChartSize(true);
                renderer.setSize(chart.chartWidth, chart.chartHeight, animation);

                // handle axes
                each(chart.axes, function(axis) {
                    axis.isDirty = true;
                    axis.setScale();
                });

                chart.isDirtyLegend = true; // force legend redraw
                chart.isDirtyBox = true; // force redraw of plot and chart border

                chart.layOutTitles(); // #2857
                chart.getMargins();

                chart.redraw(animation);


                chart.oldChartHeight = null;
                fireEvent(chart, 'resize');

                // Fire endResize and set isResizing back. If animation is disabled,
                // fire without delay
                syncTimeout(function() {
                    if (chart) {
                        fireEvent(chart, 'endResize', null, function() {
                            chart.isResizing -= 1;
                        });
                    }
                }, animObject(globalAnimation).duration);
            },

            /**
             * Set the public chart properties. This is done before and after the
             * pre-render to determine margin sizes.
             *
             * @private
             */
            setChartSize: function(skipAxes) {
                var chart = this,
                    inverted = chart.inverted,
                    renderer = chart.renderer,
                    chartWidth = chart.chartWidth,
                    chartHeight = chart.chartHeight,
                    optionsChart = chart.options.chart,
                    spacing = chart.spacing,
                    clipOffset = chart.clipOffset,
                    clipX,
                    clipY,
                    plotLeft,
                    plotTop,
                    plotWidth,
                    plotHeight,
                    plotBorderWidth;

                /**
                 * The current left position of the plot area in pixels.
                 *
                 * @name plotLeft
                 * @memberOf Chart
                 * @type {Number}
                 */
                chart.plotLeft = plotLeft = Math.round(chart.plotLeft);

                /**
                 * The current top position of the plot area in pixels.
                 *
                 * @name plotTop
                 * @memberOf Chart
                 * @type {Number}
                 */
                chart.plotTop = plotTop = Math.round(chart.plotTop);

                /**
                 * The current width of the plot area in pixels.
                 *
                 * @name plotWidth
                 * @memberOf Chart
                 * @type {Number}
                 */
                chart.plotWidth = plotWidth = Math.max(
                    0,
                    Math.round(chartWidth - plotLeft - chart.marginRight)
                );

                /**
                 * The current height of the plot area in pixels.
                 *
                 * @name plotHeight
                 * @memberOf Chart
                 * @type {Number}
                 */
                chart.plotHeight = plotHeight = Math.max(
                    0,
                    Math.round(chartHeight - plotTop - chart.marginBottom)
                );

                chart.plotSizeX = inverted ? plotHeight : plotWidth;
                chart.plotSizeY = inverted ? plotWidth : plotHeight;

                chart.plotBorderWidth = optionsChart.plotBorderWidth || 0;

                // Set boxes used for alignment
                chart.spacingBox = renderer.spacingBox = {
                    x: spacing[3],
                    y: spacing[0],
                    width: chartWidth - spacing[3] - spacing[1],
                    height: chartHeight - spacing[0] - spacing[2]
                };
                chart.plotBox = renderer.plotBox = {
                    x: plotLeft,
                    y: plotTop,
                    width: plotWidth,
                    height: plotHeight
                };

                plotBorderWidth = 2 * Math.floor(chart.plotBorderWidth / 2);
                clipX = Math.ceil(Math.max(plotBorderWidth, clipOffset[3]) / 2);
                clipY = Math.ceil(Math.max(plotBorderWidth, clipOffset[0]) / 2);
                chart.clipBox = {
                    x: clipX,
                    y: clipY,
                    width: Math.floor(
                        chart.plotSizeX -
                        Math.max(plotBorderWidth, clipOffset[1]) / 2 -
                        clipX
                    ),
                    height: Math.max(
                        0,
                        Math.floor(
                            chart.plotSizeY -
                            Math.max(plotBorderWidth, clipOffset[2]) / 2 -
                            clipY
                        )
                    )
                };

                if (!skipAxes) {
                    each(chart.axes, function(axis) {
                        axis.setAxisSize();
                        axis.setAxisTranslation();
                    });
                }
            },

            /**
             * Initial margins before auto size margins are applied.
             *
             * @private
             */
            resetMargins: function() {
                var chart = this,
                    chartOptions = chart.options.chart;

                // Create margin and spacing array
                each(['margin', 'spacing'], function splashArrays(target) {
                    var value = chartOptions[target],
                        values = isObject(value) ? value : [value, value, value, value];

                    each(['Top', 'Right', 'Bottom', 'Left'], function(sideName, side) {
                        chart[target][side] = pick(
                            chartOptions[target + sideName],
                            values[side]
                        );
                    });
                });

                // Set margin names like chart.plotTop, chart.plotLeft,
                // chart.marginRight, chart.marginBottom.
                each(marginNames, function(m, side) {
                    chart[m] = pick(chart.margin[side], chart.spacing[side]);
                });
                chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left
                chart.clipOffset = [0, 0, 0, 0];
            },

            /**
             * Internal function to draw or redraw the borders and backgrounds for chart
             * and plot area.
             *
             * @private
             */
            drawChartBox: function() {
                var chart = this,
                    optionsChart = chart.options.chart,
                    renderer = chart.renderer,
                    chartWidth = chart.chartWidth,
                    chartHeight = chart.chartHeight,
                    chartBackground = chart.chartBackground,
                    plotBackground = chart.plotBackground,
                    plotBorder = chart.plotBorder,
                    chartBorderWidth,

                    plotBGImage = chart.plotBGImage,
                    chartBackgroundColor = optionsChart.backgroundColor,
                    plotBackgroundColor = optionsChart.plotBackgroundColor,
                    plotBackgroundImage = optionsChart.plotBackgroundImage,

                    mgn,
                    bgAttr,
                    plotLeft = chart.plotLeft,
                    plotTop = chart.plotTop,
                    plotWidth = chart.plotWidth,
                    plotHeight = chart.plotHeight,
                    plotBox = chart.plotBox,
                    clipRect = chart.clipRect,
                    clipBox = chart.clipBox,
                    verb = 'animate';

                // Chart area
                if (!chartBackground) {
                    chart.chartBackground = chartBackground = renderer.rect()
                        .addClass('highcharts-background')
                        .add();
                    verb = 'attr';
                }


                // Presentational
                chartBorderWidth = optionsChart.borderWidth || 0;
                mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);

                bgAttr = {
                    fill: chartBackgroundColor || 'none'
                };

                if (chartBorderWidth || chartBackground['stroke-width']) { // #980
                    bgAttr.stroke = optionsChart.borderColor;
                    bgAttr['stroke-width'] = chartBorderWidth;
                }
                chartBackground
                    .attr(bgAttr)
                    .shadow(optionsChart.shadow);

                chartBackground[verb]({
                    x: mgn / 2,
                    y: mgn / 2,
                    width: chartWidth - mgn - chartBorderWidth % 2,
                    height: chartHeight - mgn - chartBorderWidth % 2,
                    r: optionsChart.borderRadius
                });

                // Plot background
                verb = 'animate';
                if (!plotBackground) {
                    verb = 'attr';
                    chart.plotBackground = plotBackground = renderer.rect()
                        .addClass('highcharts-plot-background')
                        .add();
                }
                plotBackground[verb](plotBox);


                // Presentational attributes for the background
                plotBackground
                    .attr({
                        fill: plotBackgroundColor || 'none'
                    })
                    .shadow(optionsChart.plotShadow);

                // Create the background image
                if (plotBackgroundImage) {
                    if (!plotBGImage) {
                        chart.plotBGImage = renderer.image(
                            plotBackgroundImage,
                            plotLeft,
                            plotTop,
                            plotWidth,
                            plotHeight
                        ).add();
                    } else {
                        plotBGImage.animate(plotBox);
                    }
                }


                // Plot clip
                if (!clipRect) {
                    chart.clipRect = renderer.clipRect(clipBox);
                } else {
                    clipRect.animate({
                        width: clipBox.width,
                        height: clipBox.height
                    });
                }

                // Plot area border
                verb = 'animate';
                if (!plotBorder) {
                    verb = 'attr';
                    chart.plotBorder = plotBorder = renderer.rect()
                        .addClass('highcharts-plot-border')
                        .attr({
                            zIndex: 1 // Above the grid
                        })
                        .add();
                }


                // Presentational
                plotBorder.attr({
                    stroke: optionsChart.plotBorderColor,
                    'stroke-width': optionsChart.plotBorderWidth || 0,
                    fill: 'none'
                });


                plotBorder[verb](plotBorder.crisp({
                    x: plotLeft,
                    y: plotTop,
                    width: plotWidth,
                    height: plotHeight
                }, -plotBorder.strokeWidth())); // #3282 plotBorder should be negative;

                // reset
                chart.isDirtyBox = false;
            },

            /**
             * Detect whether a certain chart property is needed based on inspecting its
             * options and series. This mainly applies to the chart.inverted property,
             * and in extensions to the chart.angular and chart.polar properties.
             *
             * @private
             */
            propFromSeries: function() {
                var chart = this,
                    optionsChart = chart.options.chart,
                    klass,
                    seriesOptions = chart.options.series,
                    i,
                    value;


                each(['inverted', 'angular', 'polar'], function(key) {

                    // The default series type's class
                    klass = seriesTypes[optionsChart.type ||
                        optionsChart.defaultSeriesType];

                    // Get the value from available chart-wide properties
                    value =
                        optionsChart[key] || // It is set in the options
                        (klass && klass.prototype[key]); // The default series class
                    // requires it

                    // 4. Check if any the chart's series require it
                    i = seriesOptions && seriesOptions.length;
                    while (!value && i--) {
                        klass = seriesTypes[seriesOptions[i].type];
                        if (klass && klass.prototype[key]) {
                            value = true;
                        }
                    }

                    // Set the chart property
                    chart[key] = value;
                });

            },

            /**
             * Internal function to link two or more series together, based on the 
             * `linkedTo` option. This is done from `Chart.render`, and after
             * `Chart.addSeries` and `Series.remove`.
             *
             * @private
             */
            linkSeries: function() {
                var chart = this,
                    chartSeries = chart.series;

                // Reset links
                each(chartSeries, function(series) {
                    series.linkedSeries.length = 0;
                });

                // Apply new links
                each(chartSeries, function(series) {
                    var linkedTo = series.options.linkedTo;
                    if (isString(linkedTo)) {
                        if (linkedTo === ':previous') {
                            linkedTo = chart.series[series.index - 1];
                        } else {
                            linkedTo = chart.get(linkedTo);
                        }
                        // #3341 avoid mutual linking
                        if (linkedTo && linkedTo.linkedParent !== series) {
                            linkedTo.linkedSeries.push(series);
                            series.linkedParent = linkedTo;
                            series.visible = pick(
                                series.options.visible,
                                linkedTo.options.visible,
                                series.visible
                            ); // #3879
                        }
                    }
                });
            },

            /**
             * Render series for the chart.
             *
             * @private
             */
            renderSeries: function() {
                each(this.series, function(serie) {
                    serie.translate();
                    serie.render();
                });
            },

            /**
             * Render labels for the chart.
             *
             * @private
             */
            renderLabels: function() {
                var chart = this,
                    labels = chart.options.labels;
                if (labels.items) {
                    each(labels.items, function(label) {
                        var style = extend(labels.style, label.style),
                            x = pInt(style.left) + chart.plotLeft,
                            y = pInt(style.top) + chart.plotTop + 12;

                        // delete to prevent rewriting in IE
                        delete style.left;
                        delete style.top;

                        chart.renderer.text(
                                label.html,
                                x,
                                y
                            )
                            .attr({
                                zIndex: 2
                            })
                            .css(style)
                            .add();

                    });
                }
            },

            /**
             * Render all graphics for the chart. Runs internally on initialization.
             *
             * @private
             */
            render: function() {
                var chart = this,
                    axes = chart.axes,
                    renderer = chart.renderer,
                    options = chart.options,
                    tempWidth,
                    tempHeight,
                    redoHorizontal,
                    redoVertical;

                // Title
                chart.setTitle();


                // Legend
                chart.legend = new Legend(chart, options.legend);

                // Get stacks
                if (chart.getStacks) {
                    chart.getStacks();
                }

                // Get chart margins
                chart.getMargins(true);
                chart.setChartSize();

                // Record preliminary dimensions for later comparison
                tempWidth = chart.plotWidth;
                // 21 is the most common correction for X axis labels
                // use Math.max to prevent negative plotHeight
                tempHeight = chart.plotHeight = Math.max(chart.plotHeight - 21, 0);

                // Get margins by pre-rendering axes
                each(axes, function(axis) {
                    axis.setScale();
                });
                chart.getAxisMargins();

                // If the plot area size has changed significantly, calculate tick positions again
                redoHorizontal = tempWidth / chart.plotWidth > 1.1;
                redoVertical = tempHeight / chart.plotHeight > 1.05; // Height is more sensitive

                if (redoHorizontal || redoVertical) {

                    each(axes, function(axis) {
                        if ((axis.horiz && redoHorizontal) || (!axis.horiz && redoVertical)) {
                            axis.setTickInterval(true); // update to reflect the new margins
                        }
                    });
                    chart.getMargins(); // second pass to check for new labels
                }

                // Draw the borders and backgrounds
                chart.drawChartBox();


                // Axes
                if (chart.hasCartesianSeries) {
                    each(axes, function(axis) {
                        if (axis.visible) {
                            axis.render();
                        }
                    });
                }

                // The series
                if (!chart.seriesGroup) {
                    chart.seriesGroup = renderer.g('series-group')
                        .attr({
                            zIndex: 3
                        })
                        .add();
                }
                chart.renderSeries();

                // Labels
                chart.renderLabels();

                // Credits
                chart.addCredits();

                // Handle responsiveness
                if (chart.setResponsive) {
                    chart.setResponsive();
                }

                // Set flag
                chart.hasRendered = true;

            },

            /**
             * Set a new credits label for the chart.
             *
             * @param  {CreditOptions} options
             *         A configuration object for the new credits.
             * @sample highcharts/credits/credits-update/ Add and update credits
             */
            addCredits: function(credits) {
                var chart = this;

                credits = merge(true, this.options.credits, credits);
                if (credits.enabled && !this.credits) {

                    /**
                     * The chart's credits label. The label has an `update` method that
                     * allows setting new options as per the {@link
                     * https://api.highcharts.com/highcharts/credits|
                     * credits options set}.
                     *
                     * @memberof Highcharts.Chart
                     * @name credits
                     * @type {Highcharts.SVGElement}
                     */
                    this.credits = this.renderer.text(
                            credits.text + (this.mapCredits || ''),
                            0,
                            0
                        )
                        .addClass('highcharts-credits')
                        .on('click', function() {
                            if (credits.href) {
                                win.location.href = credits.href;
                            }
                        })
                        .attr({
                            align: credits.position.align,
                            zIndex: 8
                        })

                        .css(credits.style)

                        .add()
                        .align(credits.position);

                    // Dynamically update
                    this.credits.update = function(options) {
                        chart.credits = chart.credits.destroy();
                        chart.addCredits(options);
                    };
                }
            },

            /**
             * Remove the chart and purge memory. This method is called internally
             * before adding a second chart into the same container, as well as on
             * window unload to prevent leaks.
             *
             * @sample highcharts/members/chart-destroy/
             *         Destroy the chart from a button
             * @sample stock/members/chart-destroy/
             *         Destroy with Highstock
             */
            destroy: function() {
                var chart = this,
                    axes = chart.axes,
                    series = chart.series,
                    container = chart.container,
                    i,
                    parentNode = container && container.parentNode;

                // fire the chart.destoy event
                fireEvent(chart, 'destroy');

                // Delete the chart from charts lookup array
                if (chart.renderer.forExport) {
                    H.erase(charts, chart); // #6569
                } else {
                    charts[chart.index] = undefined;
                }
                H.chartCount--;
                chart.renderTo.removeAttribute('data-highcharts-chart');

                // remove events
                removeEvent(chart);

                // ==== Destroy collections:
                // Destroy axes
                i = axes.length;
                while (i--) {
                    axes[i] = axes[i].destroy();
                }

                // Destroy scroller & scroller series before destroying base series
                if (this.scroller && this.scroller.destroy) {
                    this.scroller.destroy();
                }

                // Destroy each series
                i = series.length;
                while (i--) {
                    series[i] = series[i].destroy();
                }

                // ==== Destroy chart properties:
                each([
                    'title', 'subtitle', 'chartBackground', 'plotBackground',
                    'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits',
                    'pointer', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip',
                    'renderer'
                ], function(name) {
                    var prop = chart[name];

                    if (prop && prop.destroy) {
                        chart[name] = prop.destroy();
                    }
                });

                // remove container and all SVG
                if (container) { // can break in IE when destroyed before finished loading
                    container.innerHTML = '';
                    removeEvent(container);
                    if (parentNode) {
                        discardElement(container);
                    }

                }

                // clean it all up
                objectEach(chart, function(val, key) {
                    delete chart[key];
                });

            },


            /**
             * VML namespaces can't be added until after complete. Listening
             * for Perini's doScroll hack is not enough.
             *
             * @private
             */
            isReadyToRender: function() {
                var chart = this;

                // Note: win == win.top is required
                if ((!svg && (win == win.top && doc.readyState !== 'complete'))) { // eslint-disable-line eqeqeq
                    doc.attachEvent('onreadystatechange', function() {
                        doc.detachEvent('onreadystatechange', chart.firstRender);
                        if (doc.readyState === 'complete') {
                            chart.firstRender();
                        }
                    });
                    return false;
                }
                return true;
            },

            /**
             * Prepare for first rendering after all data are loaded.
             *
             * @private
             */
            firstRender: function() {
                var chart = this,
                    options = chart.options;

                // Check whether the chart is ready to render
                if (!chart.isReadyToRender()) {
                    return;
                }

                // Create the container
                chart.getContainer();

                // Run an early event after the container and renderer are established
                fireEvent(chart, 'init');


                chart.resetMargins();
                chart.setChartSize();

                // Set the common chart properties (mainly invert) from the given series
                chart.propFromSeries();

                // get axes
                chart.getAxes();

                // Initialize the series
                each(options.series || [], function(serieOptions) {
                    chart.initSeries(serieOptions);
                });

                chart.linkSeries();

                // Run an event after axes and series are initialized, but before render. At this stage,
                // the series data is indexed and cached in the xData and yData arrays, so we can access
                // those before rendering. Used in Highstock.
                fireEvent(chart, 'beforeRender');

                // depends on inverted and on margins being set
                if (Pointer) {

                    /**
                     * The Pointer that keeps track of mouse and touch interaction.
                     *
                     * @memberof Chart
                     * @name pointer
                     * @type Pointer
                     */
                    chart.pointer = new Pointer(chart, options);
                }

                chart.render();

                // Fire the load event if there are no external images
                if (!chart.renderer.imgCount && chart.onload) {
                    chart.onload();
                }

                // If the chart was rendered outside the top container, put it back in (#3679)
                chart.temporaryDisplay(true);

            },

            /** 
             * Internal function that runs on chart load, async if any images are loaded
             * in the chart. Runs the callbacks and triggers the `load` and `render`
             * events.
             *
             * @private
             */
            onload: function() {

                // Run callbacks
                each([this.callback].concat(this.callbacks), function(fn) {
                    if (fn && this.index !== undefined) { // Chart destroyed in its own callback (#3600)
                        fn.apply(this, [this]);
                    }
                }, this);

                fireEvent(this, 'load');
                fireEvent(this, 'render');


                // Set up auto resize, check for not destroyed (#6068)
                if (defined(this.index) && this.options.chart.reflow !== false) {
                    this.initReflow();
                }

                // Don't run again
                this.onload = null;
            }

        }); // end Chart

    }(Highcharts));
    (function(Highcharts) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var Point,
            H = Highcharts,

            each = H.each,
            extend = H.extend,
            erase = H.erase,
            fireEvent = H.fireEvent,
            format = H.format,
            isArray = H.isArray,
            isNumber = H.isNumber,
            pick = H.pick,
            removeEvent = H.removeEvent;

        /**
         * The Point object. The point objects are generated from the `series.data` 
         * configuration objects or raw numbers. They can be accessed from the
         * `Series.points` array. Other ways to instaniate points are through {@link
         * Highcharts.Series#addPoint} or {@link Highcharts.Series#setData}.
         *
         * @class
         */

        Highcharts.Point = Point = function() {};
        Highcharts.Point.prototype = {

            /**
             * Initialize the point. Called internally based on the `series.data`
             * option.
             * @param  {Series} series
             *         The series object containing this point.
             * @param  {Number|Array|Object} options
             *         The data in either number, array or object format.
             * @param  {Number} x Optionally, the X value of the point.
             * @return {Point} The Point instance.
             */
            init: function(series, options, x) {

                var point = this,
                    colors,
                    colorCount = series.chart.options.chart.colorCount,
                    colorIndex;

                /**
                 * The series object associated with the point.
                 *
                 * @name series
                 * @memberof Highcharts.Point
                 * @type Highcharts.Series
                 */
                point.series = series;


                /**
                 * The point's current color.
                 * @name color
                 * @memberof Highcharts.Point
                 * @type {Color}
                 */
                point.color = series.color; // #3445

                point.applyOptions(options, x);

                if (series.options.colorByPoint) {

                    colors = series.options.colors || series.chart.options.colors;
                    point.color = point.color || colors[series.colorCounter];
                    colorCount = colors.length;

                    colorIndex = series.colorCounter;
                    series.colorCounter++;
                    // loop back to zero
                    if (series.colorCounter === colorCount) {
                        series.colorCounter = 0;
                    }
                } else {
                    colorIndex = series.colorIndex;
                }

                /**
                 * The point's current color index, used in styled mode instead of 
                 * `color`. The color index is inserted in class names used for styling.
                 * @name colorIndex
                 * @memberof Highcharts.Point
                 * @type {Number}
                 */
                point.colorIndex = pick(point.colorIndex, colorIndex);

                series.chart.pointCount++;
                return point;
            },
            /**
             * Apply the options containing the x and y data and possible some extra
             * properties. Called on point init or from point.update.
             *
             * @private
             * @param {Object} options The point options as defined in series.data.
             * @param {Number} x Optionally, the X value.
             * @returns {Object} The Point instance.
             */
            applyOptions: function(options, x) {
                var point = this,
                    series = point.series,
                    pointValKey = series.options.pointValKey || series.pointValKey;

                options = Point.prototype.optionsToObject.call(this, options);

                // copy options directly to point
                extend(point, options);
                point.options = point.options ? extend(point.options, options) : options;

                // Since options are copied into the Point instance, some accidental options must be shielded (#5681)
                if (options.group) {
                    delete point.group;
                }

                // For higher dimension series types. For instance, for ranges, point.y is mapped to point.low.
                if (pointValKey) {
                    point.y = point[pointValKey];
                }
                point.isNull = pick(
                    point.isValid && !point.isValid(),
                    point.x === null || !isNumber(point.y, true)
                ); // #3571, check for NaN

                // The point is initially selected by options (#5777)
                if (point.selected) {
                    point.state = 'select';
                }

                // If no x is set by now, get auto incremented value. All points must have an
                // x value, however the y value can be null to create a gap in the series
                if ('name' in point && x === undefined && series.xAxis && series.xAxis.hasNames) {
                    point.x = series.xAxis.nameToX(point);
                }
                if (point.x === undefined && series) {
                    if (x === undefined) {
                        point.x = series.autoIncrement(point);
                    } else {
                        point.x = x;
                    }
                }

                return point;
            },

            /**
             * Transform number or array configs into objects. Used internally to unify
             * the different configuration formats for points. For example, a simple
             * number `10` in a line series will be transformed to `{ y: 10 }`, and an
             * array config like `[1, 10]` in a scatter series will be transformed to
             * `{ x: 1, y: 10 }`.
             *
             * @param  {Number|Array|Object} options
             *         The input options
             * @return {Object} Transformed options.
             */
            optionsToObject: function(options) {
                var ret = {},
                    series = this.series,
                    keys = series.options.keys,
                    pointArrayMap = keys || series.pointArrayMap || ['y'],
                    valueCount = pointArrayMap.length,
                    firstItemType,
                    i = 0,
                    j = 0;

                if (isNumber(options) || options === null) {
                    ret[pointArrayMap[0]] = options;

                } else if (isArray(options)) {
                    // with leading x value
                    if (!keys && options.length > valueCount) {
                        firstItemType = typeof options[0];
                        if (firstItemType === 'string') {
                            ret.name = options[0];
                        } else if (firstItemType === 'number') {
                            ret.x = options[0];
                        }
                        i++;
                    }
                    while (j < valueCount) {
                        if (!keys || options[i] !== undefined) { // Skip undefined positions for keys
                            ret[pointArrayMap[j]] = options[i];
                        }
                        i++;
                        j++;
                    }
                } else if (typeof options === 'object') {
                    ret = options;

                    // This is the fastest way to detect if there are individual point dataLabels that need
                    // to be considered in drawDataLabels. These can only occur in object configs.
                    if (options.dataLabels) {
                        series._hasPointLabels = true;
                    }

                    // Same approach as above for markers
                    if (options.marker) {
                        series._hasPointMarkers = true;
                    }
                }
                return ret;
            },

            /**
             * Get the CSS class names for individual points. Used internally where the
             * returned value is set on every point.
             * 
             * @returns {String} The class names.
             */
            getClassName: function() {
                return 'highcharts-point' +
                    (this.selected ? ' highcharts-point-select' : '') +
                    (this.negative ? ' highcharts-negative' : '') +
                    (this.isNull ? ' highcharts-null-point' : '') +
                    (this.colorIndex !== undefined ? ' highcharts-color-' +
                        this.colorIndex : '') +
                    (this.options.className ? ' ' + this.options.className : '') +
                    (this.zone && this.zone.className ? ' ' +
                        this.zone.className.replace('highcharts-negative', '') : '');
            },

            /**
             * In a series with `zones`, return the zone that the point belongs to.
             *
             * @return {Object}
             *         The zone item.
             */
            getZone: function() {
                var series = this.series,
                    zones = series.zones,
                    zoneAxis = series.zoneAxis || 'y',
                    i = 0,
                    zone;

                zone = zones[i];
                while (this[zoneAxis] >= zone.value) {
                    zone = zones[++i];
                }

                if (zone && zone.color && !this.options.color) {
                    this.color = zone.color;
                }

                return zone;
            },

            /**
             * Destroy a point to clear memory. Its reference still stays in
             * `series.data`.
             *
             * @private
             */
            destroy: function() {
                var point = this,
                    series = point.series,
                    chart = series.chart,
                    hoverPoints = chart.hoverPoints,
                    prop;

                chart.pointCount--;

                if (hoverPoints) {
                    point.setState();
                    erase(hoverPoints, point);
                    if (!hoverPoints.length) {
                        chart.hoverPoints = null;
                    }

                }
                if (point === chart.hoverPoint) {
                    point.onMouseOut();
                }

                // remove all events
                if (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive
                    removeEvent(point);
                    point.destroyElements();
                }

                if (point.legendItem) { // pies have legend items
                    chart.legend.destroyItem(point);
                }

                for (prop in point) {
                    point[prop] = null;
                }


            },

            /**
             * Destroy SVG elements associated with the point.
             *
             * @private
             */
            destroyElements: function() {
                var point = this,
                    props = ['graphic', 'dataLabel', 'dataLabelUpper', 'connector', 'shadowGroup'],
                    prop,
                    i = 6;
                while (i--) {
                    prop = props[i];
                    if (point[prop]) {
                        point[prop] = point[prop].destroy();
                    }
                }
            },

            /**
             * Return the configuration hash needed for the data label and tooltip
             * formatters.
             *
             * @returns {Object}
             *          Abstract object used in formatters and formats.
             */
            getLabelConfig: function() {
                return {
                    x: this.category,
                    y: this.y,
                    color: this.color,
                    colorIndex: this.colorIndex,
                    key: this.name || this.category,
                    series: this.series,
                    point: this,
                    percentage: this.percentage,
                    total: this.total || this.stackTotal
                };
            },

            /**
             * Extendable method for formatting each point's tooltip line.
             *
             * @param  {String} pointFormat
             *         The point format.
             * @return {String}
             *         A string to be concatenated in to the common tooltip text.
             */
            tooltipFormatter: function(pointFormat) {

                // Insert options for valueDecimals, valuePrefix, and valueSuffix
                var series = this.series,
                    seriesTooltipOptions = series.tooltipOptions,
                    valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''),
                    valuePrefix = seriesTooltipOptions.valuePrefix || '',
                    valueSuffix = seriesTooltipOptions.valueSuffix || '';

                // Loop over the point array map and replace unformatted values with sprintf formatting markup
                each(series.pointArrayMap || ['y'], function(key) {
                    key = '{point.' + key; // without the closing bracket
                    if (valuePrefix || valueSuffix) {
                        pointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix);
                    }
                    pointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}');
                });

                return format(pointFormat, {
                    point: this,
                    series: this.series
                });
            },

            /**
             * Fire an event on the Point object.
             *
             * @private
             * @param {String} eventType
             * @param {Object} eventArgs Additional event arguments
             * @param {Function} defaultFunction Default event handler
             */
            firePointEvent: function(eventType, eventArgs, defaultFunction) {
                var point = this,
                    series = this.series,
                    seriesOptions = series.options;

                // load event handlers on demand to save time on mouseover/out
                if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {
                    this.importEvents();
                }

                // add default handler if in selection mode
                if (eventType === 'click' && seriesOptions.allowPointSelect) {
                    defaultFunction = function(event) {
                        // Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera
                        if (point.select) { // Could be destroyed by prior event handlers (#2911)
                            point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
                        }
                    };
                }

                fireEvent(this, eventType, eventArgs, defaultFunction);
            },

            /**
             * For certain series types, like pie charts, where individual points can
             * be shown or hidden. 
             *
             * @name visible
             * @memberOf Highcharts.Point
             * @type {Boolean}
             */
            visible: true
        };

        /**
         * For categorized axes this property holds the category name for the 
         * point. For other axes it holds the X value.
         *
         * @name category
         * @memberOf Highcharts.Point
         * @type {String|Number}
         */

        /**
         * The name of the point. The name can be given as the first position of the 
         * point configuration array, or as a `name` property in the configuration:
         *
         * @example
         * // Array config
         * data: [
         *     ['John', 1],
         *     ['Jane', 2]
         * ]
         *
         * // Object config
         * data: [{
         * 	   name: 'John',
         * 	   y: 1
         * }, {
         *     name: 'Jane',
         *     y: 2
         * }]
         *
         * @name name
         * @memberOf Highcharts.Point
         * @type {String}
         */


        /**
         * The percentage for points in a stacked series or pies.
         *
         * @name percentage
         * @memberOf Highcharts.Point
         * @type {Number}
         */

        /**
         * The total of values in either a stack for stacked series, or a pie in a pie
         * series.
         *
         * @name total
         * @memberOf Highcharts.Point
         * @type {Number}
         */

        /**
         * The x value of the point.
         *
         * @name x
         * @memberOf Highcharts.Point
         * @type {Number}
         */

        /**
         * The y value of the point.
         *
         * @name y
         * @memberOf Highcharts.Point
         * @type {Number}
         */

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var addEvent = H.addEvent,
            animObject = H.animObject,
            arrayMax = H.arrayMax,
            arrayMin = H.arrayMin,
            correctFloat = H.correctFloat,
            Date = H.Date,
            defaultOptions = H.defaultOptions,
            defaultPlotOptions = H.defaultPlotOptions,
            defined = H.defined,
            each = H.each,
            erase = H.erase,
            extend = H.extend,
            fireEvent = H.fireEvent,
            grep = H.grep,
            isArray = H.isArray,
            isNumber = H.isNumber,
            isString = H.isString,
            LegendSymbolMixin = H.LegendSymbolMixin, // @todo add as a requirement
            merge = H.merge,
            objectEach = H.objectEach,
            pick = H.pick,
            Point = H.Point, // @todo  add as a requirement
            removeEvent = H.removeEvent,
            splat = H.splat,
            SVGElement = H.SVGElement,
            syncTimeout = H.syncTimeout,
            win = H.win;

        /**
         * This is the base series prototype that all other series types inherit from.
         * A new series is initialized either through the
         * {@link https://api.highcharts.com/highcharts/series|series} option structure,
         * or after the chart is initialized, through
         * {@link Highcharts.Chart#addSeries}.
         *
         * The object can be accessed in a number of ways. All series and point event
         * handlers give a reference to the `series` object. The chart object has a
         * {@link Highcharts.Chart.series|series} property that is a collection of all
         * the chart's series. The point objects and axis objects also have the same
         * reference.
         *
         * Another way to reference the series programmatically is by `id`. Add an id
         * in the series configuration options, and get the series object by {@link
         * Highcharts.Chart#get}.
         *
         * Configuration options for the series are given in three levels. Options for
         * all series in a chart are given in the
         * {@link https://api.highcharts.com/highcharts/plotOptions.series|
         * plotOptions.series} object. Then options for all series of a specific type
         * are given in the plotOptions of that type, for example `plotOptions.line`.
         * Next, options for one single series are given in the series array, or as
         * arguements to `chart.addSeries`.
         *
         * The data in the series is stored in various arrays.
         *
         * - First, `series.options.data` contains all the original config options for
         * each point whether added by options or methods like `series.addPoint`.
         * - Next, `series.data` contains those values converted to points, but in case
         * the series data length exceeds the `cropThreshold`, or if the data is
         * grouped, `series.data` doesn't contain all the points. It only contains the
         * points that have been created on demand.
         * - Then there's `series.points` that contains all currently visible point
         * objects. In case of cropping, the cropped-away points are not part of this
         * array. The `series.points` array starts at `series.cropStart` compared to
         * `series.data` and `series.options.data`. If however the series data is
         * grouped, these can't be correlated one to one.
         * - `series.xData` and `series.processedXData` contain clean x values,
         * equivalent to `series.data` and `series.points`.
         * - `series.yData` and `series.processedYData` contain clean y values,
         * equivalent to `series.data` and `series.points`.
         *
         * @class Highcharts.Series
         * @param  {Highcharts.Chart} chart
         *         The chart instance.
         * @param  {Options.plotOptions.series} options
         *         The series options.
         *
         */

        /**
         * General options for all series types.
         * @optionparent plotOptions.series
         */
        H.Series = H.seriesType('line', null, { // base series options

            /**
             * The SVG value used for the `stroke-linecap` and `stroke-linejoin`
             * of a line graph. Round means that lines are rounded in the ends and
             * bends.
             * 
             * @validvalue ["round", "butt", "square"]
             * @type {String}
             * @default round
             * @since 3.0.7
             * @apioption plotOptions.line.linecap
             */

            /**
             * Pixel with of the graph line.
             * 
             * @type {Number}
             * @see In styled mode, the line stroke-width can be set with the
             * `.highcharts-graph` class name.
             * @sample {highcharts} highcharts/plotoptions/series-linewidth-general/
             *         On all series
             * @sample {highcharts} highcharts/plotoptions/series-linewidth-specific/
             *         On one single series
             * @default 2
             * @product highcharts highstock
             */
            lineWidth: 2,


            /**
             * For some series, there is a limit that shuts down initial animation
             * by default when the total number of points in the chart is too high.
             * For example, for a column chart and its derivatives, animation doesn't
             * run if there is more than 250 points totally. To disable this cap, set
             * `animationLimit` to `Infinity`.
             * 
             * @type {Number}
             * @apioption plotOptions.series.animationLimit
             */

            /**
             * Allow this series' points to be selected by clicking on the graphic 
             * (columns, point markers, pie slices, map areas etc).
             *
             * @see [Chart#getSelectedPoints]
             *      (../class-reference/Highcharts.Chart#getSelectedPoints).
             * 
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-line/
             *         Line
             * @sample {highcharts}
             *         highcharts/plotoptions/series-allowpointselect-column/
             *         Column
             * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-pie/
             *         Pie
             * @sample {highmaps} maps/plotoptions/series-allowpointselect/
             *         Map area
             * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/
             *         Map bubble
             * @default false
             * @since 1.2.0
             */
            allowPointSelect: false,



            /**
             * If true, a checkbox is displayed next to the legend item to allow
             * selecting the series. The state of the checkbox is determined by
             * the `selected` option.
             *
             * @productdesc {highmaps}
             * Note that if a `colorAxis` is defined, the color axis is represented in
             * the legend, not the series.
             * 
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/series-showcheckbox-true/
             *         Show select box
             * @default false
             * @since 1.2.0
             */
            showCheckbox: false,



            /**
             * Enable or disable the initial animation when a series is displayed.
             * The animation can also be set as a configuration object. Please
             * note that this option only applies to the initial animation of the
             * series itself. For other animations, see [chart.animation](#chart.
             * animation) and the animation parameter under the API methods. The
             * following properties are supported:
             * 
             * <dl>
             * 
             * <dt>duration</dt>
             * 
             * <dd>The duration of the animation in milliseconds.</dd>
             * 
             * <dt>easing</dt>
             * 
             * <dd>A string reference to an easing function set on the `Math` object.
             * See the _Custom easing function_ demo below.</dd>
             * 
             * </dl>
             * 
             * Due to poor performance, animation is disabled in old IE browsers
             * for several chart types.
             * 
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/series-animation-disabled/
             *         Animation disabled
             * @sample {highcharts} highcharts/plotoptions/series-animation-slower/
             *         Slower animation
             * @sample {highcharts} highcharts/plotoptions/series-animation-easing/
             *         Custom easing function
             * @sample {highstock} stock/plotoptions/animation-slower/
             *         Slower animation
             * @sample {highstock} stock/plotoptions/animation-easing/
             *         Custom easing function
             * @sample {highmaps} maps/plotoptions/series-animation-true/
             *         Animation enabled on map series
             * @sample {highmaps} maps/plotoptions/mapbubble-animation-false/
             *         Disabled on mapbubble series
             * @default {highcharts} true
             * @default {highstock} true
             * @default {highmaps} false
             */
            animation: {
                duration: 1000
            },

            /**
             * A class name to apply to the series' graphical elements.
             * 
             * @type {String}
             * @since 5.0.0
             * @apioption plotOptions.series.className
             */

            /**
             * The main color of the series. In line type series it applies to the
             * line and the point markers unless otherwise specified. In bar type
             * series it applies to the bars unless a color is specified per point.
             * The default value is pulled from the `options.colors` array.
             * 
             * In styled mode, the color can be defined by the
             * [colorIndex](#plotOptions.series.colorIndex) option. Also, the series
             * color can be set with the `.highcharts-series`, `.highcharts-color-{n}`,
             * `.highcharts-{type}-series` or `.highcharts-series-{n}` class, or
             * individual classes given by the `className` option.
             *
             * @productdesc {highmaps}
             * In maps, the series color is rarely used, as most choropleth maps use the
             * color to denote the value of each point. The series color can however be
             * used in a map with multiple series holding categorized data.
             * 
             * @type {Color}
             * @sample {highcharts} highcharts/plotoptions/series-color-general/
             *         General plot option
             * @sample {highcharts} highcharts/plotoptions/series-color-specific/
             *         One specific series
             * @sample {highcharts} highcharts/plotoptions/series-color-area/
             *         Area color
             * @sample {highmaps} maps/demo/category-map/
             *         Category map by multiple series
             * @apioption plotOptions.series.color
             */

            /**
             * Styled mode only. A specific color index to use for the series, so its
             * graphic representations are given the class name `highcharts-color-
             * {n}`.
             * 
             * @type {Number}
             * @since 5.0.0
             * @apioption plotOptions.series.colorIndex
             */


            /**
             * Whether to connect a graph line across null points, or render a gap
             * between the two points on either side of the null.
             * 
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/series-connectnulls-false/
             *         False by default
             * @sample {highcharts} highcharts/plotoptions/series-connectnulls-true/
             *         True
             * @product highcharts highstock
             * @apioption plotOptions.series.connectNulls
             */


            /**
             * You can set the cursor to "pointer" if you have click events attached
             * to the series, to signal to the user that the points and lines can
             * be clicked.
             * 
             * @validvalue [null, "default", "none", "help", "pointer", "crosshair"]
             * @type {String}
             * @see In styled mode, the series cursor can be set with the same classes
             * as listed under [series.color](#plotOptions.series.color).
             * @sample {highcharts} highcharts/plotoptions/series-cursor-line/
             *         On line graph
             * @sample {highcharts} highcharts/plotoptions/series-cursor-column/
             *         On columns
             * @sample {highcharts} highcharts/plotoptions/series-cursor-scatter/
             *         On scatter markers
             * @sample {highstock} stock/plotoptions/cursor/
             *         Pointer on a line graph
             * @sample {highmaps} maps/plotoptions/series-allowpointselect/
             *         Map area
             * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/
             *         Map bubble
             * @apioption plotOptions.series.cursor
             */


            /**
             * A name for the dash style to use for the graph, or for some series types
             * the outline of each shape. The value for the `dashStyle` include:
             * 
             * *   Solid
             * *   ShortDash
             * *   ShortDot
             * *   ShortDashDot
             * *   ShortDashDotDot
             * *   Dot
             * *   Dash
             * *   LongDash
             * *   DashDot
             * *   LongDashDot
             * *   LongDashDotDot
             * 
             * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
             *             "ShortDashDotDot", "Dot", "Dash" ,"LongDash", "DashDot",
             *             "LongDashDot", "LongDashDotDot"]
             * @type {String}
             * @see In styled mode, the [stroke dash-array](http://jsfiddle.net/gh/get/
             * library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/
             * series-dashstyle/) can be set with the same classes as listed under
             * [series.color](#plotOptions.series.color).
             * 
             * @sample {highcharts} highcharts/plotoptions/series-dashstyle-all/
             *         Possible values demonstrated
             * @sample {highcharts} highcharts/plotoptions/series-dashstyle/
             *         Chart suitable for printing in black and white
             * @sample {highstock} highcharts/plotoptions/series-dashstyle-all/
             *         Possible values demonstrated
             * @sample {highmaps} highcharts/plotoptions/series-dashstyle-all/
             *         Possible values demonstrated
             * @sample {highmaps} maps/plotoptions/series-dashstyle/
             *         Dotted borders on a map
             * @default Solid
             * @since 2.1
             * @apioption plotOptions.series.dashStyle
             */

            /**
             * Requires the Accessibility module.
             * 
             * A description of the series to add to the screen reader information
             * about the series.
             * 
             * @type {String}
             * @default undefined
             * @since 5.0.0
             * @apioption plotOptions.series.description
             */





            /**
             * Enable or disable the mouse tracking for a specific series. This
             * includes point tooltips and click events on graphs and points. For
             * large datasets it improves performance.
             * 
             * @type {Boolean}
             * @sample {highcharts}
             *         highcharts/plotoptions/series-enablemousetracking-false/
             *         No mouse tracking
             * @sample {highmaps}
             *         maps/plotoptions/series-enablemousetracking-false/
             *         No mouse tracking
             * @default true
             * @apioption plotOptions.series.enableMouseTracking
             */

            /**
             * By default, series are exposed to screen readers as regions. By enabling
             * this option, the series element itself will be exposed in the same
             * way as the data points. This is useful if the series is not used
             * as a grouping entity in the chart, but you still want to attach a
             * description to the series.
             * 
             * Requires the Accessibility module.
             * 
             * @type {Boolean}
             * @sample highcharts/accessibility/art-grants/
             *         Accessible data visualization
             * @default undefined
             * @since 5.0.12
             * @apioption plotOptions.series.exposeElementToA11y
             */

            /**
             * Whether to use the Y extremes of the total chart width or only the
             * zoomed area when zooming in on parts of the X axis. By default, the
             * Y axis adjusts to the min and max of the visible data. Cartesian
             * series only.
             * 
             * @type {Boolean}
             * @default false
             * @since 4.1.6
             * @product highcharts highstock
             * @apioption plotOptions.series.getExtremesFromAll
             */

            /**
             * An id for the series. This can be used after render time to get a
             * pointer to the series object through `chart.get()`.
             * 
             * @type {String}
             * @sample {highcharts} highcharts/plotoptions/series-id/ Get series by id
             * @since 1.2.0
             * @apioption series.id
             */

            /**
             * The index of the series in the chart, affecting the internal index
             * in the `chart.series` array, the visible Z index as well as the order
             * in the legend.
             * 
             * @type {Number}
             * @default undefined
             * @since 2.3.0
             * @apioption series.index
             */

            /**
             * An array specifying which option maps to which key in the data point
             * array. This makes it convenient to work with unstructured data arrays
             * from different sources.
             * 
             * @type {Array<String>}
             * @see [series.data](#series.line.data)
             * @sample {highcharts|highstock} highcharts/series/data-keys/
             *         An extended data array with keys
             * @since 4.1.6
             * @product highcharts highstock
             * @apioption plotOptions.series.keys
             */

            /**
             * The sequential index of the series in the legend.
             * 
             * @sample {highcharts|highstock} highcharts/series/legendindex/
             *         Legend in opposite order
             * @type {Number}
             * @see [legend.reversed](#legend.reversed), [yAxis.reversedStacks](#yAxis.
             * reversedStacks)
             * @apioption series.legendIndex
             */

            /**
             * The line cap used for line ends and line joins on the graph.
             * 
             * @validvalue ["round", "square"]
             * @type {String}
             * @default round
             * @product highcharts highstock
             * @apioption plotOptions.series.linecap
             */

            /**
             * The [id](#series.id) of another series to link to. Additionally,
             * the value can be ":previous" to link to the previous series. When
             * two series are linked, only the first one appears in the legend.
             * Toggling the visibility of this also toggles the linked series.
             * 
             * @type {String}
             * @sample {highcharts} highcharts/demo/arearange-line/ Linked series
             * @sample {highstock} highcharts/demo/arearange-line/ Linked series
             * @since 3.0
             * @product highcharts highstock
             * @apioption plotOptions.series.linkedTo
             */

            /**
             * The name of the series as shown in the legend, tooltip etc.
             * 
             * @type {String}
             * @sample {highcharts} highcharts/series/name/ Series name
             * @sample {highmaps} maps/demo/category-map/ Series name
             * @apioption series.name
             */

            /**
             * The color for the parts of the graph or points that are below the
             * [threshold](#plotOptions.series.threshold).
             * 
             * @type {Color}
             * @see In styled mode, a negative color is applied by setting this
             * option to `true` combined with the `.highcharts-negative` class name.
             * 
             * @sample {highcharts} highcharts/plotoptions/series-negative-color/
             *         Spline, area and column
             * @sample {highcharts} highcharts/plotoptions/arearange-negativecolor/
             *         Arearange
             * @sample {highcharts} highcharts/css/series-negative-color/
             *         Styled mode
             * @sample {highstock} highcharts/plotoptions/series-negative-color/
             *         Spline, area and column
             * @sample {highstock} highcharts/plotoptions/arearange-negativecolor/
             *         Arearange
             * @sample {highmaps} highcharts/plotoptions/series-negative-color/
             *         Spline, area and column
             * @sample {highmaps} highcharts/plotoptions/arearange-negativecolor/
             *         Arearange
             * @default null
             * @since 3.0
             * @apioption plotOptions.series.negativeColor
             */

            /**
             * Same as [accessibility.pointDescriptionFormatter](#accessibility.
             * pointDescriptionFormatter), but for an individual series. Overrides
             * the chart wide configuration.
             * 
             * @type {Function}
             * @since 5.0.12
             * @apioption plotOptions.series.pointDescriptionFormatter
             */

            /**
             * If no x values are given for the points in a series, `pointInterval`
             * defines the interval of the x values. For example, if a series contains
             * one value every decade starting from year 0, set `pointInterval` to
             * `10`. In true `datetime` axes, the `pointInterval` is set in
             * milliseconds.
             * 
             * It can be also be combined with `pointIntervalUnit` to draw irregular
             * time intervals.
             * 
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/
             *         Datetime X axis
             * @sample {highstock} stock/plotoptions/pointinterval-pointstart/
             *         Using pointStart and pointInterval
             * @default 1
             * @product highcharts highstock
             * @apioption plotOptions.series.pointInterval
             */

            /**
             * On datetime series, this allows for setting the
             * [pointInterval](#plotOptions.series.pointInterval) to irregular time 
             * units, `day`, `month` and `year`. A day is usually the same as 24 hours,
             * but `pointIntervalUnit` also takes the DST crossover into consideration
             * when dealing with local time. Combine this option with `pointInterval`
             * to draw weeks, quarters, 6 months, 10 years etc.
             * 
             * @validvalue [null, "day", "month", "year"]
             * @type {String}
             * @sample {highcharts} highcharts/plotoptions/series-pointintervalunit/
             *         One point a month
             * @sample {highstock} highcharts/plotoptions/series-pointintervalunit/
             *         One point a month
             * @since 4.1.0
             * @product highcharts highstock
             * @apioption plotOptions.series.pointIntervalUnit
             */

            /**
             * Possible values: `null`, `"on"`, `"between"`.
             * 
             * In a column chart, when pointPlacement is `"on"`, the point will
             * not create any padding of the X axis. In a polar column chart this
             * means that the first column points directly north. If the pointPlacement
             * is `"between"`, the columns will be laid out between ticks. This
             * is useful for example for visualising an amount between two points
             * in time or in a certain sector of a polar chart.
             * 
             * Since Highcharts 3.0.2, the point placement can also be numeric,
             * where 0 is on the axis value, -0.5 is between this value and the
             * previous, and 0.5 is between this value and the next. Unlike the
             * textual options, numeric point placement options won't affect axis
             * padding.
             * 
             * Note that pointPlacement needs a [pointRange](#plotOptions.series.
             * pointRange) to work. For column series this is computed, but for
             * line-type series it needs to be set.
             * 
             * Defaults to `null` in cartesian charts, `"between"` in polar charts.
             * 
             * @validvalue [null, "on", "between"]
             * @type {String|Number}
             * @see [xAxis.tickmarkPlacement](#xAxis.tickmarkPlacement)
             * @sample {highcharts|highstock}
             *         highcharts/plotoptions/series-pointplacement-between/
             *         Between in a column chart
             * @sample {highcharts|highstock}
             *         highcharts/plotoptions/series-pointplacement-numeric/
             *         Numeric placement for custom layout
             * @default null
             * @since 2.3.0
             * @product highcharts highstock
             * @apioption plotOptions.series.pointPlacement
             */

            /**
             * If no x values are given for the points in a series, pointStart defines
             * on what value to start. For example, if a series contains one yearly
             * value starting from 1945, set pointStart to 1945.
             * 
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/series-pointstart-linear/
             *         Linear
             * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/
             *         Datetime
             * @sample {highstock} stock/plotoptions/pointinterval-pointstart/
             *         Using pointStart and pointInterval
             * @default 0
             * @product highcharts highstock
             * @apioption plotOptions.series.pointStart
             */

            /**
             * Whether to select the series initially. If `showCheckbox` is true,
             * the checkbox next to the series name in the legend will be checked for a
             * selected series.
             * 
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/series-selected/
             *         One out of two series selected
             * @default false
             * @since 1.2.0
             * @apioption plotOptions.series.selected
             */

            /**
             * Whether to apply a drop shadow to the graph line. Since 2.3 the shadow
             * can be an object configuration containing `color`, `offsetX`, `offsetY`,
             *  `opacity` and `width`.
             * 
             * @type {Boolean|Object}
             * @sample {highcharts} highcharts/plotoptions/series-shadow/ Shadow enabled
             * @default false
             * @apioption plotOptions.series.shadow
             */

            /**
             * Whether to display this particular series or series type in the legend.
             * The default value is `true` for standalone series, `false` for linked
             * series.
             * 
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/series-showinlegend/
             *         One series in the legend, one hidden
             * @default true
             * @apioption plotOptions.series.showInLegend
             */

            /**
             * If set to `True`, the accessibility module will skip past the points
             * in this series for keyboard navigation.
             * 
             * @type {Boolean}
             * @since 5.0.12
             * @apioption plotOptions.series.skipKeyboardNavigation
             */

            /**
             * This option allows grouping series in a stacked chart. The stack
             * option can be a string or a number or anything else, as long as the
             * grouped series' stack options match each other.
             * 
             * @type {String}
             * @sample {highcharts} highcharts/series/stack/ Stacked and grouped columns
             * @default null
             * @since 2.1
             * @product highcharts highstock
             * @apioption series.stack
             */

            /**
             * Whether to stack the values of each series on top of each other.
             * Possible values are `null` to disable, `"normal"` to stack by value or
             * `"percent"`. When stacking is enabled, data must be sorted in ascending
             * X order. A special stacking option is with the streamgraph series type,
             * where the stacking option is set to `"stream"`.
             * 
             * @validvalue [null, "normal", "percent"]
             * @type {String}
             * @see [yAxis.reversedStacks](#yAxis.reversedStacks)
             * @sample {highcharts} highcharts/plotoptions/series-stacking-line/
             *         Line
             * @sample {highcharts} highcharts/plotoptions/series-stacking-column/
             *         Column
             * @sample {highcharts} highcharts/plotoptions/series-stacking-bar/
             *         Bar
             * @sample {highcharts} highcharts/plotoptions/series-stacking-area/
             *         Area
             * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-line/
             *         Line
             * @sample {highcharts}
             *         highcharts/plotoptions/series-stacking-percent-column/
             *         Column
             * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-bar/
             *         Bar
             * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-area/
             *         Area
             * @sample {highstock} stock/plotoptions/stacking/
             *         Area
             * @default null
             * @product highcharts highstock
             * @apioption plotOptions.series.stacking
             */

            /**
             * Whether to apply steps to the line. Possible values are `left`, `center`
             * and `right`.
             * 
             * @validvalue [null, "left", "center", "right"]
             * @type {String}
             * @sample {highcharts} highcharts/plotoptions/line-step/
             *         Different step line options
             * @sample {highcharts} highcharts/plotoptions/area-step/
             *         Stepped, stacked area
             * @sample {highstock} stock/plotoptions/line-step/
             *         Step line
             * @default {highcharts} null
             * @default {highstock} false
             * @since 1.2.5
             * @product highcharts highstock
             * @apioption plotOptions.series.step
             */

            /**
             * The threshold, also called zero level or base level. For line type
             * series this is only used in conjunction with
             * [negativeColor](#plotOptions.series.negativeColor).
             * 
             * @type {Number}
             * @see [softThreshold](#plotOptions.series.softThreshold).
             * @default 0
             * @since 3.0
             * @product highcharts highstock
             * @apioption plotOptions.series.threshold
             */

            /**
             * The type of series, for example `line` or `column`.
             * 
             * @validvalue [null, "line", "spline", "column", "area", "areaspline",
             *       "pie", "arearange", "areasplinerange", "boxplot", "bubble",
             *       "columnrange", "errorbar", "funnel", "gauge", "scatter",
             *       "waterfall"]
             * @type {String}
             * @sample {highcharts} highcharts/series/type/
             *         Line and column in the same chart
             * @sample {highmaps} maps/demo/mapline-mappoint/
             *         Multiple types in the same map
             * @apioption series.type
             */

            /**
             * Set the initial visibility of the series.
             * 
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/series-visible/
             *         Two series, one hidden and one visible
             * @sample {highstock} stock/plotoptions/series-visibility/
             *         Hidden series
             * @default true
             * @apioption plotOptions.series.visible
             */

            /**
             * When using dual or multiple x axes, this number defines which xAxis
             * the particular series is connected to. It refers to either the [axis
             * id](#xAxis.id) or the index of the axis in the xAxis array, with
             * 0 being the first.
             * 
             * @type {Number|String}
             * @default 0
             * @product highcharts highstock
             * @apioption series.xAxis
             */

            /**
             * When using dual or multiple y axes, this number defines which yAxis
             * the particular series is connected to. It refers to either the [axis
             * id](#yAxis.id) or the index of the axis in the yAxis array, with
             * 0 being the first.
             * 
             * @type {Number|String}
             * @sample {highcharts} highcharts/series/yaxis/
             *         Apply the column series to the secondary Y axis
             * @default 0
             * @product highcharts highstock
             * @apioption series.yAxis
             */

            /**
             * Defines the Axis on which the zones are applied.
             * 
             * @type {String}
             * @see [zones](#plotOptions.series.zones)
             * @sample {highcharts} highcharts/series/color-zones-zoneaxis-x/
             *         Zones on the X-Axis
             * @sample {highstock} highcharts/series/color-zones-zoneaxis-x/
             *         Zones on the X-Axis
             * @default y
             * @since 4.1.0
             * @product highcharts highstock
             * @apioption plotOptions.series.zoneAxis
             */

            /**
             * Define the visual z index of the series.
             * 
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/series-zindex-default/
             *         With no z index, the series defined last are on top
             * @sample {highcharts} highcharts/plotoptions/series-zindex/
             *         With a z index, the series with the highest z index is on top
             * @sample {highstock} highcharts/plotoptions/series-zindex-default/
             *         With no z index, the series defined last are on top
             * @sample {highstock} highcharts/plotoptions/series-zindex/
             *         With a z index, the series with the highest z index is on top
             * @product highcharts highstock
             * @apioption series.zIndex
             */

            /**
             * General event handlers for the series items. These event hooks can also
             * be attached to the series at run time using the `Highcharts.addEvent`
             * function.
             */
            events: {

                /**
                 * Fires after the series has finished its initial animation, or in
                 * case animation is disabled, immediately as the series is displayed.
                 * 
                 * @type {Function}
                 * @context Series
                 * @sample {highcharts}
                 *         highcharts/plotoptions/series-events-afteranimate/
                 *         Show label after animate
                 * @sample {highstock}
                 *         highcharts/plotoptions/series-events-afteranimate/
                 *         Show label after animate
                 * @since 4.0
                 * @product highcharts highstock
                 * @apioption plotOptions.series.events.afterAnimate
                 */

                /**
                 * Fires when the checkbox next to the series' name in the legend is
                 * clicked. One parameter, `event`, is passed to the function. The state
                 * of the checkbox is found by `event.checked`. The checked item is
                 * found by `event.item`. Return `false` to prevent the default action
                 * which is to toggle the select state of the series.
                 * 
                 * @type {Function}
                 * @context Series
                 * @sample {highcharts}
                 *         highcharts/plotoptions/series-events-checkboxclick/
                 *         Alert checkbox status
                 * @since 1.2.0
                 * @apioption plotOptions.series.events.checkboxClick
                 */

                /**
                 * Fires when the series is clicked. One parameter, `event`, is passed
                 * to the function, containing common event information. Additionally,
                 * `event.point` holds a pointer to the nearest point on the graph.
                 * 
                 * @type {Function}
                 * @context Series
                 * @sample {highcharts} highcharts/plotoptions/series-events-click/
                 *         Alert click info
                 * @sample {highstock} stock/plotoptions/series-events-click/
                 *         Alert click info
                 * @sample {highmaps} maps/plotoptions/series-events-click/
                 *         Display click info in subtitle
                 * @apioption plotOptions.series.events.click
                 */

                /**
                 * Fires when the series is hidden after chart generation time, either
                 * by clicking the legend item or by calling `.hide()`.
                 * 
                 * @type {Function}
                 * @context Series
                 * @sample {highcharts} highcharts/plotoptions/series-events-hide/
                 *         Alert when the series is hidden by clicking the legend item
                 * @since 1.2.0
                 * @apioption plotOptions.series.events.hide
                 */

                /**
                 * Fires when the legend item belonging to the series is clicked. One
                 * parameter, `event`, is passed to the function. The default action
                 * is to toggle the visibility of the series. This can be prevented
                 * by returning `false` or calling `event.preventDefault()`.
                 * 
                 * @type {Function}
                 * @context Series
                 * @sample {highcharts}
                 *         highcharts/plotoptions/series-events-legenditemclick/
                 *         Confirm hiding and showing
                 * @apioption plotOptions.series.events.legendItemClick
                 */

                /**
                 * Fires when the mouse leaves the graph. One parameter, `event`, is
                 * passed to the function, containing common event information. If the
                 * [stickyTracking](#plotOptions.series) option is true, `mouseOut`
                 * doesn't happen before the mouse enters another graph or leaves the
                 * plot area.
                 * 
                 * @type {Function}
                 * @context Series
                 * @sample {highcharts}
                 *         highcharts/plotoptions/series-events-mouseover-sticky/
                 *         With sticky tracking    by default
                 * @sample {highcharts}
                 *         highcharts/plotoptions/series-events-mouseover-no-sticky/
                 *         Without sticky tracking
                 * @apioption plotOptions.series.events.mouseOut
                 */

                /**
                 * Fires when the mouse enters the graph. One parameter, `event`, is
                 * passed to the function, containing common event information.
                 * 
                 * @type {Function}
                 * @context Series
                 * @sample {highcharts}
                 *         highcharts/plotoptions/series-events-mouseover-sticky/
                 *         With sticky tracking by default
                 * @sample {highcharts}
                 *         highcharts/plotoptions/series-events-mouseover-no-sticky/
                 *         Without sticky tracking
                 * @apioption plotOptions.series.events.mouseOver
                 */

                /**
                 * Fires when the series is shown after chart generation time, either
                 * by clicking the legend item or by calling `.show()`.
                 * 
                 * @type {Function}
                 * @context Series
                 * @sample {highcharts} highcharts/plotoptions/series-events-show/
                 *         Alert when the series is shown by clicking the legend item.
                 * @since 1.2.0
                 * @apioption plotOptions.series.events.show
                 */

            },



            /**
             * Options for the point markers of line-like series. Properties like
             * `fillColor`, `lineColor` and `lineWidth` define the visual appearance
             * of the markers. Other series types, like column series, don't have
             * markers, but have visual options on the series level instead.
             * 
             * In styled mode, the markers can be styled with the `.highcharts-point`,
             * `.highcharts-point-hover` and `.highcharts-point-select`
             * class names.
             * 
             * @product highcharts highstock
             */
            marker: {



                /**
                 * The width of the point marker's outline.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
                 *         2px blue marker
                 * @default 0
                 * @product highcharts highstock
                 */
                lineWidth: 0,


                /**
                 * The color of the point marker's outline. When `null`, the series'
                 * or point's color is used.
                 * 
                 * @type {Color}
                 * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
                 *         Inherit from series color (null)
                 * @product highcharts highstock
                 */
                lineColor: '#ffffff',

                /**
                 * The fill color of the point marker. When `null`, the series' or
                 * point's color is used.
                 * 
                 * @type {Color}
                 * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
                 *         White fill
                 * @default null
                 * @product highcharts highstock
                 * @apioption plotOptions.series.marker.fillColor
                 */



                /**
                 * Enable or disable the point marker. If `null`, the markers are hidden
                 * when the data is dense, and shown for more widespread data points.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/plotoptions/series-marker-enabled/
                 *         Disabled markers
                 * @sample {highcharts}
                 *         highcharts/plotoptions/series-marker-enabled-false/
                 *         Disabled in normal state but enabled on hover
                 * @sample {highstock} stock/plotoptions/series-marker/
                 *         Enabled markers
                 * @default {highcharts} null
                 * @default {highstock} false
                 * @product highcharts highstock
                 * @apioption plotOptions.series.marker.enabled
                 */

                /**
                 * Image markers only. Set the image width explicitly. When using this
                 * option, a `width` must also be set.
                 * 
                 * @type {Number}
                 * @sample {highcharts}
                 *         highcharts/plotoptions/series-marker-width-height/
                 *         Fixed width and height
                 * @sample {highstock}
                 *         highcharts/plotoptions/series-marker-width-height/
                 *         Fixed width and height
                 * @default null
                 * @since 4.0.4
                 * @product highcharts highstock
                 * @apioption plotOptions.series.marker.height
                 */

                /**
                 * A predefined shape or symbol for the marker. When null, the symbol
                 * is pulled from options.symbols. Other possible values are "circle",
                 * "square", "diamond", "triangle" and "triangle-down".
                 * 
                 * Additionally, the URL to a graphic can be given on this form:
                 * "url(graphic.png)". Note that for the image to be applied to exported
                 * charts, its URL needs to be accessible by the export server.
                 * 
                 * Custom callbacks for symbol path generation can also be added to
                 * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
                 * used by its method name, as shown in the demo.
                 * 
                 * @validvalue [null, "circle", "square", "diamond", "triangle",
                 *         "triangle-down"]
                 * @type {String}
                 * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/
                 *         Predefined, graphic and custom markers
                 * @sample {highstock} highcharts/plotoptions/series-marker-symbol/
                 *         Predefined, graphic and custom markers
                 * @default null
                 * @product highcharts highstock
                 * @apioption plotOptions.series.marker.symbol
                 */

                /**
                 * The radius of the point marker.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/series-marker-radius/
                 *         Bigger markers
                 * @default 4
                 * @product highcharts highstock
                 */
                radius: 4,

                /**
                 * Image markers only. Set the image width explicitly. When using this
                 * option, a `height` must also be set.
                 * 
                 * @type {Number}
                 * @sample {highcharts}
                 *         highcharts/plotoptions/series-marker-width-height/
                 *         Fixed width and height
                 * @sample {highstock}
                 *         highcharts/plotoptions/series-marker-width-height/
                 *         Fixed width and height
                 * @default null
                 * @since 4.0.4
                 * @product highcharts highstock
                 * @apioption plotOptions.series.marker.width
                 */


                /**
                 * States for a single point marker.
                 * @product highcharts highstock
                 */
                states: {
                    /**
                     * The hover state for a single point marker.
                     * @product highcharts highstock
                     */
                    hover: {

                        /**
                         * Animation when hovering over the marker.
                         * @type {Boolean|Object}
                         */
                        animation: {
                            duration: 50
                        },

                        /**
                         * Enable or disable the point marker.
                         * 
                         * @type {Boolean}
                         * @sample {highcharts}
                         *         highcharts/plotoptions/series-marker-states-hover-enabled/
                         *         Disabled hover state
                         * @default true
                         * @product highcharts highstock
                         */
                        enabled: true,

                        /**
                         * The fill color of the marker in hover state.
                         * 
                         * @type {Color}
                         * @default null
                         * @product highcharts highstock
                         * @apioption plotOptions.series.marker.states.hover.fillColor
                         */

                        /**
                         * The color of the point marker's outline. When `null`, the
                         * series' or point's color is used.
                         * 
                         * @type {Color}
                         * @sample {highcharts}
                         *         highcharts/plotoptions/series-marker-states-hover-linecolor/
                         *         White fill color, black line color
                         * @default #ffffff
                         * @product highcharts highstock
                         * @apioption plotOptions.series.marker.states.hover.lineColor
                         */

                        /**
                         * The width of the point marker's outline.
                         * 
                         * @type {Number}
                         * @sample {highcharts}
                         *         highcharts/plotoptions/series-marker-states-hover-linewidth/
                         *         3px line width
                         * @default 0
                         * @product highcharts highstock
                         * @apioption plotOptions.series.marker.states.hover.lineWidth
                         */

                        /**
                         * The radius of the point marker. In hover state, it defaults to the
                         * normal state's radius + 2 as per the [radiusPlus](#plotOptions.series.
                         * marker.states.hover.radiusPlus) option.
                         * 
                         * @type {Number}
                         * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-radius/ 10px radius
                         * @product highcharts highstock
                         * @apioption plotOptions.series.marker.states.hover.radius
                         */

                        /**
                         * The number of pixels to increase the radius of the hovered point.
                         * 
                         * @type {Number}
                         * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/ 5 pixels greater radius on hover
                         * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/ 5 pixels greater radius on hover
                         * @default 2
                         * @since 4.0.3
                         * @product highcharts highstock
                         */
                        radiusPlus: 2,



                        /**
                         * The additional line width for a hovered point.
                         * 
                         * @type {Number}
                         * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/ 2 pixels wider on hover
                         * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/ 2 pixels wider on hover
                         * @default 1
                         * @since 4.0.3
                         * @product highcharts highstock
                         */
                        lineWidthPlus: 1

                    },




                    /**
                     * The appearance of the point marker when selected. In order to
                     * allow a point to be selected, set the `series.allowPointSelect`
                     * option to true.
                     * 
                     * @product highcharts highstock
                     */
                    select: {

                        /**
                         * Enable or disable visible feedback for selection.
                         * 
                         * @type {Boolean}
                         * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-enabled/
                         *         Disabled select state
                         * @default true
                         * @product highcharts highstock
                         * @apioption plotOptions.series.marker.states.select.enabled
                         */

                        /**
                         * The fill color of the point marker.
                         * 
                         * @type {Color}
                         * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-fillcolor/
                         *         Solid red discs for selected points
                         * @default null
                         * @product highcharts highstock
                         */
                        fillColor: '#cccccc',



                        /**
                         * The color of the point marker's outline. When `null`, the series'
                         * or point's color is used.
                         * 
                         * @type {Color}
                         * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linecolor/
                         *         Red line color for selected points
                         * @default #000000
                         * @product highcharts highstock
                         */
                        lineColor: '#000000',



                        /**
                         * The width of the point marker's outline.
                         * 
                         * @type {Number}
                         * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linewidth/
                         *         3px line width for selected points
                         * @default 0
                         * @product highcharts highstock
                         */
                        lineWidth: 2

                        /**
                         * The radius of the point marker. In hover state, it defaults to the
                         * normal state's radius + 2.
                         * 
                         * @type {Number}
                         * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-radius/
                         *         10px radius for selected points
                         * @product highcharts highstock
                         * @apioption plotOptions.series.marker.states.select.radius
                         */

                    }

                }
            },



            /**
             * Properties for each single point.
             */
            point: {


                /**
                 * Events for each single point.
                 */
                events: {

                    /**
                     * Fires when a point is clicked. One parameter, `event`, is passed
                     * to the function, containing common event information.
                     * 
                     * If the `series.allowPointSelect` option is true, the default action
                     * for the point's click event is to toggle the point's select state.
                     *  Returning `false` cancels this action.
                     * 
                     * @type {Function}
                     * @context Point
                     * @sample {highcharts} highcharts/plotoptions/series-point-events-click/ Click marker to alert values
                     * @sample {highcharts} highcharts/plotoptions/series-point-events-click-column/ Click column
                     * @sample {highcharts} highcharts/plotoptions/series-point-events-click-url/ Go to URL
                     * @sample {highmaps} maps/plotoptions/series-point-events-click/ Click marker to display values
                     * @sample {highmaps} maps/plotoptions/series-point-events-click-url/ Go to URL
                     * @apioption plotOptions.series.point.events.click
                     */

                    /**
                     * Fires when the mouse leaves the area close to the point. One parameter,
                     * `event`, is passed to the function, containing common event information.
                     * 
                     * @type {Function}
                     * @context Point
                     * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Show values in the chart's corner on mouse over
                     * @apioption plotOptions.series.point.events.mouseOut
                     */

                    /**
                     * Fires when the mouse enters the area close to the point. One parameter,
                     * `event`, is passed to the function, containing common event information.
                     * 
                     * @type {Function}
                     * @context Point
                     * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ Show values in the chart's corner on mouse over
                     * @apioption plotOptions.series.point.events.mouseOver
                     */

                    /**
                     * Fires when the point is removed using the `.remove()` method. One
                     * parameter, `event`, is passed to the function. Returning `false`
                     * cancels the operation.
                     * 
                     * @type {Function}
                     * @context Point
                     * @sample {highcharts} highcharts/plotoptions/series-point-events-remove/ Remove point and confirm
                     * @since 1.2.0
                     * @apioption plotOptions.series.point.events.remove
                     */

                    /**
                     * Fires when the point is selected either programmatically or following
                     * a click on the point. One parameter, `event`, is passed to the function.
                     *  Returning `false` cancels the operation.
                     * 
                     * @type {Function}
                     * @context Point
                     * @sample {highcharts} highcharts/plotoptions/series-point-events-select/ Report the last selected point
                     * @sample {highmaps} maps/plotoptions/series-allowpointselect/ Report select and unselect
                     * @since 1.2.0
                     * @apioption plotOptions.series.point.events.select
                     */

                    /**
                     * Fires when the point is unselected either programmatically or following
                     * a click on the point. One parameter, `event`, is passed to the function.
                     *  Returning `false` cancels the operation.
                     * 
                     * @type {Function}
                     * @context Point
                     * @sample {highcharts} highcharts/plotoptions/series-point-events-unselect/ Report the last unselected point
                     * @sample {highmaps} maps/plotoptions/series-allowpointselect/ Report select and unselect
                     * @since 1.2.0
                     * @apioption plotOptions.series.point.events.unselect
                     */

                    /**
                     * Fires when the point is updated programmatically through the `.update()`
                     * method. One parameter, `event`, is passed to the function. The new
                     * point options can be accessed through `event.options`. Returning
                     * `false` cancels the operation.
                     * 
                     * @type {Function}
                     * @context Point
                     * @sample {highcharts} highcharts/plotoptions/series-point-events-update/ Confirm point updating
                     * @since 1.2.0
                     * @apioption plotOptions.series.point.events.update
                     */

                }
            },



            /**
             * Options for the series data labels, appearing next to each data
             * point.
             * 
             * In styled mode, the data labels can be styled wtih the `.highcharts-data-label-box` and `.highcharts-data-label` class names ([see example](http://jsfiddle.
             * net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/series-
             * datalabels)).
             */
            dataLabels: {


                /**
                 * The alignment of the data label compared to the point. If `right`,
                 * the right side of the label should be touching the point. For
                 * points with an extent, like columns, the alignments also dictates
                 * how to align it inside the box, as given with the [inside](#plotOptions.
                 * column.dataLabels.inside) option. Can be one of "left", "center"
                 * or "right".
                 * 
                 * @validvalue ["left", "center", "right"]
                 * @type {String}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-align-left/ Left aligned
                 * @default center
                 */
                align: 'center',


                /**
                 * Whether to allow data labels to overlap. To make the labels less
                 * sensitive for overlapping, the [dataLabels.padding](#plotOptions.
                 * series.dataLabels.padding) can be set to 0.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
                 * @sample {highstock} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
                 * @sample {highmaps} highcharts/plotoptions/series-datalabels-allowoverlap-false/ Don't allow overlap
                 * @default false
                 * @since 4.1.0
                 * @apioption plotOptions.series.dataLabels.allowOverlap
                 */


                /**
                 * The border radius in pixels for the data label.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
                 * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
                 * @sample {highmaps} maps/plotoptions/series-datalabels-box/ Data labels box options
                 * @default 0
                 * @since 2.2.1
                 * @apioption plotOptions.series.dataLabels.borderRadius
                 */


                /**
                 * The border width in pixels for the data label.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
                 * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
                 * @default 0
                 * @since 2.2.1
                 * @apioption plotOptions.series.dataLabels.borderWidth
                 */

                /**
                 * A class name for the data label. Particularly in styled mode, this can
                 * be used to give each series' or point's data label unique styling.
                 * In addition to this option, a default color class name is added
                 * so that we can give the labels a [contrast text shadow](http://jsfiddle.
                 * net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/data-
                 * label-contrast/).
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/css/series-datalabels/ Styling by CSS
                 * @sample {highstock} highcharts/css/series-datalabels/ Styling by CSS
                 * @sample {highmaps} highcharts/css/series-datalabels/ Styling by CSS
                 * @since 5.0.0
                 * @apioption plotOptions.series.dataLabels.className
                 */

                /**
                 * The text color for the data labels. Defaults to `null`. For certain series
                 * types, like column or map, the data labels can be drawn inside the points.
                 * In this case the data label will be drawn with maximum contrast by default.
                 * Additionally, it will be given a `text-outline` style with the opposite
                 * color, to further increase the contrast. This can be overridden by setting
                 * the `text-outline` style to `none` in the `dataLabels.style` option.
                 * 
                 * @type {Color}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-color/
                 *         Red data labels
                 * @sample {highmaps} maps/demo/color-axis/
                 *         White data labels
                 * @apioption plotOptions.series.dataLabels.color
                 */

                /**
                 * Whether to hide data labels that are outside the plot area. By default,
                 * the data label is moved inside the plot area according to the [overflow](#plotOptions.
                 * series.dataLabels.overflow) option.
                 * 
                 * @type {Boolean}
                 * @default true
                 * @since 2.3.3
                 * @apioption plotOptions.series.dataLabels.crop
                 */

                /**
                 * Whether to defer displaying the data labels until the initial series
                 * animation has finished.
                 * 
                 * @type {Boolean}
                 * @default true
                 * @since 4.0
                 * @product highcharts highstock
                 * @apioption plotOptions.series.dataLabels.defer
                 */

                /**
                 * Enable or disable the data labels.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-enabled/ Data labels enabled
                 * @sample {highmaps} maps/demo/color-axis/ Data labels enabled
                 * @default false
                 * @apioption plotOptions.series.dataLabels.enabled
                 */

                /**
                 * A [format string](http://www.highcharts.com/docs/chart-concepts/labels-
                 * and-string-formatting) for the data label. Available variables are
                 * the same as for `formatter`.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ Add a unit
                 * @sample {highstock} highcharts/plotoptions/series-datalabels-format/ Add a unit
                 * @sample {highmaps} maps/plotoptions/series-datalabels-format/ Formatted value in the data label
                 * @default {highcharts} {y}
                 * @default {highstock} {y}
                 * @default {highmaps} {point.value}
                 * @since 3.0
                 * @apioption plotOptions.series.dataLabels.format
                 */

                /**
                 * Callback JavaScript function to format the data label. Note that
                 * if a `format` is defined, the format takes precedence and the formatter
                 * is ignored. Available data are:
                 * 
                 * <table>
                 * 
                 * <tbody>
                 * 
                 * <tr>
                 * 
                 * <td>`this.percentage`</td>
                 * 
                 * <td>Stacked series and pies only. The point's percentage of the
                 * total.</td>
                 * 
                 * </tr>
                 * 
                 * <tr>
                 * 
                 * <td>`this.point`</td>
                 * 
                 * <td>The point object. The point name, if defined, is available
                 * through `this.point.name`.</td>
                 * 
                 * </tr>
                 * 
                 * <tr>
                 * 
                 * <td>`this.series`:</td>
                 * 
                 * <td>The series object. The series name is available through `this.
                 * series.name`.</td>
                 * 
                 * </tr>
                 * 
                 * <tr>
                 * 
                 * <td>`this.total`</td>
                 * 
                 * <td>Stacked series only. The total value at this point's x value.
                 * </td>
                 * 
                 * </tr>
                 * 
                 * <tr>
                 * 
                 * <td>`this.x`:</td>
                 * 
                 * <td>The x value.</td>
                 * 
                 * </tr>
                 * 
                 * <tr>
                 * 
                 * <td>`this.y`:</td>
                 * 
                 * <td>The y value.</td>
                 * 
                 * </tr>
                 * 
                 * </tbody>
                 * 
                 * </table>
                 * 
                 * @type {Function}
                 * @sample {highmaps} maps/plotoptions/series-datalabels-format/ Formatted value
                 */
                formatter: function() {
                    return this.y === null ? '' : H.numberFormat(this.y, -1);
                },



                /**
                 * Styles for the label. The default `color` setting is `"contrast"`,
                 * which is a pseudo color that Highcharts picks up and applies the
                 * maximum contrast to the underlying point item, for example the
                 * bar in a bar chart.
                 * 
                 * The `textOutline` is a pseudo property that
                 * applies an outline of the given width with the given color, which
                 * by default is the maximum contrast to the text. So a bright text
                 * color will result in a black text outline for maximum readability
                 * on a mixed background. In some cases, especially with grayscale
                 * text, the text outline doesn't work well, in which cases it can
                 * be disabled by setting it to `"none"`. When `useHTML` is true, the
                 * `textOutline` will not be picked up. In this, case, the same effect
                 * can be acheived through the `text-shadow` CSS property.
                 * 
                 * @type {CSSObject}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-style/
                 *         Bold labels
                 * @sample {highmaps} maps/demo/color-axis/ Bold labels
                 * @default {"color": "contrast", "fontSize": "11px", "fontWeight": "bold", "textOutline": "1px contrast" }
                 * @since 4.1.0
                 */
                style: {
                    fontSize: '11px',
                    fontWeight: 'bold',
                    color: 'contrast',
                    textOutline: '1px contrast'
                },

                /**
                 * The background color or gradient for the data label.
                 * 
                 * @type {Color}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
                 * @sample {highmaps} maps/plotoptions/series-datalabels-box/ Data labels box options
                 * @since 2.2.1
                 * @apioption plotOptions.series.dataLabels.backgroundColor
                 */

                /**
                 * The border color for the data label. Defaults to `undefined`.
                 * 
                 * @type {Color}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
                 * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
                 * @default undefined
                 * @since 2.2.1
                 * @apioption plotOptions.series.dataLabels.borderColor
                 */

                /**
                 * The shadow of the box. Works best with `borderWidth` or `backgroundColor`.
                 * Since 2.3 the shadow can be an object configuration containing `color`,
                 *  `offsetX`, `offsetY`, `opacity` and `width`.
                 * 
                 * @type {Boolean|Object}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
                 * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
                 * @default false
                 * @since 2.2.1
                 * @apioption plotOptions.series.dataLabels.shadow
                 */


                /**
                 * For points with an extent, like columns or map areas, whether to align the data
                 * label inside the box or to the actual value point. Defaults to `false`
                 * in most cases, `true` in stacked columns.
                 * 
                 * @type {Boolean}
                 * @since 3.0
                 * @apioption plotOptions.series.dataLabels.inside
                 */

                /**
                 * How to handle data labels that flow outside the plot area. The default
                 * is `justify`, which aligns them inside the plot area. For columns
                 * and bars, this means it will be moved inside the bar. To display
                 * data labels outside the plot area, set `crop` to `false` and `overflow`
                 * to `"none"`.
                 * 
                 * @validvalue ["justify", "none"]
                 * @type {String}
                 * @default justify
                 * @since 3.0.6
                 * @apioption plotOptions.series.dataLabels.overflow
                 */

                /**
                 * Text rotation in degrees. Note that due to a more complex structure,
                 * backgrounds, borders and padding will be lost on a rotated data
                 * label.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical labels
                 * @default 0
                 * @apioption plotOptions.series.dataLabels.rotation
                 */

                /**
                 * Whether to [use HTML](http://www.highcharts.com/docs/chart-concepts/labels-
                 * and-string-formatting#html) to render the labels.
                 *
                 * @type {Boolean}
                 * @default false
                 * @apioption plotOptions.series.dataLabels.useHTML
                 */

                /**
                 * The vertical alignment of a data label. Can be one of `top`, `middle`
                 * or `bottom`. The default value depends on the data, for instance
                 * in a column chart, the label is above positive values and below
                 * negative values.
                 * 
                 * @validvalue ["top", "middle", "bottom"]
                 * @type {String}
                 * @since 2.3.3
                 */
                verticalAlign: 'bottom', // above singular point


                /**
                 * The x position offset of the label relative to the point.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical and positioned
                 * @default 0
                 */
                x: 0,


                /**
                 * The y position offset of the label relative to the point.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ Vertical and positioned
                 * @default -6
                 */
                y: 0,


                /**
                 * When either the `borderWidth` or the `backgroundColor` is set,
                 * this is the padding within the box.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ Data labels box options
                 * @sample {highstock} highcharts/plotoptions/series-datalabels-box/ Data labels box options
                 * @sample {highmaps} maps/plotoptions/series-datalabels-box/ Data labels box options
                 * @default {highcharts} 5
                 * @default {highstock} 5
                 * @default {highmaps} 0
                 * @since 2.2.1
                 */
                padding: 5

                /**
                 * The name of a symbol to use for the border around the label. Symbols
                 * are predefined functions on the Renderer object.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations
                 * @sample {highstock} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations
                 * @sample {highmaps} highcharts/plotoptions/series-datalabels-shape/ A callout for annotations (Highcharts demo)
                 * @default square
                 * @since 4.1.2
                 * @apioption plotOptions.series.dataLabels.shape
                 */

                /**
                 * The Z index of the data labels. The default Z index puts it above
                 * the series. Use a Z index of 2 to display it behind the series.
                 * 
                 * @type {Number}
                 * @default 6
                 * @since 2.3.5
                 * @apioption plotOptions.series.dataLabels.zIndex
                 */

                /**
                 * A declarative filter for which data labels to display. The
                 * declarative filter is designed for use when callback functions are
                 * not available, like when the chart options require a pure JSON
                 * structure or for use with graphical editors. For programmatic
                 * control, use the `formatter` instead, and return `false` to disable
                 * a single data label.
                 *
                 * @example
                 * filter: {
                 *     property: 'percentage',
                 *     operator: '>',
                 *     value: 4
                 * }
                 *
                 * @sample highcharts/demo/pie-monochrome
                 *         Data labels filtered by percentage
                 *
                 * @type {Object}
                 * @since 6.0.3
                 * @apioption plotOptions.series.dataLabels.filter
                 */

                /**
                 * The point property to filter by. Point options are passed directly to
                 * properties, additionally there are `y` value, `percentage` and others
                 * listed under [Point](https://api.highcharts.com/class-reference/Highcharts.Point)
                 * members.
                 *
                 * @type {String}
                 * @apioption plotOptions.series.dataLabels.filter.property
                 */

                /**
                 * The operator to compare by. Can be one of `>`, `<`, `>=`, `<=`, `==`,
                 * and `===`.
                 *
                 * @type {String}
                 * @validvalue [">", "<", ">=", "<=", "==", "===""]
                 * @apioption plotOptions.series.dataLabels.filter.operator
                 */

                /**
                 * The value to compare against.
                 *
                 * @type {Mixed}
                 * @apioption plotOptions.series.dataLabels.filter.value
                 */
            },
            // draw points outside the plot area when the number of points is less than
            // this



            /**
             * When the series contains less points than the crop threshold, all
             * points are drawn, even if the points fall outside the visible plot
             * area at the current zoom. The advantage of drawing all points (including
             * markers and columns), is that animation is performed on updates.
             * On the other hand, when the series contains more points than the
             * crop threshold, the series data is cropped to only contain points
             * that fall within the plot area. The advantage of cropping away invisible
             * points is to increase performance on large series.
             * 
             * @type {Number}
             * @default 300
             * @since 2.2
             * @product highcharts highstock
             */
            cropThreshold: 300,



            /**
             * The width of each point on the x axis. For example in a column chart
             * with one value each day, the pointRange would be 1 day (= 24 * 3600
             * * 1000 milliseconds). This is normally computed automatically, but
             * this option can be used to override the automatic value.
             * 
             * @type {Number}
             * @default 0
             * @product highstock
             */
            pointRange: 0,

            /**
             * When this is true, the series will not cause the Y axis to cross
             * the zero plane (or [threshold](#plotOptions.series.threshold) option)
             * unless the data actually crosses the plane.
             * 
             * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
             * 3 will make the Y axis show negative values according to the `minPadding`
             * option. If `softThreshold` is `true`, the Y axis starts at 0.
             * 
             * @type {Boolean}
             * @default true
             * @since 4.1.9
             * @product highcharts highstock
             */
            softThreshold: true,



            /**
             * A wrapper object for all the series options in specific states.
             * 
             * @type {plotOptions.series.states}
             */
            states: {


                /**
                 * Options for the hovered series. These settings override the normal
                 * state options when a series is moused over or touched.
                 *
                 */
                hover: {

                    /**
                     * Enable separate styles for the hovered series to visualize that the
                     * user hovers either the series itself or the legend. .
                     * 
                     * @type {Boolean}
                     * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled/ Line
                     * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-column/ Column
                     * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-pie/ Pie
                     * @default true
                     * @since 1.2
                     * @apioption plotOptions.series.states.hover.enabled
                     */


                    /**
                     * Animation setting for hovering the graph in line-type series.
                     * 
                     * @type {Boolean|Object}
                     * @default { "duration": 50 }
                     * @since 5.0.8
                     * @product highcharts
                     */
                    animation: {
                        /**
                         * The duration of the hover animation in milliseconds. By
                         * default the hover state animates quickly in, and slowly back
                         * to normal.
                         */
                        duration: 50
                    },

                    /**
                     * Pixel with of the graph line. By default this property is
                     * undefined, and the `lineWidthPlus` property dictates how much
                     * to increase the linewidth from normal state.
                     * 
                     * @type {Number}
                     * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidth/
                     *         5px line on hover
                     * @default undefined
                     * @product highcharts highstock
                     * @apioption plotOptions.series.states.hover.lineWidth
                     */


                    /**
                     * The additional line width for the graph of a hovered series.
                     * 
                     * @type {Number}
                     * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/
                     *         5 pixels wider
                     * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/
                     *         5 pixels wider
                     * @default 1
                     * @since 4.0.3
                     * @product highcharts highstock
                     */
                    lineWidthPlus: 1,



                    /**
                     * In Highcharts 1.0, the appearance of all markers belonging to
                     * the hovered series. For settings on the hover state of the individual
                     * point, see [marker.states.hover](#plotOptions.series.marker.states.
                     * hover).
                     * 
                     * @extends plotOptions.series.marker
                     * @deprecated
                     * @product highcharts highstock
                     */
                    marker: {
                        // lineWidth: base + 1,
                        // radius: base + 1
                    },



                    /**
                     * Options for the halo appearing around the hovered point in line-
                     * type series as well as outside the hovered slice in pie charts.
                     * By default the halo is filled by the current point or series
                     * color with an opacity of 0.25\. The halo can be disabled by setting
                     * the `halo` option to `false`.
                     * 
                     * In styled mode, the halo is styled with the `.highcharts-halo` class, with colors inherited from `.highcharts-color-{n}`.
                     * 
                     * @type {Object}
                     * @sample {highcharts} highcharts/plotoptions/halo/ Halo options
                     * @sample {highstock} highcharts/plotoptions/halo/ Halo options
                     * @since 4.0
                     * @product highcharts highstock
                     */
                    halo: {

                        /**
                         * A collection of SVG attributes to override the appearance of the
                         * halo, for example `fill`, `stroke` and `stroke-width`.
                         * 
                         * @type {Object}
                         * @since 4.0
                         * @product highcharts highstock
                         * @apioption plotOptions.series.states.hover.halo.attributes
                         */


                        /**
                         * The pixel size of the halo. For point markers this is the radius
                         * of the halo. For pie slices it is the width of the halo outside
                         * the slice. For bubbles it defaults to 5 and is the width of the
                         * halo outside the bubble.
                         * 
                         * @type {Number}
                         * @default 10
                         * @since 4.0
                         * @product highcharts highstock
                         */
                        size: 10,




                        /**
                         * Opacity for the halo unless a specific fill is overridden using
                         * the `attributes` setting. Note that Highcharts is only able to
                         * apply opacity to colors of hex or rgb(a) formats.
                         * 
                         * @type {Number}
                         * @default 0.25
                         * @since 4.0
                         * @product highcharts highstock
                         */
                        opacity: 0.25

                    }
                },


                /**
                 * Specific options for point in selected states, after being selected
                 * by [allowPointSelect](#plotOptions.series.allowPointSelect) or
                 * programmatically.
                 * 
                 * @type {Object}
                 * @extends plotOptions.series.states.hover
                 * @excluding brightness
                 * @sample {highmaps} maps/plotoptions/series-allowpointselect/
                 *         Allow point select demo
                 * @product highmaps
                 */
                select: {
                    marker: {}
                }
            },



            /**
             * Sticky tracking of mouse events. When true, the `mouseOut` event
             * on a series isn't triggered until the mouse moves over another series,
             * or out of the plot area. When false, the `mouseOut` event on a
             * series is triggered when the mouse leaves the area around the series'
             * graph or markers. This also implies the tooltip when not shared. When
             * `stickyTracking` is false and `tooltip.shared` is false, the tooltip will
             * be hidden when moving the mouse between series. Defaults to true for line
             * and area type series, but to false for columns, pies etc.
             * 
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/series-stickytracking-true/
             *         True by default
             * @sample {highcharts} highcharts/plotoptions/series-stickytracking-false/
             *         False
             * @default {highcharts} true
             * @default {highstock} true
             * @default {highmaps} false
             * @since 2.0
             */
            stickyTracking: true,

            /**
             * A configuration object for the tooltip rendering of each single series.
             * Properties are inherited from [tooltip](#tooltip), but only the
             * following properties can be defined on a series level.
             * 
             * @type {Object}
             * @extends tooltip
             * @excluding animation,backgroundColor,borderColor,borderRadius,borderWidth,crosshairs,enabled,formatter,positioner,shadow,shared,shape,snap,style,useHTML
             * @since 2.3
             * @apioption plotOptions.series.tooltip
             */

            /**
             * When a series contains a data array that is longer than this, only
             * one dimensional arrays of numbers, or two dimensional arrays with
             * x and y values are allowed. Also, only the first point is tested,
             * and the rest are assumed to be the same format. This saves expensive
             * data checking and indexing in long series. Set it to `0` disable.
             * 
             * @type {Number}
             * @default 1000
             * @since 2.2
             * @product highcharts highstock
             */
            turboThreshold: 1000,

            /**
             * An array defining zones within a series. Zones can be applied to
             * the X axis, Y axis or Z axis for bubbles, according to the `zoneAxis`
             * option.
             * 
             * In styled mode, the color zones are styled with the `.highcharts-
             * zone-{n}` class, or custom classed from the `className` option ([view
             * live demo](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/color-
             * zones/)).
             * 
             * @type {Array}
             * @see [zoneAxis](#plotOptions.series.zoneAxis)
             * @sample {highcharts} highcharts/series/color-zones-simple/ Color zones
             * @sample {highstock} highcharts/series/color-zones-simple/ Color zones
             * @since 4.1.0
             * @product highcharts highstock
             * @apioption plotOptions.series.zones
             */

            /**
             * Styled mode only. A custom class name for the zone.
             * 
             * @type {String}
             * @sample {highcharts} highcharts/css/color-zones/ Zones styled by class name
             * @sample {highstock} highcharts/css/color-zones/ Zones styled by class name
             * @sample {highmaps} highcharts/css/color-zones/ Zones styled by class name
             * @since 5.0.0
             * @apioption plotOptions.series.zones.className
             */

            /**
             * Defines the color of the series.
             * 
             * @type {Color}
             * @see [series color](#plotOptions.series.color)
             * @since 4.1.0
             * @product highcharts highstock
             * @apioption plotOptions.series.zones.color
             */

            /**
             * A name for the dash style to use for the graph.
             * 
             * @type {String}
             * @see [series.dashStyle](#plotOptions.series.dashStyle)
             * @sample {highcharts} highcharts/series/color-zones-dashstyle-dot/
             *         Dashed line indicates prognosis
             * @sample {highstock} highcharts/series/color-zones-dashstyle-dot/
             *         Dashed line indicates prognosis
             * @since 4.1.0
             * @product highcharts highstock
             * @apioption plotOptions.series.zones.dashStyle
             */

            /**
             * Defines the fill color for the series (in area type series)
             * 
             * @type {Color}
             * @see [fillColor](#plotOptions.area.fillColor)
             * @since 4.1.0
             * @product highcharts highstock
             * @apioption plotOptions.series.zones.fillColor
             */

            /**
             * The value up to where the zone extends, if undefined the zones stretches
             * to the last value in the series.
             * 
             * @type {Number}
             * @default undefined
             * @since 4.1.0
             * @product highcharts highstock
             * @apioption plotOptions.series.zones.value
             */



            /**
             * Determines whether the series should look for the nearest point
             * in both dimensions or just the x-dimension when hovering the series.
             * Defaults to `'xy'` for scatter series and `'x'` for most other
             * series. If the data has duplicate x-values, it is recommended to
             * set this to `'xy'` to allow hovering over all points.
             * 
             * Applies only to series types using nearest neighbor search (not
             * direct hover) for tooltip.
             * 
             * @validvalue ['x', 'xy']
             * @type {String}
             * @sample {highcharts} highcharts/series/findnearestpointby/
             *         Different hover behaviors
             * @sample {highstock} highcharts/series/findnearestpointby/
             *         Different hover behaviors
             * @sample {highmaps} highcharts/series/findnearestpointby/
             *         Different hover behaviors
             * @since 5.0.10
             */
            findNearestPointBy: 'x'

        }, /** @lends Highcharts.Series.prototype */ {
            isCartesian: true,
            pointClass: Point,
            sorted: true, // requires the data to be sorted
            requireSorting: true,
            directTouch: false,
            axisTypes: ['xAxis', 'yAxis'],
            colorCounter: 0,
            // each point's x and y values are stored in this.xData and this.yData
            parallelArrays: ['x', 'y'],
            coll: 'series',
            init: function(chart, options) {
                var series = this,
                    events,
                    chartSeries = chart.series,
                    lastSeries;

                /**
                 * Read only. The chart that the series belongs to.
                 *
                 * @name chart
                 * @memberOf Series
                 * @type {Chart}
                 */
                series.chart = chart;

                /**
                 * Read only. The series' type, like "line", "area", "column" etc. The
                 * type in the series options anc can be altered using {@link
                 * Series#update}.
                 *
                 * @name type
                 * @memberOf Series
                 * @type String
                 */

                /**
                 * Read only. The series' current options. To update, use {@link
                 * Series#update}.
                 *
                 * @name options
                 * @memberOf Series
                 * @type SeriesOptions
                 */
                series.options = options = series.setOptions(options);
                series.linkedSeries = [];

                // bind the axes
                series.bindAxes();

                // set some variables
                extend(series, {
                    /**
                     * The series name as given in the options. Defaults to
                     * "Series {n}".
                     *
                     * @name name
                     * @memberOf Series
                     * @type {String}
                     */
                    name: options.name,
                    state: '',
                    /**
                     * Read only. The series' visibility state as set by {@link
                     * Series#show}, {@link Series#hide}, or in the initial
                     * configuration.
                     *
                     * @name visible
                     * @memberOf Series
                     * @type {Boolean}
                     */
                    visible: options.visible !== false, // true by default
                    /**
                     * Read only. The series' selected state as set by {@link
                     * Highcharts.Series#select}.
                     *
                     * @name selected
                     * @memberOf Series
                     * @type {Boolean}
                     */
                    selected: options.selected === true // false by default
                });

                // register event listeners
                events = options.events;

                objectEach(events, function(event, eventType) {
                    addEvent(series, eventType, event);
                });
                if (
                    (events && events.click) ||
                    (
                        options.point &&
                        options.point.events &&
                        options.point.events.click
                    ) ||
                    options.allowPointSelect
                ) {
                    chart.runTrackerClick = true;
                }

                series.getColor();
                series.getSymbol();

                // Set the data
                each(series.parallelArrays, function(key) {
                    series[key + 'Data'] = [];
                });
                series.setData(options.data, false);

                // Mark cartesian
                if (series.isCartesian) {
                    chart.hasCartesianSeries = true;
                }

                // Get the index and register the series in the chart. The index is one
                // more than the current latest series index (#5960).
                if (chartSeries.length) {
                    lastSeries = chartSeries[chartSeries.length - 1];
                }
                series._i = pick(lastSeries && lastSeries._i, -1) + 1;

                // Insert the series and re-order all series above the insertion point.
                chart.orderSeries(this.insert(chartSeries));
            },

            /**
             * Insert the series in a collection with other series, either the chart
             * series or yAxis series, in the correct order according to the index
             * option. Used internally when adding series.
             *
             * @private
             * @param   {Array.<Series>} collection
             *          A collection of series, like `chart.series` or `xAxis.series`.
             * @returns {Number} The index of the series in the collection.
             */
            insert: function(collection) {
                var indexOption = this.options.index,
                    i;

                // Insert by index option
                if (isNumber(indexOption)) {
                    i = collection.length;
                    while (i--) {
                        // Loop down until the interted element has higher index
                        if (indexOption >=
                            pick(collection[i].options.index, collection[i]._i)) {
                            collection.splice(i + 1, 0, this);
                            break;
                        }
                    }
                    if (i === -1) {
                        collection.unshift(this);
                    }
                    i = i + 1;

                    // Or just push it to the end
                } else {
                    collection.push(this);
                }
                return pick(i, collection.length - 1);
            },

            /**
             * Set the xAxis and yAxis properties of cartesian series, and register the
             * series in the `axis.series` array.
             *
             * @private
             */
            bindAxes: function() {
                var series = this,
                    seriesOptions = series.options,
                    chart = series.chart,
                    axisOptions;

                // repeat for xAxis and yAxis
                each(series.axisTypes || [], function(AXIS) {

                    // loop through the chart's axis objects
                    each(chart[AXIS], function(axis) {
                        axisOptions = axis.options;

                        // apply if the series xAxis or yAxis option mathches the number
                        // of the axis, or if undefined, use the first axis
                        if (
                            seriesOptions[AXIS] === axisOptions.index ||
                            (
                                seriesOptions[AXIS] !== undefined &&
                                seriesOptions[AXIS] === axisOptions.id
                            ) ||
                            (
                                seriesOptions[AXIS] === undefined &&
                                axisOptions.index === 0
                            )
                        ) {

                            // register this series in the axis.series lookup
                            series.insert(axis.series);

                            // set this series.xAxis or series.yAxis reference
                            /**
                             * Read only. The unique xAxis object associated with the
                             * series.
                             *
                             * @name xAxis
                             * @memberOf Series
                             * @type Axis
                             */
                            /**
                             * Read only. The unique yAxis object associated with the
                             * series.
                             *
                             * @name yAxis
                             * @memberOf Series
                             * @type Axis
                             */
                            series[AXIS] = axis;

                            // mark dirty for redraw
                            axis.isDirty = true;
                        }
                    });

                    // The series needs an X and an Y axis
                    if (!series[AXIS] && series.optionalAxis !== AXIS) {
                        H.error(18, true);
                    }

                });
            },

            /**
             * For simple series types like line and column, the data values are held in
             * arrays like xData and yData for quick lookup to find extremes and more.
             * For multidimensional series like bubble and map, this can be extended
             * with arrays like zData and valueData by adding to the
             * `series.parallelArrays` array.
             *
             * @private
             */
            updateParallelArrays: function(point, i) {
                var series = point.series,
                    args = arguments,
                    fn = isNumber(i) ?
                    // Insert the value in the given position
                    function(key) {
                        var val = key === 'y' && series.toYData ?
                            series.toYData(point) :
                            point[key];
                        series[key + 'Data'][i] = val;
                    } :
                    // Apply the method specified in i with the following arguments
                    // as arguments
                    function(key) {
                        Array.prototype[i].apply(
                            series[key + 'Data'],
                            Array.prototype.slice.call(args, 2)
                        );
                    };

                each(series.parallelArrays, fn);
            },

            /**
             * Return an auto incremented x value based on the pointStart and
             * pointInterval options. This is only used if an x value is not given for
             * the point that calls autoIncrement.
             *
             * @private
             */
            autoIncrement: function() {

                var options = this.options,
                    xIncrement = this.xIncrement,
                    date,
                    pointInterval,
                    pointIntervalUnit = options.pointIntervalUnit;

                xIncrement = pick(xIncrement, options.pointStart, 0);

                this.pointInterval = pointInterval = pick(
                    this.pointInterval,
                    options.pointInterval,
                    1
                );

                // Added code for pointInterval strings
                if (pointIntervalUnit) {
                    date = new Date(xIncrement);

                    if (pointIntervalUnit === 'day') {
                        date = +date[Date.hcSetDate](
                            date[Date.hcGetDate]() + pointInterval
                        );
                    } else if (pointIntervalUnit === 'month') {
                        date = +date[Date.hcSetMonth](
                            date[Date.hcGetMonth]() + pointInterval
                        );
                    } else if (pointIntervalUnit === 'year') {
                        date = +date[Date.hcSetFullYear](
                            date[Date.hcGetFullYear]() + pointInterval
                        );
                    }
                    pointInterval = date - xIncrement;

                }

                this.xIncrement = xIncrement + pointInterval;
                return xIncrement;
            },

            /**
             * Set the series options by merging from the options tree. Called
             * internally on initiating and updating series. This function will not
             * redraw the series. For API usage, use {@link Series#update}.
             * 
             * @param  {Options.plotOptions.series} itemOptions
             *         The series options.
             */
            setOptions: function(itemOptions) {
                var chart = this.chart,
                    chartOptions = chart.options,
                    plotOptions = chartOptions.plotOptions,
                    userOptions = chart.userOptions || {},
                    userPlotOptions = userOptions.plotOptions || {},
                    typeOptions = plotOptions[this.type],
                    options,
                    zones;

                this.userOptions = itemOptions;

                // General series options take precedence over type options because
                // otherwise, default type options like column.animation would be
                // overwritten by the general option. But issues have been raised here
                // (#3881), and the solution may be to distinguish between default
                // option and userOptions like in the tooltip below.
                options = merge(
                    typeOptions,
                    plotOptions.series,
                    itemOptions
                );

                // The tooltip options are merged between global and series specific
                // options. Importance order asscendingly:
                // globals: (1)tooltip, (2)plotOptions.series, (3)plotOptions[this.type]
                // init userOptions with possible later updates: 4-6 like 1-3 and
                // (7)this series options
                this.tooltipOptions = merge(
                    defaultOptions.tooltip, // 1
                    defaultOptions.plotOptions.series &&
                    defaultOptions.plotOptions.series.tooltip, // 2
                    defaultOptions.plotOptions[this.type].tooltip, // 3
                    chartOptions.tooltip.userOptions, // 4
                    plotOptions.series && plotOptions.series.tooltip, // 5
                    plotOptions[this.type].tooltip, // 6
                    itemOptions.tooltip // 7
                );

                // When shared tooltip, stickyTracking is true by default,
                // unless user says otherwise.
                this.stickyTracking = pick(
                    itemOptions.stickyTracking,
                    userPlotOptions[this.type] &&
                    userPlotOptions[this.type].stickyTracking,
                    userPlotOptions.series && userPlotOptions.series.stickyTracking,
                    (
                        this.tooltipOptions.shared && !this.noSharedTooltip ?
                        true :
                        options.stickyTracking
                    )
                );

                // Delete marker object if not allowed (#1125)
                if (typeOptions.marker === null) {
                    delete options.marker;
                }

                // Handle color zones
                this.zoneAxis = options.zoneAxis;
                zones = this.zones = (options.zones || []).slice();
                if (
                    (options.negativeColor || options.negativeFillColor) &&
                    !options.zones
                ) {
                    zones.push({
                        value: options[this.zoneAxis + 'Threshold'] ||
                            options.threshold ||
                            0,
                        className: 'highcharts-negative',

                        color: options.negativeColor,
                        fillColor: options.negativeFillColor

                    });
                }
                if (zones.length) { // Push one extra zone for the rest
                    if (defined(zones[zones.length - 1].value)) {
                        zones.push({

                            color: this.color,
                            fillColor: this.fillColor

                        });
                    }
                }
                return options;
            },

            getCyclic: function(prop, value, defaults) {
                var i,
                    chart = this.chart,
                    userOptions = this.userOptions,
                    indexName = prop + 'Index',
                    counterName = prop + 'Counter',
                    len = defaults ? defaults.length : pick(
                        chart.options.chart[prop + 'Count'],
                        chart[prop + 'Count']
                    ),
                    setting;

                if (!value) {
                    // Pick up either the colorIndex option, or the _colorIndex after
                    // Series.update()
                    setting = pick(
                        userOptions[indexName],
                        userOptions['_' + indexName]
                    );
                    if (defined(setting)) { // after Series.update()
                        i = setting;
                    } else {
                        // #6138
                        if (!chart.series.length) {
                            chart[counterName] = 0;
                        }
                        userOptions['_' + indexName] = i = chart[counterName] % len;
                        chart[counterName] += 1;
                    }
                    if (defaults) {
                        value = defaults[i];
                    }
                }
                // Set the colorIndex
                if (i !== undefined) {
                    this[indexName] = i;
                }
                this[prop] = value;
            },

            /**
             * Get the series' color based on either the options or pulled from global
             * options.
             *
             * @return  {Color} The series color.
             */

            getColor: function() {
                if (this.options.colorByPoint) {
                    // #4359, selected slice got series.color even when colorByPoint was
                    // set.
                    this.options.color = null;
                } else {
                    this.getCyclic(
                        'color',
                        this.options.color || defaultPlotOptions[this.type].color,
                        this.chart.options.colors
                    );
                }
            },

            /**
             * Get the series' symbol based on either the options or pulled from global
             * options.
             */
            getSymbol: function() {
                var seriesMarkerOption = this.options.marker;

                this.getCyclic(
                    'symbol',
                    seriesMarkerOption.symbol,
                    this.chart.options.symbols
                );
            },

            drawLegendSymbol: LegendSymbolMixin.drawLineMarker,

            /**
             * Apply a new set of data to the series and optionally redraw it. The new
             * data array is passed by reference (except in case of `updatePoints`), and
             * may later be mutated when updating the chart data.
             *
             * Note the difference in behaviour when setting the same amount of points,
             * or a different amount of points, as handled by the `updatePoints`
             * parameter.
             *
             * @param  {SeriesDataOptions} data
             *         Takes an array of data in the same format as described under
             *         `series.typedata` for the given series type.
             * @param  {Boolean} [redraw=true]
             *         Whether to redraw the chart after the series is altered. If doing
             *         more operations on the chart, it is a good idea to set redraw to
             *         false and call {@link Chart#redraw} after.
             * @param  {AnimationOptions} [animation]
             *         When the updated data is the same length as the existing data,
             *         points will be updated by default, and animation visualizes how
             *         the points are changed. Set false to disable animation, or a
             *         configuration object to set duration or easing.
             * @param  {Boolean} [updatePoints=true]
             *         When the updated data is the same length as the existing data,
             *         points will be updated instead of replaced. This allows updating
             *         with animation and performs better. In this case, the original
             *         array is not passed by reference. Set false to prevent.
             *
             * @sample highcharts/members/series-setdata/
             *         Set new data from a button
             * @sample highcharts/members/series-setdata-pie/
             *         Set data in a pie
             * @sample stock/members/series-setdata/
             *         Set new data in Highstock
             * @sample maps/members/series-setdata/
             *         Set new data in Highmaps
             */
            setData: function(data, redraw, animation, updatePoints) {
                var series = this,
                    oldData = series.points,
                    oldDataLength = (oldData && oldData.length) || 0,
                    dataLength,
                    options = series.options,
                    chart = series.chart,
                    firstPoint = null,
                    xAxis = series.xAxis,
                    i,
                    turboThreshold = options.turboThreshold,
                    pt,
                    xData = this.xData,
                    yData = this.yData,
                    pointArrayMap = series.pointArrayMap,
                    valueCount = pointArrayMap && pointArrayMap.length;

                data = data || [];
                dataLength = data.length;
                redraw = pick(redraw, true);

                // If the point count is the same as is was, just run Point.update which
                // is cheaper, allows animation, and keeps references to points.
                if (
                    updatePoints !== false &&
                    dataLength &&
                    oldDataLength === dataLength &&
                    !series.cropped &&
                    !series.hasGroupedData &&
                    series.visible
                ) {
                    each(data, function(point, i) {
                        // .update doesn't exist on a linked, hidden series (#3709)
                        if (oldData[i].update && point !== options.data[i]) {
                            oldData[i].update(point, false, null, false);
                        }
                    });

                } else {

                    // Reset properties
                    series.xIncrement = null;

                    series.colorCounter = 0; // for series with colorByPoint (#1547)

                    // Update parallel arrays
                    each(this.parallelArrays, function(key) {
                        series[key + 'Data'].length = 0;
                    });

                    // In turbo mode, only one- or twodimensional arrays of numbers are
                    // allowed. The first value is tested, and we assume that all the
                    // rest are defined the same way. Although the 'for' loops are
                    // similar, they are repeated inside each if-else conditional for
                    // max performance.
                    if (turboThreshold && dataLength > turboThreshold) {

                        // find the first non-null point
                        i = 0;
                        while (firstPoint === null && i < dataLength) {
                            firstPoint = data[i];
                            i++;
                        }


                        if (isNumber(firstPoint)) { // assume all points are numbers
                            for (i = 0; i < dataLength; i++) {
                                xData[i] = this.autoIncrement();
                                yData[i] = data[i];
                            }

                            // Assume all points are arrays when first point is
                        } else if (isArray(firstPoint)) {
                            if (valueCount) { // [x, low, high] or [x, o, h, l, c]
                                for (i = 0; i < dataLength; i++) {
                                    pt = data[i];
                                    xData[i] = pt[0];
                                    yData[i] = pt.slice(1, valueCount + 1);
                                }
                            } else { // [x, y]
                                for (i = 0; i < dataLength; i++) {
                                    pt = data[i];
                                    xData[i] = pt[0];
                                    yData[i] = pt[1];
                                }
                            }
                        } else {
                            // Highcharts expects configs to be numbers or arrays in
                            // turbo mode
                            H.error(12);
                        }
                    } else {
                        for (i = 0; i < dataLength; i++) {
                            if (data[i] !== undefined) { // stray commas in oldIE
                                pt = {
                                    series: series
                                };
                                series.pointClass.prototype.applyOptions.apply(
                                    pt, [data[i]]
                                );
                                series.updateParallelArrays(pt, i);
                            }
                        }
                    }

                    // Forgetting to cast strings to numbers is a common caveat when
                    // handling CSV or JSON
                    if (yData && isString(yData[0])) {
                        H.error(14, true);
                    }

                    series.data = [];
                    series.options.data = series.userOptions.data = data;

                    // destroy old points
                    i = oldDataLength;
                    while (i--) {
                        if (oldData[i] && oldData[i].destroy) {
                            oldData[i].destroy();
                        }
                    }

                    // reset minRange (#878)
                    if (xAxis) {
                        xAxis.minRange = xAxis.userMinRange;
                    }

                    // redraw
                    series.isDirty = chart.isDirtyBox = true;
                    series.isDirtyData = !!oldData;
                    animation = false;
                }

                // Typically for pie series, points need to be processed and generated
                // prior to rendering the legend
                if (options.legendType === 'point') {
                    this.processData();
                    this.generatePoints();
                }

                if (redraw) {
                    chart.redraw(animation);
                }
            },

            /**
             * Internal function to process the data by cropping away unused data points
             * if the series is longer than the crop threshold. This saves computing
             * time for large series. In Highstock, this function is extended to
             * provide data grouping.
             *
             * @private
             * @param  {Boolean} force
             *         Force data grouping.
             */
            processData: function(force) {
                var series = this,
                    processedXData = series.xData, // copied during slice operation
                    processedYData = series.yData,
                    dataLength = processedXData.length,
                    croppedData,
                    cropStart = 0,
                    cropped,
                    distance,
                    closestPointRange,
                    xAxis = series.xAxis,
                    i, // loop variable
                    options = series.options,
                    cropThreshold = options.cropThreshold,
                    getExtremesFromAll =
                    series.getExtremesFromAll ||
                    options.getExtremesFromAll, // #4599
                    isCartesian = series.isCartesian,
                    xExtremes,
                    val2lin = xAxis && xAxis.val2lin,
                    isLog = xAxis && xAxis.isLog,
                    throwOnUnsorted = series.requireSorting,
                    min,
                    max;

                // If the series data or axes haven't changed, don't go through this.
                // Return false to pass the message on to override methods like in data
                // grouping.
                if (
                    isCartesian &&
                    !series.isDirty &&
                    !xAxis.isDirty &&
                    !series.yAxis.isDirty &&
                    !force
                ) {
                    return false;
                }

                if (xAxis) {
                    xExtremes = xAxis.getExtremes(); // corrected for log axis (#3053)
                    min = xExtremes.min;
                    max = xExtremes.max;
                }

                // optionally filter out points outside the plot area
                if (
                    isCartesian &&
                    series.sorted &&
                    !getExtremesFromAll &&
                    (!cropThreshold || dataLength > cropThreshold || series.forceCrop)
                ) {

                    // it's outside current extremes
                    if (
                        processedXData[dataLength - 1] < min ||
                        processedXData[0] > max
                    ) {
                        processedXData = [];
                        processedYData = [];

                        // only crop if it's actually spilling out
                    } else if (
                        processedXData[0] < min ||
                        processedXData[dataLength - 1] > max
                    ) {
                        croppedData = this.cropData(
                            series.xData,
                            series.yData,
                            min,
                            max
                        );
                        processedXData = croppedData.xData;
                        processedYData = croppedData.yData;
                        cropStart = croppedData.start;
                        cropped = true;
                    }
                }


                // Find the closest distance between processed points
                i = processedXData.length || 1;
                while (--i) {
                    distance = isLog ?
                        val2lin(processedXData[i]) - val2lin(processedXData[i - 1]) :
                        processedXData[i] - processedXData[i - 1];

                    if (
                        distance > 0 &&
                        (
                            closestPointRange === undefined ||
                            distance < closestPointRange
                        )
                    ) {
                        closestPointRange = distance;

                        // Unsorted data is not supported by the line tooltip, as well as
                        // data grouping and navigation in Stock charts (#725) and width
                        // calculation of columns (#1900)
                    } else if (distance < 0 && throwOnUnsorted) {
                        H.error(15);
                        throwOnUnsorted = false; // Only once
                    }
                }

                // Record the properties
                series.cropped = cropped; // undefined or true
                series.cropStart = cropStart;
                series.processedXData = processedXData;
                series.processedYData = processedYData;

                series.closestPointRange = closestPointRange;

            },

            /**
             * Iterate over xData and crop values between min and max. Returns object
             * containing crop start/end cropped xData with corresponding part of yData,
             * dataMin and dataMax within the cropped range.
             *
             * @private
             */
            cropData: function(xData, yData, min, max) {
                var dataLength = xData.length,
                    cropStart = 0,
                    cropEnd = dataLength,
                    // line-type series need one point outside
                    cropShoulder = pick(this.cropShoulder, 1),
                    i,
                    j;

                // iterate up to find slice start
                for (i = 0; i < dataLength; i++) {
                    if (xData[i] >= min) {
                        cropStart = Math.max(0, i - cropShoulder);
                        break;
                    }
                }

                // proceed to find slice end
                for (j = i; j < dataLength; j++) {
                    if (xData[j] > max) {
                        cropEnd = j + cropShoulder;
                        break;
                    }
                }

                return {
                    xData: xData.slice(cropStart, cropEnd),
                    yData: yData.slice(cropStart, cropEnd),
                    start: cropStart,
                    end: cropEnd
                };
            },


            /**
             * Generate the data point after the data has been processed by cropping
             * away unused points and optionally grouped in Highcharts Stock.
             *
             * @private
             */
            generatePoints: function() {
                var series = this,
                    options = series.options,
                    dataOptions = options.data,
                    data = series.data,
                    dataLength,
                    processedXData = series.processedXData,
                    processedYData = series.processedYData,
                    PointClass = series.pointClass,
                    processedDataLength = processedXData.length,
                    cropStart = series.cropStart || 0,
                    cursor,
                    hasGroupedData = series.hasGroupedData,
                    keys = options.keys,
                    point,
                    points = [],
                    i;

                if (!data && !hasGroupedData) {
                    var arr = [];
                    arr.length = dataOptions.length;
                    data = series.data = arr;
                }

                if (keys && hasGroupedData) {
                    // grouped data has already applied keys (#6590)
                    series.options.keys = false;
                }

                for (i = 0; i < processedDataLength; i++) {
                    cursor = cropStart + i;
                    if (!hasGroupedData) {
                        point = data[cursor];
                        if (!point && dataOptions[cursor] !== undefined) { // #970
                            data[cursor] = point = (new PointClass()).init(
                                series,
                                dataOptions[cursor],
                                processedXData[i]
                            );
                        }
                    } else {
                        // splat the y data in case of ohlc data array
                        point = (new PointClass()).init(
                            series, [processedXData[i]].concat(splat(processedYData[i]))
                        );

                        /**
                         * Highstock only. If a point object is created by data
                         * grouping, it doesn't reflect actual points in the raw data.
                         * In this case, the `dataGroup` property holds information
                         * that points back to the raw data.
                         *
                         * - `dataGroup.start` is the index of the first raw data point
                         * in the group.
                         * - `dataGroup.length` is the amount of points in the group.
                         *
                         * @name dataGroup
                         * @memberOf Point
                         * @type {Object}
                         *
                         */
                        point.dataGroup = series.groupMap[i];
                    }
                    if (point) { // #6279
                        point.index = cursor; // For faster access in Point.update
                        points[i] = point;
                    }
                }

                // restore keys options (#6590)
                series.options.keys = keys;

                // Hide cropped-away points - this only runs when the number of points
                // is above cropThreshold, or when swithching view from non-grouped
                // data to grouped data (#637)
                if (
                    data &&
                    (
                        processedDataLength !== (dataLength = data.length) ||
                        hasGroupedData
                    )
                ) {
                    for (i = 0; i < dataLength; i++) {
                        // when has grouped data, clear all points
                        if (i === cropStart && !hasGroupedData) {
                            i += processedDataLength;
                        }
                        if (data[i]) {
                            data[i].destroyElements();
                            data[i].plotX = undefined; // #1003
                        }
                    }
                }

                /**
                 * Read only. An array containing those values converted to points, but
                 * in case the series data length exceeds the `cropThreshold`, or if the
                 * data is grouped, `series.data` doesn't contain all the points. It
                 * only contains the points that have been created on demand. To
                 * modify the data, use {@link Highcharts.Series#setData} or {@link
                 * Highcharts.Point#update}.
                 *
                 * @name data
                 * @memberOf Highcharts.Series
                 * @see  Series.points
                 * @type {Array.<Highcharts.Point>}
                 */
                series.data = data;

                /**
                 * An array containing all currently visible point objects. In case of
                 * cropping, the cropped-away points are not part of this array. The
                 * `series.points` array starts at `series.cropStart` compared to
                 * `series.data` and `series.options.data`. If however the series data
                 * is grouped, these can't be correlated one to one. To
                 * modify the data, use {@link Highcharts.Series#setData} or {@link
                 * Highcharts.Point#update}.
                 * @name points
                 * @memberof Series
                 * @type {Array.<Point>}
                 */
                series.points = points;
            },

            /**
             * Calculate Y extremes for the visible data. The result is set as 
             * `dataMin` and `dataMax` on the Series item.
             *
             * @param  {Array.<Number>} [yData]
             *         The data to inspect. Defaults to the current data within the
             *         visible range.
             * 
             */
            getExtremes: function(yData) {
                var xAxis = this.xAxis,
                    yAxis = this.yAxis,
                    xData = this.processedXData,
                    yDataLength,
                    activeYData = [],
                    activeCounter = 0,
                    // #2117, need to compensate for log X axis
                    xExtremes = xAxis.getExtremes(),
                    xMin = xExtremes.min,
                    xMax = xExtremes.max,
                    validValue,
                    withinRange,
                    x,
                    y,
                    i,
                    j;

                yData = yData || this.stackedYData || this.processedYData || [];
                yDataLength = yData.length;

                for (i = 0; i < yDataLength; i++) {

                    x = xData[i];
                    y = yData[i];

                    // For points within the visible range, including the first point
                    // outside the visible range (#7061), consider y extremes.
                    validValue =
                        (isNumber(y, true) || isArray(y)) &&
                        (!yAxis.positiveValuesOnly || (y.length || y > 0));
                    withinRange =
                        this.getExtremesFromAll ||
                        this.options.getExtremesFromAll ||
                        this.cropped ||
                        ((xData[i + 1] || x) >= xMin && (xData[i - 1] || x) <= xMax);

                    if (validValue && withinRange) {

                        j = y.length;
                        if (j) { // array, like ohlc or range data
                            while (j--) {
                                if (y[j] !== null) {
                                    activeYData[activeCounter++] = y[j];
                                }
                            }
                        } else {
                            activeYData[activeCounter++] = y;
                        }
                    }
                }

                this.dataMin = arrayMin(activeYData);
                this.dataMax = arrayMax(activeYData);
            },

            /**
             * Translate data points from raw data values to chart specific positioning
             * data needed later in the `drawPoints` and `drawGraph` functions. This
             * function can be overridden in plugins and custom series type
             * implementations.
             */
            translate: function() {
                if (!this.processedXData) { // hidden series
                    this.processData();
                }
                this.generatePoints();
                var series = this,
                    options = series.options,
                    stacking = options.stacking,
                    xAxis = series.xAxis,
                    categories = xAxis.categories,
                    yAxis = series.yAxis,
                    points = series.points,
                    dataLength = points.length,
                    hasModifyValue = !!series.modifyValue,
                    i,
                    pointPlacement = options.pointPlacement,
                    dynamicallyPlaced =
                    pointPlacement === 'between' ||
                    isNumber(pointPlacement),
                    threshold = options.threshold,
                    stackThreshold = options.startFromThreshold ? threshold : 0,
                    plotX,
                    plotY,
                    lastPlotX,
                    stackIndicator,
                    closestPointRangePx = Number.MAX_VALUE;

                // Point placement is relative to each series pointRange (#5889)
                if (pointPlacement === 'between') {
                    pointPlacement = 0.5;
                }
                if (isNumber(pointPlacement)) {
                    pointPlacement *= pick(options.pointRange || xAxis.pointRange);
                }

                // Translate each point
                for (i = 0; i < dataLength; i++) {
                    var point = points[i],
                        xValue = point.x,
                        yValue = point.y,
                        yBottom = point.low,
                        stack = stacking && yAxis.stacks[(
                            series.negStacks &&
                            yValue < (stackThreshold ? 0 : threshold) ? '-' : ''
                        ) + series.stackKey],
                        pointStack,
                        stackValues;

                    // Discard disallowed y values for log axes (#3434)
                    if (yAxis.positiveValuesOnly && yValue !== null && yValue <= 0) {
                        point.isNull = true;
                    }

                    // Get the plotX translation
                    point.plotX = plotX = correctFloat( // #5236
                        Math.min(Math.max(-1e5, xAxis.translate(
                            xValue,
                            0,
                            0,
                            0,
                            1,
                            pointPlacement,
                            this.type === 'flags'
                        )), 1e5) // #3923
                    );

                    // Calculate the bottom y value for stacked series
                    if (
                        stacking &&
                        series.visible &&
                        !point.isNull &&
                        stack &&
                        stack[xValue]
                    ) {
                        stackIndicator = series.getStackIndicator(
                            stackIndicator,
                            xValue,
                            series.index
                        );
                        pointStack = stack[xValue];
                        stackValues = pointStack.points[stackIndicator.key];
                        yBottom = stackValues[0];
                        yValue = stackValues[1];

                        if (
                            yBottom === stackThreshold &&
                            stackIndicator.key === stack[xValue].base
                        ) {
                            yBottom = pick(threshold, yAxis.min);
                        }
                        if (yAxis.positiveValuesOnly && yBottom <= 0) { // #1200, #1232
                            yBottom = null;
                        }

                        point.total = point.stackTotal = pointStack.total;
                        point.percentage =
                            pointStack.total &&
                            (point.y / pointStack.total * 100);
                        point.stackY = yValue;

                        // Place the stack label
                        pointStack.setOffset(
                            series.pointXOffset || 0,
                            series.barW || 0
                        );

                    }

                    // Set translated yBottom or remove it
                    point.yBottom = defined(yBottom) ?
                        yAxis.translate(yBottom, 0, 1, 0, 1) :
                        null;

                    // general hook, used for Highstock compare mode
                    if (hasModifyValue) {
                        yValue = series.modifyValue(yValue, point);
                    }

                    // Set the the plotY value, reset it for redraws
                    point.plotY = plotY =
                        (typeof yValue === 'number' && yValue !== Infinity) ?
                        Math.min(Math.max(-1e5,
                            yAxis.translate(yValue, 0, 1, 0, 1)), 1e5) : // #3201
                        undefined;

                    point.isInside =
                        plotY !== undefined &&
                        plotY >= 0 &&
                        plotY <= yAxis.len && // #3519
                        plotX >= 0 &&
                        plotX <= xAxis.len;


                    // Set client related positions for mouse tracking
                    point.clientX = dynamicallyPlaced ?
                        correctFloat(
                            xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement)
                        ) :
                        plotX; // #1514, #5383, #5518

                    point.negative = point.y < (threshold || 0);

                    // some API data
                    point.category = categories && categories[point.x] !== undefined ?
                        categories[point.x] : point.x;

                    // Determine auto enabling of markers (#3635, #5099)
                    if (!point.isNull) {
                        if (lastPlotX !== undefined) {
                            closestPointRangePx = Math.min(
                                closestPointRangePx,
                                Math.abs(plotX - lastPlotX)
                            );
                        }
                        lastPlotX = plotX;
                    }

                    // Find point zone
                    point.zone = this.zones.length && point.getZone();
                }
                series.closestPointRangePx = closestPointRangePx;
            },

            /**
             * Return the series points with null points filtered out.
             *
             * @param  {Array.<Point>} [points]
             *         The points to inspect, defaults to {@link Series.points}.
             * @param  {Boolean} [insideOnly=false]
             *         Whether to inspect only the points that are inside the visible
             *         view.
             *
             * @return {Array.<Point>}
             *         The valid points.
             */
            getValidPoints: function(points, insideOnly) {
                var chart = this.chart;
                // #3916, #5029, #5085
                return grep(points || this.points || [], function isValidPoint(point) {
                    if (insideOnly && !chart.isInsidePlot(
                            point.plotX,
                            point.plotY,
                            chart.inverted
                        )) {
                        return false;
                    }
                    return !point.isNull;
                });
            },

            /**
             * Set the clipping for the series. For animated series it is called twice,
             * first to initiate animating the clip then the second time without the
             * animation to set the final clip.
             *
             * @private
             */
            setClip: function(animation) {
                var chart = this.chart,
                    options = this.options,
                    renderer = chart.renderer,
                    inverted = chart.inverted,
                    seriesClipBox = this.clipBox,
                    clipBox = seriesClipBox || chart.clipBox,
                    sharedClipKey =
                    this.sharedClipKey || [
                        '_sharedClip',
                        animation && animation.duration,
                        animation && animation.easing,
                        clipBox.height,
                        options.xAxis,
                        options.yAxis
                    ].join(','), // #4526
                    clipRect = chart[sharedClipKey],
                    markerClipRect = chart[sharedClipKey + 'm'];

                // If a clipping rectangle with the same properties is currently present
                // in the chart, use that.
                if (!clipRect) {

                    // When animation is set, prepare the initial positions
                    if (animation) {
                        clipBox.width = 0;
                        if (inverted) {
                            clipBox.x = chart.plotSizeX;
                        }

                        chart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect(
                            inverted ? chart.plotSizeX + 99 : -99, // include the width of the first marker
                            inverted ? -chart.plotLeft : -chart.plotTop,
                            99,
                            inverted ? chart.chartWidth : chart.chartHeight
                        );
                    }
                    chart[sharedClipKey] = clipRect = renderer.clipRect(clipBox);
                    // Create hashmap for series indexes
                    clipRect.count = {
                        length: 0
                    };

                }
                if (animation) {
                    if (!clipRect.count[this.index]) {
                        clipRect.count[this.index] = true;
                        clipRect.count.length += 1;
                    }
                }

                if (options.clip !== false) {
                    this.group.clip(animation || seriesClipBox ? clipRect : chart.clipRect);
                    this.markerGroup.clip(markerClipRect);
                    this.sharedClipKey = sharedClipKey;
                }

                // Remove the shared clipping rectangle when all series are shown
                if (!animation) {
                    if (clipRect.count[this.index]) {
                        delete clipRect.count[this.index];
                        clipRect.count.length -= 1;
                    }

                    if (clipRect.count.length === 0 && sharedClipKey && chart[sharedClipKey]) {
                        if (!seriesClipBox) {
                            chart[sharedClipKey] = chart[sharedClipKey].destroy();
                        }
                        if (chart[sharedClipKey + 'm']) {
                            chart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();
                        }
                    }
                }
            },

            /**
             * Animate in the series. Called internally twice. First with the `init`
             * parameter set to true, which sets up the initial state of the animation.
             * Then when ready, it is called with the `init` parameter undefined, in 
             * order to perform the actual animation. After the second run, the function
             * is removed.
             *
             * @param  {Boolean} init
             *         Initialize the animation.
             */
            animate: function(init) {
                var series = this,
                    chart = series.chart,
                    clipRect,
                    animation = animObject(series.options.animation),
                    sharedClipKey;

                // Initialize the animation. Set up the clipping rectangle.
                if (init) {

                    series.setClip(animation);

                    // Run the animation
                } else {
                    sharedClipKey = this.sharedClipKey;
                    clipRect = chart[sharedClipKey];
                    if (clipRect) {
                        clipRect.animate({
                            width: chart.plotSizeX,
                            x: 0
                        }, animation);
                    }
                    if (chart[sharedClipKey + 'm']) {
                        chart[sharedClipKey + 'm'].animate({
                            width: chart.plotSizeX + 99,
                            x: 0
                        }, animation);
                    }

                    // Delete this function to allow it only once
                    series.animate = null;

                }
            },

            /**
             * This runs after animation to land on the final plot clipping.
             *
             * @private
             */
            afterAnimate: function() {
                this.setClip();
                fireEvent(this, 'afterAnimate');
                this.finishedAnimating = true;
            },

            /**
             * Draw the markers for line-like series types, and columns or other
             * graphical representation for {@link Point} objects for other series
             * types. The resulting element is typically stored as {@link
             * Point.graphic}, and is created on the first call and updated and moved on
             * subsequent calls.
             */
            drawPoints: function() {
                var series = this,
                    points = series.points,
                    chart = series.chart,
                    i,
                    point,
                    symbol,
                    graphic,
                    options = series.options,
                    seriesMarkerOptions = options.marker,
                    pointMarkerOptions,
                    hasPointMarker,
                    enabled,
                    isInside,
                    markerGroup = series[series.specialGroup] || series.markerGroup,
                    xAxis = series.xAxis,
                    markerAttribs,
                    globallyEnabled = pick(
                        seriesMarkerOptions.enabled,
                        xAxis.isRadial ? true : null,
                        // Use larger or equal as radius is null in bubbles (#6321)
                        series.closestPointRangePx >= 2 * seriesMarkerOptions.radius
                    );

                if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) {

                    for (i = 0; i < points.length; i++) {
                        point = points[i];
                        graphic = point.graphic;
                        pointMarkerOptions = point.marker || {};
                        hasPointMarker = !!point.marker;
                        enabled = (globallyEnabled && pointMarkerOptions.enabled === undefined) || pointMarkerOptions.enabled;
                        isInside = point.isInside;

                        // only draw the point if y is defined
                        if (enabled && !point.isNull) {

                            // Shortcuts
                            symbol = pick(pointMarkerOptions.symbol, series.symbol);
                            point.hasImage = symbol.indexOf('url') === 0;

                            markerAttribs = series.markerAttribs(
                                point,
                                point.selected && 'select'
                            );

                            if (graphic) { // update
                                graphic[isInside ? 'show' : 'hide'](true) // Since the marker group isn't clipped, each individual marker must be toggled
                                    .animate(markerAttribs);
                            } else if (isInside && (markerAttribs.width > 0 || point.hasImage)) {

                                /**
                                 * The graphic representation of the point. Typically
                                 * this is a simple shape, like a `rect` for column
                                 * charts or `path` for line markers, but for some 
                                 * complex series types like boxplot or 3D charts, the
                                 * graphic may be a `g` element containing other shapes.
                                 * The graphic is generated the first time {@link
                                 * Series#drawPoints} runs, and updated and moved on
                                 * subsequent runs.
                                 *
                                 * @memberof Point
                                 * @name graphic
                                 * @type {SVGElement}
                                 */
                                point.graphic = graphic = chart.renderer.symbol(
                                        symbol,
                                        markerAttribs.x,
                                        markerAttribs.y,
                                        markerAttribs.width,
                                        markerAttribs.height,
                                        hasPointMarker ? pointMarkerOptions : seriesMarkerOptions
                                    )
                                    .add(markerGroup);
                            }


                            // Presentational attributes
                            if (graphic) {
                                graphic.attr(series.pointAttribs(point, point.selected && 'select'));
                            }


                            if (graphic) {
                                graphic.addClass(point.getClassName(), true);
                            }

                        } else if (graphic) {
                            point.graphic = graphic.destroy(); // #1269
                        }
                    }
                }

            },

            /**
             * Get non-presentational attributes for a point. Used internally for both
             * styled mode and classic. Can be overridden for different series types.
             *
             * @see    Series#pointAttribs
             *
             * @param  {Point} point
             *         The Point to inspect.
             * @param  {String} [state]
             *         The state, can be either `hover`, `select` or undefined.
             *
             * @return {SVGAttributes}
             *         A hash containing those attributes that are not settable from
             *         CSS.
             */
            markerAttribs: function(point, state) {
                var seriesMarkerOptions = this.options.marker,
                    seriesStateOptions,
                    pointMarkerOptions = point.marker || {},
                    pointStateOptions,
                    radius = pick(
                        pointMarkerOptions.radius,
                        seriesMarkerOptions.radius
                    ),
                    attribs;

                // Handle hover and select states
                if (state) {
                    seriesStateOptions = seriesMarkerOptions.states[state];
                    pointStateOptions = pointMarkerOptions.states &&
                        pointMarkerOptions.states[state];

                    radius = pick(
                        pointStateOptions && pointStateOptions.radius,
                        seriesStateOptions && seriesStateOptions.radius,
                        radius + (seriesStateOptions && seriesStateOptions.radiusPlus || 0)
                    );
                }

                if (point.hasImage) {
                    radius = 0; // and subsequently width and height is not set
                }

                attribs = {
                    x: Math.floor(point.plotX) - radius, // Math.floor for #1843
                    y: point.plotY - radius
                };

                if (radius) {
                    attribs.width = attribs.height = 2 * radius;
                }

                return attribs;

            },


            /**
             * Internal function to get presentational attributes for each point. Unlike
             * {@link Series#markerAttribs}, this function should return those
             * attributes that can also be set in CSS. In styled mode, `pointAttribs`
             * won't be called.
             *
             * @param  {Point} point
             *         The point instance to inspect.
             * @param  {String} [state]
             *         The point state, can be either `hover`, `select` or undefined for
             *         normal state.
             *
             * @return {SVGAttributes}
             *         The presentational attributes to be set on the point.
             */
            pointAttribs: function(point, state) {
                var seriesMarkerOptions = this.options.marker,
                    seriesStateOptions,
                    pointOptions = point && point.options,
                    pointMarkerOptions = (pointOptions && pointOptions.marker) || {},
                    pointStateOptions,
                    color = this.color,
                    pointColorOption = pointOptions && pointOptions.color,
                    pointColor = point && point.color,
                    strokeWidth = pick(
                        pointMarkerOptions.lineWidth,
                        seriesMarkerOptions.lineWidth
                    ),
                    zoneColor = point && point.zone && point.zone.color,
                    fill,
                    stroke;

                color = pointColorOption || zoneColor || pointColor || color;
                fill = pointMarkerOptions.fillColor || seriesMarkerOptions.fillColor || color;
                stroke = pointMarkerOptions.lineColor || seriesMarkerOptions.lineColor || color;

                // Handle hover and select states
                if (state) {
                    seriesStateOptions = seriesMarkerOptions.states[state];
                    pointStateOptions = (pointMarkerOptions.states && pointMarkerOptions.states[state]) || {};
                    strokeWidth = pick(
                        pointStateOptions.lineWidth,
                        seriesStateOptions.lineWidth,
                        strokeWidth + pick(
                            pointStateOptions.lineWidthPlus,
                            seriesStateOptions.lineWidthPlus,
                            0
                        )
                    );
                    fill = pointStateOptions.fillColor || seriesStateOptions.fillColor || fill;
                    stroke = pointStateOptions.lineColor || seriesStateOptions.lineColor || stroke;
                }

                return {
                    'stroke': stroke,
                    'stroke-width': strokeWidth,
                    'fill': fill
                };
            },

            /**
             * Clear DOM objects and free up memory.
             *
             * @private
             */
            destroy: function() {
                var series = this,
                    chart = series.chart,
                    issue134 = /AppleWebKit\/533/.test(win.navigator.userAgent),
                    destroy,
                    i,
                    data = series.data || [],
                    point,
                    axis;

                // add event hook
                fireEvent(series, 'destroy');

                // remove all events
                removeEvent(series);

                // erase from axes
                each(series.axisTypes || [], function(AXIS) {
                    axis = series[AXIS];
                    if (axis && axis.series) {
                        erase(axis.series, series);
                        axis.isDirty = axis.forceRedraw = true;
                    }
                });

                // remove legend items
                if (series.legendItem) {
                    series.chart.legend.destroyItem(series);
                }

                // destroy all points with their elements
                i = data.length;
                while (i--) {
                    point = data[i];
                    if (point && point.destroy) {
                        point.destroy();
                    }
                }
                series.points = null;

                // Clear the animation timeout if we are destroying the series during initial animation
                clearTimeout(series.animationTimeout);

                // Destroy all SVGElements associated to the series
                objectEach(series, function(val, prop) {
                    if (val instanceof SVGElement && !val.survive) { // Survive provides a hook for not destroying

                        // issue 134 workaround
                        destroy = issue134 && prop === 'group' ?
                            'hide' :
                            'destroy';

                        val[destroy]();
                    }
                });

                // remove from hoverSeries
                if (chart.hoverSeries === series) {
                    chart.hoverSeries = null;
                }
                erase(chart.series, series);
                chart.orderSeries();

                // clear all members
                objectEach(series, function(val, prop) {
                    delete series[prop];
                });
            },

            /**
             * Get the graph path.
             *
             * @private
             */
            getGraphPath: function(points, nullsAsZeroes, connectCliffs) {
                var series = this,
                    options = series.options,
                    step = options.step,
                    reversed,
                    graphPath = [],
                    xMap = [],
                    gap;

                points = points || series.points;

                // Bottom of a stack is reversed
                reversed = points.reversed;
                if (reversed) {
                    points.reverse();
                }
                // Reverse the steps (#5004)
                step = {
                    right: 1,
                    center: 2
                }[step] || (step && 3);
                if (step && reversed) {
                    step = 4 - step;
                }

                // Remove invalid points, especially in spline (#5015)
                if (options.connectNulls && !nullsAsZeroes && !connectCliffs) {
                    points = this.getValidPoints(points);
                }

                // Build the line
                each(points, function(point, i) {

                    var plotX = point.plotX,
                        plotY = point.plotY,
                        lastPoint = points[i - 1],
                        pathToPoint; // the path to this point from the previous

                    if ((point.leftCliff || (lastPoint && lastPoint.rightCliff)) && !connectCliffs) {
                        gap = true; // ... and continue
                    }

                    // Line series, nullsAsZeroes is not handled
                    if (point.isNull && !defined(nullsAsZeroes) && i > 0) {
                        gap = !options.connectNulls;

                        // Area series, nullsAsZeroes is set
                    } else if (point.isNull && !nullsAsZeroes) {
                        gap = true;

                    } else {

                        if (i === 0 || gap) {
                            pathToPoint = ['M', point.plotX, point.plotY];

                        } else if (series.getPointSpline) { // generate the spline as defined in the SplineSeries object

                            pathToPoint = series.getPointSpline(points, point, i);

                        } else if (step) {

                            if (step === 1) { // right
                                pathToPoint = [
                                    'L',
                                    lastPoint.plotX,
                                    plotY
                                ];

                            } else if (step === 2) { // center
                                pathToPoint = [
                                    'L',
                                    (lastPoint.plotX + plotX) / 2,
                                    lastPoint.plotY,
                                    'L',
                                    (lastPoint.plotX + plotX) / 2,
                                    plotY
                                ];

                            } else {
                                pathToPoint = [
                                    'L',
                                    plotX,
                                    lastPoint.plotY
                                ];
                            }
                            pathToPoint.push('L', plotX, plotY);

                        } else {
                            // normal line to next point
                            pathToPoint = [
                                'L',
                                plotX,
                                plotY
                            ];
                        }

                        // Prepare for animation. When step is enabled, there are two path nodes for each x value.
                        xMap.push(point.x);
                        if (step) {
                            xMap.push(point.x);
                        }

                        graphPath.push.apply(graphPath, pathToPoint);
                        gap = false;
                    }
                });

                graphPath.xMap = xMap;
                series.graphPath = graphPath;

                return graphPath;

            },

            /**
             * Draw the graph. Called internally when rendering line-like series types.
             * The first time it generates the `series.graph` item and optionally other
             * series-wide items like `series.area` for area charts. On subsequent calls
             * these items are updated with new positions and attributes.
             */
            drawGraph: function() {
                var series = this,
                    options = this.options,
                    graphPath = (this.gappedPath || this.getGraphPath).call(this),
                    props = [
                        [
                            'graph',
                            'highcharts-graph',

                            options.lineColor || this.color,
                            options.dashStyle

                        ]
                    ];

                // Add the zone properties if any
                each(this.zones, function(zone, i) {
                    props.push([
                        'zone-graph-' + i,
                        'highcharts-graph highcharts-zone-graph-' + i + ' ' + (zone.className || ''),

                        zone.color || series.color,
                        zone.dashStyle || options.dashStyle

                    ]);
                });

                // Draw the graph
                each(props, function(prop, i) {
                    var graphKey = prop[0],
                        graph = series[graphKey],
                        attribs;

                    if (graph) {
                        graph.endX = series.preventGraphAnimation ?
                            null :
                            graphPath.xMap;
                        graph.animate({
                            d: graphPath
                        });

                    } else if (graphPath.length) { // #1487

                        series[graphKey] = series.chart.renderer.path(graphPath)
                            .addClass(prop[1])
                            .attr({
                                zIndex: 1
                            }) // #1069
                            .add(series.group);


                        attribs = {
                            'stroke': prop[2],
                            'stroke-width': options.lineWidth,
                            'fill': (series.fillGraph && series.color) || 'none' // Polygon series use filled graph
                        };

                        if (prop[3]) {
                            attribs.dashstyle = prop[3];
                        } else if (options.linecap !== 'square') {
                            attribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round';
                        }

                        graph = series[graphKey]
                            .attr(attribs)
                            .shadow((i < 2) && options.shadow); // add shadow to normal series (0) or to first zone (1) #3932

                    }

                    // Helpers for animation
                    if (graph) {
                        graph.startX = graphPath.xMap;
                        graph.isArea = graphPath.isArea; // For arearange animation
                    }
                });
            },

            /**
             * Clip the graphs into zones for colors and styling.
             *
             * @private
             */
            applyZones: function() {
                var series = this,
                    chart = this.chart,
                    renderer = chart.renderer,
                    zones = this.zones,
                    translatedFrom,
                    translatedTo,
                    clips = this.clips || [],
                    clipAttr,
                    graph = this.graph,
                    area = this.area,
                    chartSizeMax = Math.max(chart.chartWidth, chart.chartHeight),
                    axis = this[(this.zoneAxis || 'y') + 'Axis'],
                    extremes,
                    reversed,
                    inverted = chart.inverted,
                    horiz,
                    pxRange,
                    pxPosMin,
                    pxPosMax,
                    ignoreZones = false;

                if (zones.length && (graph || area) && axis && axis.min !== undefined) {
                    reversed = axis.reversed;
                    horiz = axis.horiz;
                    // The use of the Color Threshold assumes there are no gaps
                    // so it is safe to hide the original graph and area
                    if (graph) {
                        graph.hide();
                    }
                    if (area) {
                        area.hide();
                    }

                    // Create the clips
                    extremes = axis.getExtremes();
                    each(zones, function(threshold, i) {

                        translatedFrom = reversed ?
                            (horiz ? chart.plotWidth : 0) :
                            (horiz ? 0 : axis.toPixels(extremes.min));
                        translatedFrom = Math.min(Math.max(pick(translatedTo, translatedFrom), 0), chartSizeMax);
                        translatedTo = Math.min(Math.max(Math.round(axis.toPixels(pick(threshold.value, extremes.max), true)), 0), chartSizeMax);

                        if (ignoreZones) {
                            translatedFrom = translatedTo = axis.toPixels(extremes.max);
                        }

                        pxRange = Math.abs(translatedFrom - translatedTo);
                        pxPosMin = Math.min(translatedFrom, translatedTo);
                        pxPosMax = Math.max(translatedFrom, translatedTo);
                        if (axis.isXAxis) {
                            clipAttr = {
                                x: inverted ? pxPosMax : pxPosMin,
                                y: 0,
                                width: pxRange,
                                height: chartSizeMax
                            };
                            if (!horiz) {
                                clipAttr.x = chart.plotHeight - clipAttr.x;
                            }
                        } else {
                            clipAttr = {
                                x: 0,
                                y: inverted ? pxPosMax : pxPosMin,
                                width: chartSizeMax,
                                height: pxRange
                            };
                            if (horiz) {
                                clipAttr.y = chart.plotWidth - clipAttr.y;
                            }
                        }


                        // VML SUPPPORT
                        if (inverted && renderer.isVML) {
                            if (axis.isXAxis) {
                                clipAttr = {
                                    x: 0,
                                    y: reversed ? pxPosMin : pxPosMax,
                                    height: clipAttr.width,
                                    width: chart.chartWidth
                                };
                            } else {
                                clipAttr = {
                                    x: clipAttr.y - chart.plotLeft - chart.spacingBox.x,
                                    y: 0,
                                    width: clipAttr.height,
                                    height: chart.chartHeight
                                };
                            }
                        }
                        // END OF VML SUPPORT


                        if (clips[i]) {
                            clips[i].animate(clipAttr);
                        } else {
                            clips[i] = renderer.clipRect(clipAttr);

                            if (graph) {
                                series['zone-graph-' + i].clip(clips[i]);
                            }

                            if (area) {
                                series['zone-area-' + i].clip(clips[i]);
                            }
                        }
                        // if this zone extends out of the axis, ignore the others
                        ignoreZones = threshold.value > extremes.max;
                    });
                    this.clips = clips;
                }
            },

            /**
             * Initialize and perform group inversion on series.group and
             * series.markerGroup.
             *
             * @private
             */
            invertGroups: function(inverted) {
                var series = this,
                    chart = series.chart,
                    remover;

                function setInvert() {
                    each(['group', 'markerGroup'], function(groupName) {
                        if (series[groupName]) {

                            // VML/HTML needs explicit attributes for flipping
                            if (chart.renderer.isVML) {
                                series[groupName].attr({
                                    width: series.yAxis.len,
                                    height: series.xAxis.len
                                });
                            }

                            series[groupName].width = series.yAxis.len;
                            series[groupName].height = series.xAxis.len;
                            series[groupName].invert(inverted);
                        }
                    });
                }

                // Pie, go away (#1736)
                if (!series.xAxis) {
                    return;
                }

                // A fixed size is needed for inversion to work
                remover = addEvent(chart, 'resize', setInvert);
                addEvent(series, 'destroy', remover);

                // Do it now
                setInvert(inverted); // do it now

                // On subsequent render and redraw, just do setInvert without setting up events again
                series.invertGroups = setInvert;
            },

            /**
             * General abstraction for creating plot groups like series.group,
             * series.dataLabelsGroup and series.markerGroup. On subsequent calls, the
             * group will only be adjusted to the updated plot size.
             *
             * @private
             */
            plotGroup: function(prop, name, visibility, zIndex, parent) {
                var group = this[prop],
                    isNew = !group;

                // Generate it on first call
                if (isNew) {
                    this[prop] = group = this.chart.renderer.g()
                        .attr({
                            zIndex: zIndex || 0.1 // IE8 and pointer logic use this
                        })
                        .add(parent);

                }

                // Add the class names, and replace existing ones as response to
                // Series.update (#6660)
                group.addClass(
                    (
                        'highcharts-' + name +
                        ' highcharts-series-' + this.index +
                        ' highcharts-' + this.type + '-series ' +
                        (
                            defined(this.colorIndex) ?
                            'highcharts-color-' + this.colorIndex + ' ' :
                            ''
                        ) +
                        (this.options.className || '') +
                        (group.hasClass('highcharts-tracker') ? ' highcharts-tracker' : '')
                    ),
                    true
                );

                // Place it on first and subsequent (redraw) calls
                group.attr({
                    visibility: visibility
                })[isNew ? 'attr' : 'animate'](
                    this.getPlotBox()
                );
                return group;
            },

            /**
             * Get the translation and scale for the plot area of this series.
             */
            getPlotBox: function() {
                var chart = this.chart,
                    xAxis = this.xAxis,
                    yAxis = this.yAxis;

                // Swap axes for inverted (#2339)
                if (chart.inverted) {
                    xAxis = yAxis;
                    yAxis = this.xAxis;
                }
                return {
                    translateX: xAxis ? xAxis.left : chart.plotLeft,
                    translateY: yAxis ? yAxis.top : chart.plotTop,
                    scaleX: 1, // #1623
                    scaleY: 1
                };
            },

            /**
             * Render the graph and markers. Called internally when first rendering and
             * later when redrawing the chart. This function can be extended in plugins,
             * but normally shouldn't be called directly.
             */
            render: function() {
                var series = this,
                    chart = series.chart,
                    group,
                    options = series.options,
                    // Animation doesn't work in IE8 quirks when the group div is
                    // hidden, and looks bad in other oldIE
                    animDuration = (!!series.animate &&
                        chart.renderer.isSVG &&
                        animObject(options.animation).duration
                    ),
                    visibility = series.visible ? 'inherit' : 'hidden', // #2597
                    zIndex = options.zIndex,
                    hasRendered = series.hasRendered,
                    chartSeriesGroup = chart.seriesGroup,
                    inverted = chart.inverted;

                // the group
                group = series.plotGroup(
                    'group',
                    'series',
                    visibility,
                    zIndex,
                    chartSeriesGroup
                );

                series.markerGroup = series.plotGroup(
                    'markerGroup',
                    'markers',
                    visibility,
                    zIndex,
                    chartSeriesGroup
                );

                // initiate the animation
                if (animDuration) {
                    series.animate(true);
                }

                // SVGRenderer needs to know this before drawing elements (#1089, #1795)
                group.inverted = series.isCartesian ? inverted : false;

                // draw the graph if any
                if (series.drawGraph) {
                    series.drawGraph();
                    series.applyZones();
                }

                /*		each(series.points, function (point) {
                			if (point.redraw) {
                				point.redraw();
                			}
                		});*/

                // draw the data labels (inn pies they go before the points)
                if (series.drawDataLabels) {
                    series.drawDataLabels();
                }

                // draw the points
                if (series.visible) {
                    series.drawPoints();
                }


                // draw the mouse tracking area
                if (
                    series.drawTracker &&
                    series.options.enableMouseTracking !== false
                ) {
                    series.drawTracker();
                }

                // Handle inverted series and tracker groups
                series.invertGroups(inverted);

                // Initial clipping, must be defined after inverting groups for VML.
                // Applies to columns etc. (#3839).
                if (options.clip !== false && !series.sharedClipKey && !hasRendered) {
                    group.clip(chart.clipRect);
                }

                // Run the animation
                if (animDuration) {
                    series.animate();
                }

                // Call the afterAnimate function on animation complete (but don't
                // overwrite the animation.complete option which should be available to
                // the user).
                if (!hasRendered) {
                    series.animationTimeout = syncTimeout(function() {
                        series.afterAnimate();
                    }, animDuration);
                }

                series.isDirty = false; // means data is in accordance with what you see
                // (See #322) series.isDirty = series.isDirtyData = false; // means
                // data is in accordance with what you see
                series.hasRendered = true;
            },

            /**
             * Redraw the series. This function is called internally from `chart.redraw`
             * and normally shouldn't be called directly.
             *
             * @private
             */
            redraw: function() {
                var series = this,
                    chart = series.chart,
                    // cache it here as it is set to false in render, but used after
                    wasDirty = series.isDirty || series.isDirtyData,
                    group = series.group,
                    xAxis = series.xAxis,
                    yAxis = series.yAxis;

                // reposition on resize
                if (group) {
                    if (chart.inverted) {
                        group.attr({
                            width: chart.plotWidth,
                            height: chart.plotHeight
                        });
                    }

                    group.animate({
                        translateX: pick(xAxis && xAxis.left, chart.plotLeft),
                        translateY: pick(yAxis && yAxis.top, chart.plotTop)
                    });
                }

                series.translate();
                series.render();
                if (wasDirty) { // #3868, #3945
                    delete this.kdTree;
                }
            },

            kdAxisArray: ['clientX', 'plotY'],

            searchPoint: function(e, compareX) {
                var series = this,
                    xAxis = series.xAxis,
                    yAxis = series.yAxis,
                    inverted = series.chart.inverted;

                return this.searchKDTree({
                    clientX: inverted ?
                        xAxis.len - e.chartY + xAxis.pos : e.chartX - xAxis.pos,
                    plotY: inverted ?
                        yAxis.len - e.chartX + yAxis.pos : e.chartY - yAxis.pos
                }, compareX);
            },

            /**
             * Build the k-d-tree that is used by mouse and touch interaction to get the
             * closest point. Line-like series typically have a one-dimensional tree
             * where points are searched along the X axis, while scatter-like series
             * typically search in two dimensions, X and Y.
             *
             * @private
             */
            buildKDTree: function() {

                // Prevent multiple k-d-trees from being built simultaneously (#6235)
                this.buildingKdTree = true;

                var series = this,
                    dimensions = series.options.findNearestPointBy.indexOf('y') > -1 ?
                    2 : 1;

                // Internal function
                function _kdtree(points, depth, dimensions) {
                    var axis,
                        median,
                        length = points && points.length;

                    if (length) {

                        // alternate between the axis
                        axis = series.kdAxisArray[depth % dimensions];

                        // sort point array
                        points.sort(function(a, b) {
                            return a[axis] - b[axis];
                        });

                        median = Math.floor(length / 2);

                        // build and return nod
                        return {
                            point: points[median],
                            left: _kdtree(
                                points.slice(0, median), depth + 1, dimensions
                            ),
                            right: _kdtree(
                                points.slice(median + 1), depth + 1, dimensions
                            )
                        };

                    }
                }

                // Start the recursive build process with a clone of the points array
                // and null points filtered out (#3873)
                function startRecursive() {
                    series.kdTree = _kdtree(
                        series.getValidPoints(
                            null,
                            // For line-type series restrict to plot area, but
                            // column-type series not (#3916, #4511)
                            !series.directTouch
                        ),
                        dimensions,
                        dimensions
                    );
                    series.buildingKdTree = false;
                }
                delete series.kdTree;

                // For testing tooltips, don't build async
                syncTimeout(startRecursive, series.options.kdNow ? 0 : 1);
            },

            searchKDTree: function(point, compareX) {
                var series = this,
                    kdX = this.kdAxisArray[0],
                    kdY = this.kdAxisArray[1],
                    kdComparer = compareX ? 'distX' : 'dist',
                    kdDimensions = series.options.findNearestPointBy.indexOf('y') > -1 ?
                    2 : 1;

                // Set the one and two dimensional distance on the point object
                function setDistance(p1, p2) {
                    var x = (defined(p1[kdX]) && defined(p2[kdX])) ?
                        Math.pow(p1[kdX] - p2[kdX], 2) :
                        null,
                        y = (defined(p1[kdY]) && defined(p2[kdY])) ?
                        Math.pow(p1[kdY] - p2[kdY], 2) :
                        null,
                        r = (x || 0) + (y || 0);

                    p2.dist = defined(r) ? Math.sqrt(r) : Number.MAX_VALUE;
                    p2.distX = defined(x) ? Math.sqrt(x) : Number.MAX_VALUE;
                }

                function _search(search, tree, depth, dimensions) {
                    var point = tree.point,
                        axis = series.kdAxisArray[depth % dimensions],
                        tdist,
                        sideA,
                        sideB,
                        ret = point,
                        nPoint1,
                        nPoint2;

                    setDistance(search, point);

                    // Pick side based on distance to splitting point
                    tdist = search[axis] - point[axis];
                    sideA = tdist < 0 ? 'left' : 'right';
                    sideB = tdist < 0 ? 'right' : 'left';

                    // End of tree
                    if (tree[sideA]) {
                        nPoint1 = _search(search, tree[sideA], depth + 1, dimensions);

                        ret = (nPoint1[kdComparer] < ret[kdComparer] ? nPoint1 : point);
                    }
                    if (tree[sideB]) {
                        // compare distance to current best to splitting point to decide
                        // wether to check side B or not
                        if (Math.sqrt(tdist * tdist) < ret[kdComparer]) {
                            nPoint2 = _search(
                                search,
                                tree[sideB],
                                depth + 1,
                                dimensions
                            );
                            ret = nPoint2[kdComparer] < ret[kdComparer] ?
                                nPoint2 :
                                ret;
                        }
                    }

                    return ret;
                }

                if (!this.kdTree && !this.buildingKdTree) {
                    this.buildKDTree();
                }

                if (this.kdTree) {
                    return _search(point, this.kdTree, kdDimensions, kdDimensions);
                }
            }

        }); // end Series prototype

        /**
         * A line series displays information as a series of data points connected by
         * straight line segments.
         *
         * @sample {highcharts} highcharts/demo/line-basic/ Line chart
         * @sample {highstock} stock/demo/basic-line/ Line chart
         * 
         * @extends plotOptions.series
         * @product highcharts highstock
         * @apioption plotOptions.line
         */

        /**
         * A `line` series. If the [type](#series.line.type) option is not
         * specified, it is inherited from [chart.type](#chart.type).
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * line](#plotOptions.line).
         * 
         * @type {Object}
         * @extends series,plotOptions.line
         * @excluding dataParser,dataURL
         * @product highcharts highstock
         * @apioption series.line
         */

        /**
         * An array of data points for the series. For the `line` series type,
         * points can be given in the following ways:
         * 
         * 1.  An array of numerical values. In this case, the numerical values
         * will be interpreted as `y` options. The `x` values will be automatically
         * calculated, either starting at 0 and incremented by 1, or from `pointStart`
         * and `pointInterval` given in the series options. If the axis has
         * categories, these will be used. Example:
         * 
         *  ```js
         *  data: [0, 5, 3, 5]
         *  ```
         * 
         * 2.  An array of arrays with 2 values. In this case, the values correspond
         * to `x,y`. If the first value is a string, it is applied as the name
         * of the point, and the `x` value is inferred.
         * 
         *  ```js
         *     data: [
         *         [0, 1],
         *         [1, 2],
         *         [2, 8]
         *     ]
         *  ```
         * 
         * 3.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.line.turboThreshold),
         * this option is not available.
         * 
         *  ```js
         *     data: [{
         *         x: 1,
         *         y: 9,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         y: 6,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         * 
         * @type {Array<Object|Array|Number>}
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
         * @apioption series.line.data
         */

        /**
         * An additional, individual class name for the data point's graphic
         * representation.
         * 
         * @type {String}
         * @since 5.0.0
         * @product highcharts
         * @apioption series.line.data.className
         */

        /**
         * Individual color for the point. By default the color is pulled from
         * the global `colors` array.
         *
         * In styled mode, the `color` option doesn't take effect. Instead, use 
         * `colorIndex`.
         * 
         * @type {Color}
         * @sample {highcharts} highcharts/point/color/ Mark the highest point
         * @default undefined
         * @product highcharts highstock
         * @apioption series.line.data.color
         */

        /**
         * Styled mode only. A specific color index to use for the point, so its
         * graphic representations are given the class name
         * `highcharts-color-{n}`.
         * 
         * @type {Number}
         * @since 5.0.0
         * @product highcharts
         * @apioption series.line.data.colorIndex
         */

        /**
         * Individual data label for each point. The options are the same as
         * the ones for [plotOptions.series.dataLabels](#plotOptions.series.
         * dataLabels)
         * 
         * @type {Object}
         * @sample {highcharts} highcharts/point/datalabels/ Show a label for the last value
         * @sample {highstock} highcharts/point/datalabels/ Show a label for the last value
         * @product highcharts highstock
         * @apioption series.line.data.dataLabels
         */

        /**
         * A description of the point to add to the screen reader information
         * about the point. Requires the Accessibility module.
         * 
         * @type {String}
         * @default undefined
         * @since 5.0.0
         * @apioption series.line.data.description
         */

        /**
         * An id for the point. This can be used after render time to get a
         * pointer to the point object through `chart.get()`.
         * 
         * @type {String}
         * @sample {highcharts} highcharts/point/id/ Remove an id'd point
         * @default null
         * @since 1.2.0
         * @product highcharts highstock
         * @apioption series.line.data.id
         */

        /**
         * The rank for this point's data label in case of collision. If two
         * data labels are about to overlap, only the one with the highest `labelrank`
         * will be drawn.
         * 
         * @type {Number}
         * @apioption series.line.data.labelrank
         */

        /**
         * The name of the point as shown in the legend, tooltip, dataLabel
         * etc.
         * 
         * @type {String}
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Point names
         * @see [xAxis.uniqueNames](#xAxis.uniqueNames)
         * @apioption series.line.data.name
         */

        /**
         * Whether the data point is selected initially.
         * 
         * @type {Boolean}
         * @default false
         * @product highcharts highstock
         * @apioption series.line.data.selected
         */

        /**
         * The x value of the point. For datetime axes, the X value is the timestamp
         * in milliseconds since 1970.
         * 
         * @type {Number}
         * @product highcharts highstock
         * @apioption series.line.data.x
         */

        /**
         * The y value of the point.
         * 
         * @type {Number}
         * @default null
         * @product highcharts highstock
         * @apioption series.line.data.y
         */

        /**
         * Individual point events
         * 
         * @extends plotOptions.series.point.events
         * @product highcharts highstock
         * @apioption series.line.data.events
         */

        /**
         * @extends plotOptions.series.marker
         * @product highcharts highstock
         * @apioption series.line.data.marker
         */

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var Axis = H.Axis,
            Chart = H.Chart,
            correctFloat = H.correctFloat,
            defined = H.defined,
            destroyObjectProperties = H.destroyObjectProperties,
            each = H.each,
            format = H.format,
            objectEach = H.objectEach,
            pick = H.pick,
            Series = H.Series;

        /**
         * The class for stacks. Each stack, on a specific X value and either negative
         * or positive, has its own stack item.
         *
         * @class
         */
        H.StackItem = function(axis, options, isNegative, x, stackOption) {

            var inverted = axis.chart.inverted;

            this.axis = axis;

            // Tells if the stack is negative
            this.isNegative = isNegative;

            // Save the options to be able to style the label
            this.options = options;

            // Save the x value to be able to position the label later
            this.x = x;

            // Initialize total value
            this.total = null;

            // This will keep each points' extremes stored by series.index and point 
            // index
            this.points = {};

            // Save the stack option on the series configuration object, and whether to 
            // treat it as percent
            this.stack = stackOption;
            this.leftCliff = 0;
            this.rightCliff = 0;

            // The align options and text align varies on whether the stack is negative 
            // and if the chart is inverted or not.
            // First test the user supplied value, then use the dynamic.
            this.alignOptions = {
                align: options.align ||
                    (inverted ? (isNegative ? 'left' : 'right') : 'center'),
                verticalAlign: options.verticalAlign ||
                    (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),
                y: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),
                x: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)
            };

            this.textAlign = options.textAlign ||
                (inverted ? (isNegative ? 'right' : 'left') : 'center');
        };

        H.StackItem.prototype = {
            destroy: function() {
                destroyObjectProperties(this, this.axis);
            },

            /**
             * Renders the stack total label and adds it to the stack label group.
             */
            render: function(group) {
                var options = this.options,
                    formatOption = options.format,
                    str = formatOption ?
                    format(formatOption, this) :
                    options.formatter.call(this); // format the text in the label

                // Change the text to reflect the new total and set visibility to hidden
                // in case the serie is hidden
                if (this.label) {
                    this.label.attr({
                        text: str,
                        visibility: 'hidden'
                    });
                    // Create new label
                } else {
                    this.label =
                        this.axis.chart.renderer.text(str, null, null, options.useHTML)
                        .css(options.style)
                        .attr({
                            align: this.textAlign,
                            rotation: options.rotation,
                            visibility: 'hidden' // hidden until setOffset is called
                        })
                        .add(group); // add to the labels-group
                }
            },

            /**
             * Sets the offset that the stack has from the x value and repositions the
             * label.
             */
            setOffset: function(xOffset, xWidth) {
                var stackItem = this,
                    axis = stackItem.axis,
                    chart = axis.chart,
                    // stack value translated mapped to chart coordinates
                    y = axis.translate(
                        axis.usePercentage ? 100 : stackItem.total,
                        0,
                        0,
                        0,
                        1
                    ),
                    yZero = axis.translate(0), // stack origin
                    h = Math.abs(y - yZero), // stack height
                    x = chart.xAxis[0].translate(stackItem.x) + xOffset, // x position
                    stackBox = stackItem.getStackBox(chart, stackItem, x, y, xWidth, h),
                    label = stackItem.label,
                    alignAttr;

                if (label) {
                    // Align the label to the box
                    label.align(stackItem.alignOptions, null, stackBox);

                    // Set visibility (#678)
                    alignAttr = label.alignAttr;
                    label[
                        stackItem.options.crop === false || chart.isInsidePlot(
                            alignAttr.x,
                            alignAttr.y
                        ) ? 'show' : 'hide'](true);
                }
            },
            getStackBox: function(chart, stackItem, x, y, xWidth, h) {
                var reversed = stackItem.axis.reversed,
                    inverted = chart.inverted,
                    plotHeight = chart.plotHeight,
                    neg = (stackItem.isNegative && !reversed) ||
                    (!stackItem.isNegative && reversed); // #4056

                return { // this is the box for the complete stack
                    x: inverted ? (neg ? y : y - h) : x,
                    y: inverted ?
                        plotHeight - x - xWidth :
                        (neg ?
                            (plotHeight - y - h) :
                            plotHeight - y
                        ),
                    width: inverted ? h : xWidth,
                    height: inverted ? xWidth : h
                };
            }
        };

        /**
         * Generate stacks for each series and calculate stacks total values
         */
        Chart.prototype.getStacks = function() {
            var chart = this;

            // reset stacks for each yAxis
            each(chart.yAxis, function(axis) {
                if (axis.stacks && axis.hasVisibleSeries) {
                    axis.oldStacks = axis.stacks;
                }
            });

            each(chart.series, function(series) {
                if (series.options.stacking && (series.visible === true ||
                        chart.options.chart.ignoreHiddenSeries === false)) {
                    series.stackKey = series.type + pick(series.options.stack, '');
                }
            });
        };


        // Stacking methods defined on the Axis prototype

        /**
         * Build the stacks from top down
         */
        Axis.prototype.buildStacks = function() {
            var axisSeries = this.series,
                reversedStacks = pick(this.options.reversedStacks, true),
                len = axisSeries.length,
                i;
            if (!this.isXAxis) {
                this.usePercentage = false;
                i = len;
                while (i--) {
                    axisSeries[reversedStacks ? i : len - i - 1].setStackedPoints();
                }

                // Loop up again to compute percent and stream stack
                for (i = 0; i < len; i++) {
                    axisSeries[i].modifyStacks();
                }
            }
        };

        Axis.prototype.renderStackTotals = function() {
            var axis = this,
                chart = axis.chart,
                renderer = chart.renderer,
                stacks = axis.stacks,
                stackTotalGroup = axis.stackTotalGroup;

            // Create a separate group for the stack total labels
            if (!stackTotalGroup) {
                axis.stackTotalGroup = stackTotalGroup =
                    renderer.g('stack-labels')
                    .attr({
                        visibility: 'visible',
                        zIndex: 6
                    })
                    .add();
            }

            // plotLeft/Top will change when y axis gets wider so we need to translate
            // the stackTotalGroup at every render call. See bug #506 and #516
            stackTotalGroup.translate(chart.plotLeft, chart.plotTop);

            // Render each stack total
            objectEach(stacks, function(type) {
                objectEach(type, function(stack) {
                    stack.render(stackTotalGroup);
                });
            });
        };

        /**
         * Set all the stacks to initial states and destroy unused ones.
         */
        Axis.prototype.resetStacks = function() {
            var axis = this,
                stacks = axis.stacks;
            if (!axis.isXAxis) {
                objectEach(stacks, function(type) {
                    objectEach(type, function(stack, key) {
                        // Clean up memory after point deletion (#1044, #4320)
                        if (stack.touched < axis.stacksTouched) {
                            stack.destroy();
                            delete type[key];

                            // Reset stacks
                        } else {
                            stack.total = null;
                            stack.cum = null;
                        }
                    });
                });
            }
        };

        Axis.prototype.cleanStacks = function() {
            var stacks;

            if (!this.isXAxis) {
                if (this.oldStacks) {
                    stacks = this.stacks = this.oldStacks;
                }

                // reset stacks
                objectEach(stacks, function(type) {
                    objectEach(type, function(stack) {
                        stack.cum = stack.total;
                    });
                });
            }
        };


        // Stacking methods defnied for Series prototype

        /**
         * Adds series' points value to corresponding stack
         */
        Series.prototype.setStackedPoints = function() {
            if (!this.options.stacking || (this.visible !== true &&
                    this.chart.options.chart.ignoreHiddenSeries !== false)) {
                return;
            }

            var series = this,
                xData = series.processedXData,
                yData = series.processedYData,
                stackedYData = [],
                yDataLength = yData.length,
                seriesOptions = series.options,
                threshold = seriesOptions.threshold,
                stackThreshold = seriesOptions.startFromThreshold ? threshold : 0,
                stackOption = seriesOptions.stack,
                stacking = seriesOptions.stacking,
                stackKey = series.stackKey,
                negKey = '-' + stackKey,
                negStacks = series.negStacks,
                yAxis = series.yAxis,
                stacks = yAxis.stacks,
                oldStacks = yAxis.oldStacks,
                stackIndicator,
                isNegative,
                stack,
                other,
                key,
                pointKey,
                i,
                x,
                y;


            yAxis.stacksTouched += 1;

            // loop over the non-null y values and read them into a local array
            for (i = 0; i < yDataLength; i++) {
                x = xData[i];
                y = yData[i];
                stackIndicator = series.getStackIndicator(
                    stackIndicator,
                    x,
                    series.index
                );
                pointKey = stackIndicator.key;
                // Read stacked values into a stack based on the x value,
                // the sign of y and the stack key. Stacking is also handled for null
                // values (#739)
                isNegative = negStacks && y < (stackThreshold ? 0 : threshold);
                key = isNegative ? negKey : stackKey;

                // Create empty object for this stack if it doesn't exist yet
                if (!stacks[key]) {
                    stacks[key] = {};
                }

                // Initialize StackItem for this x
                if (!stacks[key][x]) {
                    if (oldStacks[key] && oldStacks[key][x]) {
                        stacks[key][x] = oldStacks[key][x];
                        stacks[key][x].total = null;
                    } else {
                        stacks[key][x] = new H.StackItem(
                            yAxis,
                            yAxis.options.stackLabels,
                            isNegative,
                            x,
                            stackOption
                        );
                    }
                }

                // If the StackItem doesn't exist, create it first
                stack = stacks[key][x];
                if (y !== null) {
                    stack.points[pointKey] = stack.points[series.index] = [pick(stack.cum, stackThreshold)];

                    // Record the base of the stack
                    if (!defined(stack.cum)) {
                        stack.base = pointKey;
                    }
                    stack.touched = yAxis.stacksTouched;


                    // In area charts, if there are multiple points on the same X value,
                    // let the area fill the full span of those points
                    if (stackIndicator.index > 0 && series.singleStacks === false) {
                        stack.points[pointKey][0] =
                            stack.points[series.index + ',' + x + ',0'][0];
                    }
                }

                // Add value to the stack total
                if (stacking === 'percent') {

                    // Percent stacked column, totals are the same for the positive and
                    // negative stacks
                    other = isNegative ? stackKey : negKey;
                    if (negStacks && stacks[other] && stacks[other][x]) {
                        other = stacks[other][x];
                        stack.total = other.total =
                            Math.max(other.total, stack.total) + Math.abs(y) || 0;

                        // Percent stacked areas
                    } else {
                        stack.total = correctFloat(stack.total + (Math.abs(y) || 0));
                    }
                } else {
                    stack.total = correctFloat(stack.total + (y || 0));
                }

                stack.cum = pick(stack.cum, stackThreshold) + (y || 0);

                if (y !== null) {
                    stack.points[pointKey].push(stack.cum);
                    stackedYData[i] = stack.cum;
                }

            }

            if (stacking === 'percent') {
                yAxis.usePercentage = true;
            }

            this.stackedYData = stackedYData; // To be used in getExtremes

            // Reset old stacks
            yAxis.oldStacks = {};
        };

        /**
         * Iterate over all stacks and compute the absolute values to percent
         */
        Series.prototype.modifyStacks = function() {
            var series = this,
                stackKey = series.stackKey,
                stacks = series.yAxis.stacks,
                processedXData = series.processedXData,
                stackIndicator,
                stacking = series.options.stacking;

            if (series[stacking + 'Stacker']) { // Modifier function exists
                each([stackKey, '-' + stackKey], function(key) {
                    var i = processedXData.length,
                        x,
                        stack,
                        pointExtremes;

                    while (i--) {
                        x = processedXData[i];
                        stackIndicator = series.getStackIndicator(
                            stackIndicator,
                            x,
                            series.index,
                            key
                        );
                        stack = stacks[key] && stacks[key][x];
                        pointExtremes = stack && stack.points[stackIndicator.key];
                        if (pointExtremes) {
                            series[stacking + 'Stacker'](pointExtremes, stack, i);
                        }
                    }
                });
            }
        };

        /**
         * Modifier function for percent stacks. Blows up the stack to 100%.
         */
        Series.prototype.percentStacker = function(pointExtremes, stack, i) {
            var totalFactor = stack.total ? 100 / stack.total : 0;
            // Y bottom value
            pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor);
            // Y value
            pointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor);
            this.stackedYData[i] = pointExtremes[1];
        };

        /**
         * Get stack indicator, according to it's x-value, to determine points with the
         * same x-value
         */
        Series.prototype.getStackIndicator = function(stackIndicator, x, index, key) {
            // Update stack indicator, when:
            // first point in a stack || x changed || stack type (negative vs positive)
            // changed:
            if (!defined(stackIndicator) || stackIndicator.x !== x ||
                (key && stackIndicator.key !== key)) {
                stackIndicator = {
                    x: x,
                    index: 0,
                    key: key
                };
            } else {
                stackIndicator.index++;
            }

            stackIndicator.key = [index, x, stackIndicator.index].join(',');

            return stackIndicator;
        };

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var addEvent = H.addEvent,
            animate = H.animate,
            Axis = H.Axis,
            Chart = H.Chart,
            createElement = H.createElement,
            css = H.css,
            defined = H.defined,
            each = H.each,
            erase = H.erase,
            extend = H.extend,
            fireEvent = H.fireEvent,
            inArray = H.inArray,
            isNumber = H.isNumber,
            isObject = H.isObject,
            isArray = H.isArray,
            merge = H.merge,
            objectEach = H.objectEach,
            pick = H.pick,
            Point = H.Point,
            Series = H.Series,
            seriesTypes = H.seriesTypes,
            setAnimation = H.setAnimation,
            splat = H.splat;

        // Extend the Chart prototype for dynamic methods
        extend(Chart.prototype, /** @lends Highcharts.Chart.prototype */ {

            /**
             * Add a series to the chart after render time. Note that this method should
             * never be used when adding data synchronously at chart render time, as it
             * adds expense to the calculations and rendering. When adding data at the
             * same time as the chart is initialized, add the series as a configuration
             * option instead. With multiple axes, the `offset` is dynamically adjusted.
             *
             * @param  {SeriesOptions} options
             *         The config options for the series.
             * @param  {Boolean} [redraw=true]
             *         Whether to redraw the chart after adding.
             * @param  {AnimationOptions} animation
             *         Whether to apply animation, and optionally animation
             *         configuration.
             *
             * @return {Highcharts.Series}
             *         The newly created series object.
             *
             * @sample highcharts/members/chart-addseries/
             *         Add a series from a button
             * @sample stock/members/chart-addseries/
             *         Add a series in Highstock
             */
            addSeries: function(options, redraw, animation) {
                var series,
                    chart = this;

                if (options) {
                    redraw = pick(redraw, true); // defaults to true

                    fireEvent(chart, 'addSeries', {
                        options: options
                    }, function() {
                        series = chart.initSeries(options);

                        chart.isDirtyLegend = true; // the series array is out of sync with the display
                        chart.linkSeries();
                        if (redraw) {
                            chart.redraw(animation);
                        }
                    });
                }

                return series;
            },

            /**
             * Add an axis to the chart after render time. Note that this method should
             * never be used when adding data synchronously at chart render time, as it
             * adds expense to the calculations and rendering. When adding data at the
             * same time as the chart is initialized, add the axis as a configuration
             * option instead.
             * @param  {AxisOptions} options
             *         The axis options.
             * @param  {Boolean} [isX=false]
             *         Whether it is an X axis or a value axis.
             * @param  {Boolean} [redraw=true]
             *         Whether to redraw the chart after adding.
             * @param  {AnimationOptions} [animation=true]
             *         Whether and how to apply animation in the redraw.
             *
             * @sample highcharts/members/chart-addaxis/ Add and remove axes
             *
             * @return {Axis}
             *         The newly generated Axis object.
             */
            addAxis: function(options, isX, redraw, animation) {
                var key = isX ? 'xAxis' : 'yAxis',
                    chartOptions = this.options,
                    userOptions = merge(options, {
                        index: this[key].length,
                        isX: isX
                    }),
                    axis;

                axis = new Axis(this, userOptions);

                // Push the new axis options to the chart options
                chartOptions[key] = splat(chartOptions[key] || {});
                chartOptions[key].push(userOptions);

                if (pick(redraw, true)) {
                    this.redraw(animation);
                }

                return axis;
            },

            /**
             * Dim the chart and show a loading text or symbol. Options for the loading
             * screen are defined in {@link
             * https://api.highcharts.com/highcharts/loading|the loading options}.
             * 
             * @param  {String} str
             *         An optional text to show in the loading label instead of the
             *         default one. The default text is set in {@link
             *         http://api.highcharts.com/highcharts/lang.loading|lang.loading}.
             *
             * @sample highcharts/members/chart-hideloading/
             *         Show and hide loading from a button
             * @sample highcharts/members/chart-showloading/
             *         Apply different text labels
             * @sample stock/members/chart-show-hide-loading/
             *         Toggle loading in Highstock
             */
            showLoading: function(str) {
                var chart = this,
                    options = chart.options,
                    loadingDiv = chart.loadingDiv,
                    loadingOptions = options.loading,
                    setLoadingSize = function() {
                        if (loadingDiv) {
                            css(loadingDiv, {
                                left: chart.plotLeft + 'px',
                                top: chart.plotTop + 'px',
                                width: chart.plotWidth + 'px',
                                height: chart.plotHeight + 'px'
                            });
                        }
                    };

                // create the layer at the first call
                if (!loadingDiv) {
                    chart.loadingDiv = loadingDiv = createElement('div', {
                        className: 'highcharts-loading highcharts-loading-hidden'
                    }, null, chart.container);

                    chart.loadingSpan = createElement(
                        'span', {
                            className: 'highcharts-loading-inner'
                        },
                        null,
                        loadingDiv
                    );
                    addEvent(chart, 'redraw', setLoadingSize); // #1080
                }

                loadingDiv.className = 'highcharts-loading';

                // Update text
                chart.loadingSpan.innerHTML = str || options.lang.loading;


                // Update visuals
                css(loadingDiv, extend(loadingOptions.style, {
                    zIndex: 10
                }));
                css(chart.loadingSpan, loadingOptions.labelStyle);

                // Show it
                if (!chart.loadingShown) {
                    css(loadingDiv, {
                        opacity: 0,
                        display: ''
                    });
                    animate(loadingDiv, {
                        opacity: loadingOptions.style.opacity || 0.5
                    }, {
                        duration: loadingOptions.showDuration || 0
                    });
                }


                chart.loadingShown = true;
                setLoadingSize();
            },

            /**
             * Hide the loading layer.
             *
             * @see    Highcharts.Chart#showLoading
             * @sample highcharts/members/chart-hideloading/
             *         Show and hide loading from a button
             * @sample stock/members/chart-show-hide-loading/
             *         Toggle loading in Highstock
             */
            hideLoading: function() {
                var options = this.options,
                    loadingDiv = this.loadingDiv;

                if (loadingDiv) {
                    loadingDiv.className = 'highcharts-loading highcharts-loading-hidden';

                    animate(loadingDiv, {
                        opacity: 0
                    }, {
                        duration: options.loading.hideDuration || 100,
                        complete: function() {
                            css(loadingDiv, {
                                display: 'none'
                            });
                        }
                    });

                }
                this.loadingShown = false;
            },

            /** 
             * These properties cause isDirtyBox to be set to true when updating. Can be extended from plugins.
             */
            propsRequireDirtyBox: ['backgroundColor', 'borderColor', 'borderWidth', 'margin', 'marginTop', 'marginRight',
                'marginBottom', 'marginLeft', 'spacing', 'spacingTop', 'spacingRight', 'spacingBottom', 'spacingLeft',
                'borderRadius', 'plotBackgroundColor', 'plotBackgroundImage', 'plotBorderColor', 'plotBorderWidth',
                'plotShadow', 'shadow'
            ],

            /** 
             * These properties cause all series to be updated when updating. Can be
             * extended from plugins.
             */
            propsRequireUpdateSeries: ['chart.inverted', 'chart.polar',
                'chart.ignoreHiddenSeries', 'chart.type', 'colors', 'plotOptions',
                'tooltip'
            ],

            /**
             * A generic function to update any element of the chart. Elements can be
             * enabled and disabled, moved, re-styled, re-formatted etc.
             *
             * A special case is configuration objects that take arrays, for example
             * {@link https://api.highcharts.com/highcharts/xAxis|xAxis}, 
             * {@link https://api.highcharts.com/highcharts/yAxis|yAxis} or 
             * {@link https://api.highcharts.com/highcharts/series|series}. For these
             * collections, an `id` option is used to map the new option set to an
             * existing object. If an existing object of the same id is not found, the
             * corresponding item is updated. So for example, running `chart.update`
             * with a series item without an id, will cause the existing chart's series
             * with the same index in the series array to be updated. When the
             * `oneToOne` parameter is true, `chart.update` will also take care of
             * adding and removing items from the collection. Read more under the
             * parameter description below.
             *
             * See also the {@link https://api.highcharts.com/highcharts/responsive|
             * responsive option set}. Switching between `responsive.rules` basically
             * runs `chart.update` under the hood.
             *
             * @param  {Options} options
             *         A configuration object for the new chart options.
             * @param  {Boolean} [redraw=true]
             *         Whether to redraw the chart.
             * @param  {Boolean} [oneToOne=false]
             *         When `true`, the `series`, `xAxis` and `yAxis` collections will
             *         be updated one to one, and items will be either added or removed
             *         to match the new updated options. For example, if the chart has
             *         two series and we call `chart.update` with a configuration 
             *         containing three series, one will be added. If we call
             *         `chart.update` with one series, one will be removed. Setting an
             *         empty `series` array will remove all series, but leaving out the
             *         `series` property will leave all series untouched. If the series
             *         have id's, the new series options will be matched by id, and the
             *         remaining ones removed.
             *
             * @sample highcharts/members/chart-update/
             *         Update chart geometry 
             */
            update: function(options, redraw, oneToOne) {
                var chart = this,
                    adders = {
                        credits: 'addCredits',
                        title: 'setTitle',
                        subtitle: 'setSubtitle'
                    },
                    optionsChart = options.chart,
                    updateAllAxes,
                    updateAllSeries,
                    newWidth,
                    newHeight,
                    itemsForRemoval = [];

                // If the top-level chart option is present, some special updates are required		
                if (optionsChart) {
                    merge(true, chart.options.chart, optionsChart);

                    // Setter function
                    if ('className' in optionsChart) {
                        chart.setClassName(optionsChart.className);
                    }

                    if ('inverted' in optionsChart || 'polar' in optionsChart) {
                        // Parse options.chart.inverted and options.chart.polar together
                        // with the available series.
                        chart.propFromSeries();
                        updateAllAxes = true;
                    }

                    if ('alignTicks' in optionsChart) { // #6452
                        updateAllAxes = true;
                    }

                    objectEach(optionsChart, function(val, key) {
                        if (inArray('chart.' + key, chart.propsRequireUpdateSeries) !== -1) {
                            updateAllSeries = true;
                        }
                        // Only dirty box
                        if (inArray(key, chart.propsRequireDirtyBox) !== -1) {
                            chart.isDirtyBox = true;
                        }
                    });


                    if ('style' in optionsChart) {
                        chart.renderer.setStyle(optionsChart.style);
                    }

                }

                // Moved up, because tooltip needs updated plotOptions (#6218)

                if (options.colors) {
                    this.options.colors = options.colors;
                }


                if (options.plotOptions) {
                    merge(true, this.options.plotOptions, options.plotOptions);
                }

                // Some option stuctures correspond one-to-one to chart objects that
                // have update methods, for example
                // options.credits => chart.credits
                // options.legend => chart.legend
                // options.title => chart.title
                // options.tooltip => chart.tooltip
                // options.subtitle => chart.subtitle
                // options.mapNavigation => chart.mapNavigation
                // options.navigator => chart.navigator
                // options.scrollbar => chart.scrollbar
                objectEach(options, function(val, key) {
                    if (chart[key] && typeof chart[key].update === 'function') {
                        chart[key].update(val, false);

                        // If a one-to-one object does not exist, look for an adder function
                    } else if (typeof chart[adders[key]] === 'function') {
                        chart[adders[key]](val);
                    }

                    if (
                        key !== 'chart' &&
                        inArray(key, chart.propsRequireUpdateSeries) !== -1
                    ) {
                        updateAllSeries = true;
                    }
                });

                // Setters for collections. For axes and series, each item is referred
                // by an id. If the id is not found, it defaults to the corresponding
                // item in the collection, so setting one series without an id, will
                // update the first series in the chart. Setting two series without
                // an id will update the first and the second respectively (#6019)
                // chart.update and responsive.
                each([
                    'xAxis',
                    'yAxis',
                    'zAxis',
                    'series',
                    'colorAxis',
                    'pane'
                ], function(coll) {
                    if (options[coll]) {
                        each(splat(options[coll]), function(newOptions, i) {
                            var item = (
                                defined(newOptions.id) &&
                                chart.get(newOptions.id)
                            ) || chart[coll][i];
                            if (item && item.coll === coll) {
                                item.update(newOptions, false);

                                if (oneToOne) {
                                    item.touched = true;
                                }
                            }

                            // If oneToOne and no matching item is found, add one
                            if (!item && oneToOne) {
                                if (coll === 'series') {
                                    chart.addSeries(newOptions, false)
                                        .touched = true;
                                } else if (coll === 'xAxis' || coll === 'yAxis') {
                                    chart.addAxis(newOptions, coll === 'xAxis', false)
                                        .touched = true;
                                }
                            }

                        });

                        // Add items for removal
                        if (oneToOne) {
                            each(chart[coll], function(item) {
                                if (!item.touched) {
                                    itemsForRemoval.push(item);
                                } else {
                                    delete item.touched;
                                }
                            });
                        }


                    }
                });

                each(itemsForRemoval, function(item) {
                    item.remove(false);
                });

                if (updateAllAxes) {
                    each(chart.axes, function(axis) {
                        axis.update({}, false);
                    });
                }

                // Certain options require the whole series structure to be thrown away
                // and rebuilt
                if (updateAllSeries) {
                    each(chart.series, function(series) {
                        series.update({}, false);
                    });
                }

                // For loading, just update the options, do not redraw
                if (options.loading) {
                    merge(true, chart.options.loading, options.loading);
                }

                // Update size. Redraw is forced.
                newWidth = optionsChart && optionsChart.width;
                newHeight = optionsChart && optionsChart.height;
                if ((isNumber(newWidth) && newWidth !== chart.chartWidth) ||
                    (isNumber(newHeight) && newHeight !== chart.chartHeight)) {
                    chart.setSize(newWidth, newHeight);
                } else if (pick(redraw, true)) {
                    chart.redraw();
                }
            },

            /**
             * Shortcut to set the subtitle options. This can also be done from {@link
             * Chart#update} or {@link Chart#setTitle}.
             *
             * @param  {SubtitleOptions} options
             *         New subtitle options. The subtitle text itself is set by the
             *         `options.text` property.
             */
            setSubtitle: function(options) {
                this.setTitle(undefined, options);
            }


        });

        // extend the Point prototype for dynamic methods
        extend(Point.prototype, /** @lends Highcharts.Point.prototype */ {
            /**
             * Update point with new options (typically x/y data) and optionally redraw
             * the series.
             *
             * @param  {Object} options
             *         The point options. Point options are handled as described under
             *         the `series.type.data` item for each series type. For example
             *         for a line series, if options is a single number, the point will
             *         be given that number as the main y value. If it is an array, it
             *         will be interpreted as x and y values respectively. If it is an
             *         object, advanced options are applied. 
             * @param  {Boolean} [redraw=true]
             *          Whether to redraw the chart after the point is updated. If doing
             *          more operations on the chart, it is best practice to set
             *          `redraw` to false and call `chart.redraw()` after.
             * @param  {AnimationOptions} [animation=true]
             *         Whether to apply animation, and optionally animation
             *         configuration.
             *
             * @sample highcharts/members/point-update-column/
             *         Update column value
             * @sample highcharts/members/point-update-pie/
             *         Update pie slice
             * @sample maps/members/point-update/
             *         Update map area value in Highmaps
             */
            update: function(options, redraw, animation, runEvent) {
                var point = this,
                    series = point.series,
                    graphic = point.graphic,
                    i,
                    chart = series.chart,
                    seriesOptions = series.options;

                redraw = pick(redraw, true);

                function update() {

                    point.applyOptions(options);

                    // Update visuals
                    if (point.y === null && graphic) { // #4146
                        point.graphic = graphic.destroy();
                    }
                    if (isObject(options, true)) {
                        // Destroy so we can get new elements
                        if (graphic && graphic.element) {
                            // "null" is also a valid symbol
                            if (options && options.marker && options.marker.symbol !== undefined) {
                                point.graphic = graphic.destroy();
                            }
                        }
                        if (options && options.dataLabels && point.dataLabel) { // #2468
                            point.dataLabel = point.dataLabel.destroy();
                        }
                        if (point.connector) {
                            point.connector = point.connector.destroy(); // #7243
                        }
                    }

                    // record changes in the parallel arrays
                    i = point.index;
                    series.updateParallelArrays(point, i);

                    // Record the options to options.data. If the old or the new config
                    // is an object, use point options, otherwise use raw options
                    // (#4701, #4916).
                    seriesOptions.data[i] = (
                            isObject(seriesOptions.data[i], true) ||
                            isObject(options, true)
                        ) ?
                        point.options :
                        options;

                    // redraw
                    series.isDirty = series.isDirtyData = true;
                    if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320
                        chart.isDirtyBox = true;
                    }

                    if (seriesOptions.legendType === 'point') { // #1831, #1885
                        chart.isDirtyLegend = true;
                    }
                    if (redraw) {
                        chart.redraw(animation);
                    }
                }

                // Fire the event with a default handler of doing the update
                if (runEvent === false) { // When called from setData
                    update();
                } else {
                    point.firePointEvent('update', {
                        options: options
                    }, update);
                }
            },

            /**
             * Remove a point and optionally redraw the series and if necessary the axes
             * @param  {Boolean} redraw
             *         Whether to redraw the chart or wait for an explicit call. When
             *         doing more operations on the chart, for example running
             *         `point.remove()` in a loop, it is best practice to set `redraw`
             *         to false and call `chart.redraw()` after.         
             * @param  {AnimationOptions} [animation=false]
             *         Whether to apply animation, and optionally animation
             *         configuration.
             *
             * @sample highcharts/plotoptions/series-point-events-remove/
             *         Remove point and confirm
             * @sample highcharts/members/point-remove/
             *         Remove pie slice
             * @sample maps/members/point-remove/
             *         Remove selected points in Highmaps
             */
            remove: function(redraw, animation) {
                this.series.removePoint(inArray(this, this.series.data), redraw, animation);
            }
        });

        // Extend the series prototype for dynamic methods
        extend(Series.prototype, /** @lends Series.prototype */ {
            /**
             * Add a point to the series after render time. The point can be added at
             * the end, or by giving it an X value, to the start or in the middle of the
             * series.
             * 
             * @param  {Number|Array|Object} options
             *         The point options. If options is a single number, a point with
             *         that y value is appended to the series.If it is an array, it will
             *         be interpreted as x and y values respectively. If it is an
             *         object, advanced options as outlined under `series.data` are
             *         applied.
             * @param  {Boolean} [redraw=true]
             *         Whether to redraw the chart after the point is added. When adding
             *         more than one point, it is highly recommended that the redraw
             *         option be set to false, and instead {@link Chart#redraw}
             *         is explicitly called after the adding of points is finished.
             *         Otherwise, the chart will redraw after adding each point.
             * @param  {Boolean} [shift=false]
             *         If true, a point is shifted off the start of the series as one is
             *         appended to the end.
             * @param  {AnimationOptions} [animation]
             *         Whether to apply animation, and optionally animation
             *         configuration.
             *
             * @sample highcharts/members/series-addpoint-append/
             *         Append point
             * @sample highcharts/members/series-addpoint-append-and-shift/
             *         Append and shift
             * @sample highcharts/members/series-addpoint-x-and-y/
             *         Both X and Y values given
             * @sample highcharts/members/series-addpoint-pie/
             *         Append pie slice
             * @sample stock/members/series-addpoint/
             *         Append 100 points in Highstock
             * @sample stock/members/series-addpoint-shift/
             *         Append and shift in Highstock
             * @sample maps/members/series-addpoint/
             *         Add a point in Highmaps
             */
            addPoint: function(options, redraw, shift, animation) {
                var series = this,
                    seriesOptions = series.options,
                    data = series.data,
                    chart = series.chart,
                    xAxis = series.xAxis,
                    names = xAxis && xAxis.hasNames && xAxis.names,
                    dataOptions = seriesOptions.data,
                    point,
                    isInTheMiddle,
                    xData = series.xData,
                    i,
                    x;

                // Optional redraw, defaults to true
                redraw = pick(redraw, true);

                // Get options and push the point to xData, yData and series.options. In series.generatePoints
                // the Point instance will be created on demand and pushed to the series.data array.
                point = {
                    series: series
                };
                series.pointClass.prototype.applyOptions.apply(point, [options]);
                x = point.x;

                // Get the insertion point
                i = xData.length;
                if (series.requireSorting && x < xData[i - 1]) {
                    isInTheMiddle = true;
                    while (i && xData[i - 1] > x) {
                        i--;
                    }
                }

                series.updateParallelArrays(point, 'splice', i, 0, 0); // insert undefined item
                series.updateParallelArrays(point, i); // update it

                if (names && point.name) {
                    names[x] = point.name;
                }
                dataOptions.splice(i, 0, options);

                if (isInTheMiddle) {
                    series.data.splice(i, 0, null);
                    series.processData();
                }

                // Generate points to be added to the legend (#1329)
                if (seriesOptions.legendType === 'point') {
                    series.generatePoints();
                }

                // Shift the first point off the parallel arrays
                if (shift) {
                    if (data[0] && data[0].remove) {
                        data[0].remove(false);
                    } else {
                        data.shift();
                        series.updateParallelArrays(point, 'shift');

                        dataOptions.shift();
                    }
                }

                // redraw
                series.isDirty = true;
                series.isDirtyData = true;

                if (redraw) {
                    chart.redraw(animation); // Animation is set anyway on redraw, #5665
                }
            },

            /**
             * Remove a point from the series. Unlike the {@link Highcharts.Point#remove}
             * method, this can also be done on a point that is not instanciated because
             * it is outside the view or subject to Highstock data grouping.
             *
             * @param  {Number} i
             *         The index of the point in the {@link Highcharts.Series.data|data}
             *         array.
             * @param  {Boolean} [redraw=true]
             *         Whether to redraw the chart after the point is added. When 
             *         removing more than one point, it is highly recommended that the
             *         `redraw` option be set to `false`, and instead {@link
             *         Highcharts.Chart#redraw} is explicitly called after the adding of
             *         points is finished.
             * @param  {AnimationOptions} [animation]
             *         Whether and optionally how the series should be animated.
             *
             * @sample highcharts/members/series-removepoint/
             *         Remove cropped point
             */
            removePoint: function(i, redraw, animation) {

                var series = this,
                    data = series.data,
                    point = data[i],
                    points = series.points,
                    chart = series.chart,
                    remove = function() {

                        if (points && points.length === data.length) { // #4935
                            points.splice(i, 1);
                        }
                        data.splice(i, 1);
                        series.options.data.splice(i, 1);
                        series.updateParallelArrays(point || {
                            series: series
                        }, 'splice', i, 1);

                        if (point) {
                            point.destroy();
                        }

                        // redraw
                        series.isDirty = true;
                        series.isDirtyData = true;
                        if (redraw) {
                            chart.redraw();
                        }
                    };

                setAnimation(animation, chart);
                redraw = pick(redraw, true);

                // Fire the event with a default handler of removing the point
                if (point) {
                    point.firePointEvent('remove', null, remove);
                } else {
                    remove();
                }
            },

            /**
             * Remove a series and optionally redraw the chart.
             *
             * @param  {Boolean} [redraw=true]
             *         Whether to redraw the chart or wait for an explicit call to
             *         {@link Highcharts.Chart#redraw}.
             * @param  {AnimationOptions} [animation]
             *         Whether to apply animation, and optionally animation
             *         configuration
             * @param  {Boolean} [withEvent=true]
             *         Used internally, whether to fire the series `remove` event.
             *
             * @sample highcharts/members/series-remove/
             *         Remove first series from a button
             */
            remove: function(redraw, animation, withEvent) {
                var series = this,
                    chart = series.chart;

                function remove() {

                    // Destroy elements
                    series.destroy();

                    // Redraw
                    chart.isDirtyLegend = chart.isDirtyBox = true;
                    chart.linkSeries();

                    if (pick(redraw, true)) {
                        chart.redraw(animation);
                    }
                }

                // Fire the event with a default handler of removing the point
                if (withEvent !== false) {
                    fireEvent(series, 'remove', null, remove);
                } else {
                    remove();
                }
            },

            /**
             * Update the series with a new set of options. For a clean and precise
             * handling of new options, all methods and elements from the series are
             * removed, and it is initiated from scratch. Therefore, this method is more
             * performance expensive than some other utility methods like {@link
             * Series#setData} or {@link Series#setVisible}.
             *
             * @param  {SeriesOptions} options
             *         New options that will be merged with the series' existing
             *         options.
             * @param  {Boolean} [redraw=true]
             *         Whether to redraw the chart after the series is altered. If doing
             *         more operations on the chart, it is a good idea to set redraw to
             *         false and call {@link Chart#redraw} after.
             *
             * @sample highcharts/members/series-update/
             *         Updating series options
             * @sample maps/members/series-update/
             *         Update series options in Highmaps
             */
            update: function(newOptions, redraw) {
                var series = this,
                    chart = series.chart,
                    // must use user options when changing type because series.options
                    // is merged in with type specific plotOptions
                    oldOptions = series.userOptions,
                    oldType = series.oldType || series.type,
                    newType = newOptions.type || oldOptions.type || chart.options.chart.type,
                    proto = seriesTypes[oldType].prototype,
                    n,
                    preserveGroups = [
                        'group',
                        'markerGroup',
                        'dataLabelsGroup'
                    ],
                    preserve = [
                        'navigatorSeries',
                        'baseSeries'
                    ],

                    // Animation must be enabled when calling update before the initial
                    // animation has first run. This happens when calling update
                    // directly after chart initialization, or when applying responsive
                    // rules (#6912).
                    animation = series.finishedAnimating && {
                        animation: false
                    };

                // Running Series.update to update the data only is an intuitive usage,
                // so we want to make sure that when used like this, we run the
                // cheaper setData function and allow animation instead of completely
                // recreating the series instance.
                if (Object.keys && Object.keys(newOptions).toString() === 'data') {
                    return this.setData(newOptions.data, redraw);
                }

                // If we're changing type or zIndex, create new groups (#3380, #3404)
                // Also create new groups for navigator series.
                if (
                    (newType && newType !== oldType) ||
                    newOptions.zIndex !== undefined
                ) {
                    preserveGroups.length = 0;
                }

                // Make sure preserved properties are not destroyed (#3094)
                preserve = preserveGroups.concat(preserve);
                each(preserve, function(prop) {
                    preserve[prop] = series[prop];
                    delete series[prop];
                });

                // Do the merge, with some forced options
                newOptions = merge(oldOptions, animation, {
                    index: series.index,
                    pointStart: series.xData[0] // when updating after addPoint
                }, {
                    data: series.options.data
                }, newOptions);

                // Destroy the series and delete all properties. Reinsert all methods
                // and properties from the new type prototype (#2270, #3719)
                series.remove(false, null, false);
                for (n in proto) {
                    series[n] = undefined;
                }
                extend(series, seriesTypes[newType || oldType].prototype);

                // Re-register groups (#3094) and other preserved properties
                each(preserve, function(prop) {
                    series[prop] = preserve[prop];
                });

                series.init(chart, newOptions);
                series.oldType = oldType;
                chart.linkSeries(); // Links are lost in series.remove (#3028)
                if (pick(redraw, true)) {
                    chart.redraw(false);
                }
            }
        });

        // Extend the Axis.prototype for dynamic methods
        extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ {

            /**
             * Update an axis object with a new set of options. The options are merged
             * with the existing options, so only new or altered options need to be
             * specified.
             *
             * @param  {Object} options
             *         The new options that will be merged in with existing options on
             *         the axis.
             * @sample highcharts/members/axis-update/ Axis update demo
             */
            update: function(options, redraw) {
                var chart = this.chart;

                options = chart.options[this.coll][this.options.index] =
                    merge(this.userOptions, options);

                this.destroy(true);

                this.init(chart, extend(options, {
                    events: undefined
                }));

                chart.isDirtyBox = true;
                if (pick(redraw, true)) {
                    chart.redraw();
                }
            },

            /**
             * Remove the axis from the chart.
             *
             * @param {Boolean} [redraw=true] Whether to redraw the chart following the
             * remove.
             *
             * @sample highcharts/members/chart-addaxis/ Add and remove axes
             */
            remove: function(redraw) {
                var chart = this.chart,
                    key = this.coll, // xAxis or yAxis
                    axisSeries = this.series,
                    i = axisSeries.length;

                // Remove associated series (#2687)
                while (i--) {
                    if (axisSeries[i]) {
                        axisSeries[i].remove(false);
                    }
                }

                // Remove the axis
                erase(chart.axes, this);
                erase(chart[key], this);

                if (isArray(chart.options[key])) {
                    chart.options[key].splice(this.options.index, 1);
                } else { // color axis, #6488
                    delete chart.options[key];
                }

                each(chart[key], function(axis, i) { // Re-index, #1706
                    axis.options.index = i;
                });
                this.destroy();
                chart.isDirtyBox = true;

                if (pick(redraw, true)) {
                    chart.redraw();
                }
            },

            /**
             * Update the axis title by options after render time.
             *
             * @param  {TitleOptions} titleOptions
             *         The additional title options.
             * @param  {Boolean} [redraw=true]
             *         Whether to redraw the chart after setting the title.
             * @sample highcharts/members/axis-settitle/ Set a new Y axis title
             */
            setTitle: function(titleOptions, redraw) {
                this.update({
                    title: titleOptions
                }, redraw);
            },

            /**
             * Set new axis categories and optionally redraw.
             * @param {Array.<String>} categories - The new categories.
             * @param {Boolean} [redraw=true] - Whether to redraw the chart.
             * @sample highcharts/members/axis-setcategories/ Set categories by click on
             * a button
             */
            setCategories: function(categories, redraw) {
                this.update({
                    categories: categories
                }, redraw);
            }

        });

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var color = H.color,
            each = H.each,
            LegendSymbolMixin = H.LegendSymbolMixin,
            map = H.map,
            pick = H.pick,
            Series = H.Series,
            seriesType = H.seriesType;

        /**
         * Area series type.
         * @constructor seriesTypes.area
         * @extends {Series}
         */
        /**
         * The area series type.
         * @extends {plotOptions.line}
         * @product highcharts highstock
         * @sample {highcharts} highcharts/demo/area-basic/
         *         Area chart
         * @sample {highstock} stock/demo/area/
         *         Area chart
         * @optionparent plotOptions.area
         */
        seriesType('area', 'line', {

            /**
             * Fill color or gradient for the area. When `null`, the series' `color`
             * is used with the series' `fillOpacity`.
             * 
             * @type {Color}
             * @see In styled mode, the fill color can be set with the `.highcharts-area` class name.
             * @sample {highcharts} highcharts/plotoptions/area-fillcolor-default/ Null by default
             * @sample {highcharts} highcharts/plotoptions/area-fillcolor-gradient/ Gradient
             * @default null
             * @product highcharts highstock
             * @apioption plotOptions.area.fillColor
             */

            /**
             * Fill opacity for the area. When you set an explicit `fillColor`,
             * the `fillOpacity` is not applied. Instead, you should define the
             * opacity in the `fillColor` with an rgba color definition. The `fillOpacity`
             * setting, also the default setting, overrides the alpha component
             * of the `color` setting.
             * 
             * @type {Number}
             * @see In styled mode, the fill opacity can be set with the `.highcharts-area` class name.
             * @sample {highcharts} highcharts/plotoptions/area-fillopacity/ Automatic fill color and fill opacity of 0.1
             * @default {highcharts} 0.75
             * @default {highstock} .75
             * @product highcharts highstock
             * @apioption plotOptions.area.fillOpacity
             */

            /**
             * A separate color for the graph line. By default the line takes the
             * `color` of the series, but the lineColor setting allows setting a
             * separate color for the line without altering the `fillColor`.
             * 
             * @type {Color}
             * @see In styled mode, the line stroke can be set with the `.highcharts-graph` class name.
             * @sample {highcharts} highcharts/plotoptions/area-linecolor/ Dark gray line
             * @default null
             * @product highcharts highstock
             * @apioption plotOptions.area.lineColor
             */

            /**
             * A separate color for the negative part of the area.
             * 
             * @type {Color}
             * @see [negativeColor](#plotOptions.area.negativeColor). In styled mode, a negative
             * color is set with the `.highcharts-negative` class name ([view live
             * demo](http://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/series-
             * negative-color/)).
             * @since 3.0
             * @product highcharts
             * @apioption plotOptions.area.negativeFillColor
             */

            /**
             * When this is true, the series will not cause the Y axis to cross
             * the zero plane (or [threshold](#plotOptions.series.threshold) option)
             * unless the data actually crosses the plane.
             * 
             * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
             * 3 will make the Y axis show negative values according to the `minPadding`
             * option. If `softThreshold` is `true`, the Y axis starts at 0.
             * 
             * @type {Boolean}
             * @default false
             * @since 4.1.9
             * @product highcharts highstock
             */
            softThreshold: false,

            /**
             * The Y axis value to serve as the base for the area, for distinguishing
             * between values above and below a threshold. If `null`, the area
             * behaves like a line series with fill between the graph and the Y
             * axis minimum.
             * 
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/area-threshold/ A threshold of 100
             * @default 0
             * @since 2.0
             * @product highcharts highstock
             */
            threshold: 0

            /**
             * Whether the whole area or just the line should respond to mouseover
             * tooltips and other mouse or touch events.
             * 
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/area-trackbyarea/ Display the tooltip when the     area is hovered
             * @sample {highstock} highcharts/plotoptions/area-trackbyarea/ Display the tooltip when the     area is hovered
             * @default false
             * @since 1.1.6
             * @product highcharts highstock
             * @apioption plotOptions.area.trackByArea
             */


        }, /** @lends seriesTypes.area.prototype */ {
            singleStacks: false,
            /** 
             * Return an array of stacked points, where null and missing points are replaced by 
             * dummy points in order for gaps to be drawn correctly in stacks.
             */
            getStackPoints: function(points) {
                var series = this,
                    segment = [],
                    keys = [],
                    xAxis = this.xAxis,
                    yAxis = this.yAxis,
                    stack = yAxis.stacks[this.stackKey],
                    pointMap = {},
                    seriesIndex = series.index,
                    yAxisSeries = yAxis.series,
                    seriesLength = yAxisSeries.length,
                    visibleSeries,
                    upOrDown = pick(yAxis.options.reversedStacks, true) ? 1 : -1,
                    i;


                points = points || this.points;

                if (this.options.stacking) {

                    for (i = 0; i < points.length; i++) {
                        // Reset after point update (#7326)
                        points[i].leftNull = points[i].rightNull = null;

                        // Create a map where we can quickly look up the points by their
                        // X values.
                        pointMap[points[i].x] = points[i];
                    }

                    // Sort the keys (#1651)
                    H.objectEach(stack, function(stackX, x) {
                        if (stackX.total !== null) { // nulled after switching between grouping and not (#1651, #2336)
                            keys.push(x);
                        }
                    });
                    keys.sort(function(a, b) {
                        return a - b;
                    });

                    visibleSeries = map(yAxisSeries, function() {
                        return this.visible;
                    });

                    each(keys, function(x, idx) {
                        var y = 0,
                            stackPoint,
                            stackedValues;

                        if (pointMap[x] && !pointMap[x].isNull) {
                            segment.push(pointMap[x]);

                            // Find left and right cliff. -1 goes left, 1 goes right.
                            each([-1, 1], function(direction) {
                                var nullName = direction === 1 ? 'rightNull' : 'leftNull',
                                    cliffName = direction === 1 ? 'rightCliff' : 'leftCliff',
                                    cliff = 0,
                                    otherStack = stack[keys[idx + direction]];

                                // If there is a stack next to this one, to the left or to the right...
                                if (otherStack) {
                                    i = seriesIndex;
                                    while (i >= 0 && i < seriesLength) { // Can go either up or down, depending on reversedStacks
                                        stackPoint = otherStack.points[i];
                                        if (!stackPoint) {
                                            // If the next point in this series is missing, mark the point
                                            // with point.leftNull or point.rightNull = true.
                                            if (i === seriesIndex) {
                                                pointMap[x][nullName] = true;

                                                // If there are missing points in the next stack in any of the 
                                                // series below this one, we need to substract the missing values
                                                // and add a hiatus to the left or right.
                                            } else if (visibleSeries[i]) {
                                                stackedValues = stack[x].points[i];
                                                if (stackedValues) {
                                                    cliff -= stackedValues[1] - stackedValues[0];
                                                }
                                            }
                                        }
                                        // When reversedStacks is true, loop up, else loop down
                                        i += upOrDown;
                                    }
                                }
                                pointMap[x][cliffName] = cliff;
                            });


                            // There is no point for this X value in this series, so we 
                            // insert a dummy point in order for the areas to be drawn
                            // correctly.
                        } else {

                            // Loop down the stack to find the series below this one that has
                            // a value (#1991)
                            i = seriesIndex;
                            while (i >= 0 && i < seriesLength) {
                                stackPoint = stack[x].points[i];
                                if (stackPoint) {
                                    y = stackPoint[1];
                                    break;
                                }
                                // When reversedStacks is true, loop up, else loop down
                                i += upOrDown;
                            }
                            y = yAxis.translate(y, 0, 1, 0, 1); // #6272
                            segment.push({
                                isNull: true,
                                plotX: xAxis.translate(x, 0, 0, 0, 1), // #6272
                                x: x,
                                plotY: y,
                                yBottom: y
                            });
                        }
                    });

                }

                return segment;
            },

            getGraphPath: function(points) {
                var getGraphPath = Series.prototype.getGraphPath,
                    graphPath,
                    options = this.options,
                    stacking = options.stacking,
                    yAxis = this.yAxis,
                    topPath,
                    bottomPath,
                    bottomPoints = [],
                    graphPoints = [],
                    seriesIndex = this.index,
                    i,
                    areaPath,
                    plotX,
                    stacks = yAxis.stacks[this.stackKey],
                    threshold = options.threshold,
                    translatedThreshold = yAxis.getThreshold(options.threshold),
                    isNull,
                    yBottom,
                    connectNulls = options.connectNulls || stacking === 'percent',
                    /**
                     * To display null points in underlying stacked series, this series graph must be 
                     * broken, and the area also fall down to fill the gap left by the null point. #2069
                     */
                    addDummyPoints = function(i, otherI, side) {
                        var point = points[i],
                            stackedValues = stacking && stacks[point.x].points[seriesIndex],
                            nullVal = point[side + 'Null'] || 0,
                            cliffVal = point[side + 'Cliff'] || 0,
                            top,
                            bottom,
                            isNull = true;

                        if (cliffVal || nullVal) {

                            top = (nullVal ? stackedValues[0] : stackedValues[1]) + cliffVal;
                            bottom = stackedValues[0] + cliffVal;
                            isNull = !!nullVal;

                        } else if (!stacking && points[otherI] && points[otherI].isNull) {
                            top = bottom = threshold;
                        }

                        // Add to the top and bottom line of the area
                        if (top !== undefined) {
                            graphPoints.push({
                                plotX: plotX,
                                plotY: top === null ? translatedThreshold : yAxis.getThreshold(top),
                                isNull: isNull,
                                isCliff: true
                            });
                            bottomPoints.push({
                                plotX: plotX,
                                plotY: bottom === null ? translatedThreshold : yAxis.getThreshold(bottom),
                                doCurve: false // #1041, gaps in areaspline areas
                            });
                        }
                    };

                // Find what points to use
                points = points || this.points;

                // Fill in missing points
                if (stacking) {
                    points = this.getStackPoints(points);
                }

                for (i = 0; i < points.length; i++) {
                    isNull = points[i].isNull;
                    plotX = pick(points[i].rectPlotX, points[i].plotX);
                    yBottom = pick(points[i].yBottom, translatedThreshold);

                    if (!isNull || connectNulls) {

                        if (!connectNulls) {
                            addDummyPoints(i, i - 1, 'left');
                        }

                        if (!(isNull && !stacking && connectNulls)) { // Skip null point when stacking is false and connectNulls true
                            graphPoints.push(points[i]);
                            bottomPoints.push({
                                x: i,
                                plotX: plotX,
                                plotY: yBottom
                            });
                        }

                        if (!connectNulls) {
                            addDummyPoints(i, i + 1, 'right');
                        }
                    }
                }

                topPath = getGraphPath.call(this, graphPoints, true, true);

                bottomPoints.reversed = true;
                bottomPath = getGraphPath.call(this, bottomPoints, true, true);
                if (bottomPath.length) {
                    bottomPath[0] = 'L';
                }

                areaPath = topPath.concat(bottomPath);
                graphPath = getGraphPath.call(this, graphPoints, false, connectNulls); // TODO: don't set leftCliff and rightCliff when connectNulls?

                areaPath.xMap = topPath.xMap;
                this.areaPath = areaPath;

                return graphPath;
            },

            /**
             * Draw the graph and the underlying area. This method calls the Series base
             * function and adds the area. The areaPath is calculated in the getSegmentPath
             * method called from Series.prototype.drawGraph.
             */
            drawGraph: function() {

                // Define or reset areaPath
                this.areaPath = [];

                // Call the base method
                Series.prototype.drawGraph.apply(this);

                // Define local variables
                var series = this,
                    areaPath = this.areaPath,
                    options = this.options,
                    zones = this.zones,
                    props = [
                        [
                            'area',
                            'highcharts-area',

                            this.color,
                            options.fillColor

                        ]
                    ]; // area name, main color, fill color

                each(zones, function(zone, i) {
                    props.push([
                        'zone-area-' + i,
                        'highcharts-area highcharts-zone-area-' + i + ' ' + zone.className,

                        zone.color || series.color,
                        zone.fillColor || options.fillColor

                    ]);
                });

                each(props, function(prop) {
                    var areaKey = prop[0],
                        area = series[areaKey];

                    // Create or update the area
                    if (area) { // update
                        area.endX = series.preventGraphAnimation ? null : areaPath.xMap;
                        area.animate({
                            d: areaPath
                        });

                    } else { // create
                        area = series[areaKey] = series.chart.renderer.path(areaPath)
                            .addClass(prop[1])
                            .attr({

                                fill: pick(
                                    prop[3],
                                    color(prop[2]).setOpacity(pick(options.fillOpacity, 0.75)).get()
                                ),

                                zIndex: 0 // #1069
                            }).add(series.group);
                        area.isArea = true;
                    }
                    area.startX = areaPath.xMap;
                    area.shiftUnit = options.step ? 2 : 1;
                });
            },

            drawLegendSymbol: LegendSymbolMixin.drawRectangle
        });

        /**
         * A `area` series. If the [type](#series.area.type) option is not
         * specified, it is inherited from [chart.type](#chart.type).
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * area](#plotOptions.area).
         * 
         * @type {Object}
         * @extends series,plotOptions.area
         * @excluding dataParser,dataURL
         * @product highcharts highstock
         * @apioption series.area
         */

        /**
         * An array of data points for the series. For the `area` series type,
         * points can be given in the following ways:
         * 
         * 1.  An array of numerical values. In this case, the numerical values
         * will be interpreted as `y` options. The `x` values will be automatically
         * calculated, either starting at 0 and incremented by 1, or from `pointStart`
         * and `pointInterval` given in the series options. If the axis has
         * categories, these will be used. Example:
         * 
         *  ```js
         *  data: [0, 5, 3, 5]
         *  ```
         * 
         * 2.  An array of arrays with 2 values. In this case, the values correspond
         * to `x,y`. If the first value is a string, it is applied as the name
         * of the point, and the `x` value is inferred.
         * 
         *  ```js
         *     data: [
         *         [0, 9],
         *         [1, 7],
         *         [2, 6]
         *     ]
         *  ```
         * 
         * 3.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.area.turboThreshold),
         * this option is not available.
         * 
         *  ```js
         *     data: [{
         *         x: 1,
         *         y: 9,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         y: 6,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         * 
         * @type {Array<Object|Array|Number>}
         * @extends series.line.data
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
         * @product highcharts highstock
         * @apioption series.area.data
         */

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var pick = H.pick,
            seriesType = H.seriesType;

        /**
         * A spline series is a special type of line series, where the segments between
         * the data points are smoothed.
         *
         * @sample {highcharts} highcharts/demo/spline-irregular-time/ Spline chart
         * @sample {highstock} stock/demo/spline/ Spline chart
         * 
         * @extends plotOptions.series
         * @excluding step
         * @product highcharts highstock
         * @apioption plotOptions.spline
         */

        /**
         * Spline series type.
         * @constructor seriesTypes.spline
         * @extends {Series}
         */
        seriesType('spline', 'line', {}, /** @lends seriesTypes.spline.prototype */ {
            /**
             * Get the spline segment from a given point's previous neighbour to the
             * given point
             */
            getPointSpline: function(points, point, i) {
                var
                    // 1 means control points midway between points, 2 means 1/3 from
                    // the point, 3 is 1/4 etc
                    smoothing = 1.5,
                    denom = smoothing + 1,
                    plotX = point.plotX,
                    plotY = point.plotY,
                    lastPoint = points[i - 1],
                    nextPoint = points[i + 1],
                    leftContX,
                    leftContY,
                    rightContX,
                    rightContY,
                    ret;

                function doCurve(otherPoint) {
                    return otherPoint &&
                        !otherPoint.isNull &&
                        otherPoint.doCurve !== false &&
                        !point.isCliff; // #6387, area splines next to null
                }

                // Find control points
                if (doCurve(lastPoint) && doCurve(nextPoint)) {
                    var lastX = lastPoint.plotX,
                        lastY = lastPoint.plotY,
                        nextX = nextPoint.plotX,
                        nextY = nextPoint.plotY,
                        correction = 0;

                    leftContX = (smoothing * plotX + lastX) / denom;
                    leftContY = (smoothing * plotY + lastY) / denom;
                    rightContX = (smoothing * plotX + nextX) / denom;
                    rightContY = (smoothing * plotY + nextY) / denom;

                    // Have the two control points make a straight line through main
                    // point
                    if (rightContX !== leftContX) { // #5016, division by zero
                        correction = ((rightContY - leftContY) * (rightContX - plotX)) /
                            (rightContX - leftContX) + plotY - rightContY;
                    }

                    leftContY += correction;
                    rightContY += correction;

                    // to prevent false extremes, check that control points are between
                    // neighbouring points' y values
                    if (leftContY > lastY && leftContY > plotY) {
                        leftContY = Math.max(lastY, plotY);
                        // mirror of left control point
                        rightContY = 2 * plotY - leftContY;
                    } else if (leftContY < lastY && leftContY < plotY) {
                        leftContY = Math.min(lastY, plotY);
                        rightContY = 2 * plotY - leftContY;
                    }
                    if (rightContY > nextY && rightContY > plotY) {
                        rightContY = Math.max(nextY, plotY);
                        leftContY = 2 * plotY - rightContY;
                    } else if (rightContY < nextY && rightContY < plotY) {
                        rightContY = Math.min(nextY, plotY);
                        leftContY = 2 * plotY - rightContY;
                    }

                    // record for drawing in next point
                    point.rightContX = rightContX;
                    point.rightContY = rightContY;


                }

                // Visualize control points for debugging
                /*
                if (leftContX) {
                	this.chart.renderer.circle(
                			leftContX + this.chart.plotLeft,
                			leftContY + this.chart.plotTop,
                			2
                		)
                		.attr({
                			stroke: 'red',
                			'stroke-width': 2,
                			fill: 'none',
                			zIndex: 9
                		})
                		.add();
                	this.chart.renderer.path(['M', leftContX + this.chart.plotLeft,
                		leftContY + this.chart.plotTop,
                		'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
                		.attr({
                			stroke: 'red',
                			'stroke-width': 2,
                			zIndex: 9
                		})
                		.add();
                }
                if (rightContX) {
                	this.chart.renderer.circle(
                			rightContX + this.chart.plotLeft,
                			rightContY + this.chart.plotTop,
                			2
                		)
                		.attr({
                			stroke: 'green',
                			'stroke-width': 2,
                			fill: 'none',
                			zIndex: 9
                		})
                		.add();
                	this.chart.renderer.path(['M', rightContX + this.chart.plotLeft,
                		rightContY + this.chart.plotTop,
                		'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])
                		.attr({
                			stroke: 'green',
                			'stroke-width': 2,
                			zIndex: 9
                		})
                		.add();
                }
                // */
                ret = [
                    'C',
                    pick(lastPoint.rightContX, lastPoint.plotX),
                    pick(lastPoint.rightContY, lastPoint.plotY),
                    pick(leftContX, plotX),
                    pick(leftContY, plotY),
                    plotX,
                    plotY
                ];
                // reset for updating series later
                lastPoint.rightContX = lastPoint.rightContY = null;
                return ret;
            }
        });

        /**
         * A `spline` series. If the [type](#series.spline.type) option is
         * not specified, it is inherited from [chart.type](#chart.type).
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * spline](#plotOptions.spline).
         * 
         * @type {Object}
         * @extends series,plotOptions.spline
         * @excluding dataParser,dataURL
         * @product highcharts highstock
         * @apioption series.spline
         */

        /**
         * An array of data points for the series. For the `spline` series type,
         * points can be given in the following ways:
         * 
         * 1.  An array of numerical values. In this case, the numerical values
         * will be interpreted as `y` options. The `x` values will be automatically
         * calculated, either starting at 0 and incremented by 1, or from `pointStart`
         * and `pointInterval` given in the series options. If the axis has
         * categories, these will be used. Example:
         * 
         *  ```js
         *  data: [0, 5, 3, 5]
         *  ```
         * 
         * 2.  An array of arrays with 2 values. In this case, the values correspond
         * to `x,y`. If the first value is a string, it is applied as the name
         * of the point, and the `x` value is inferred.
         * 
         *  ```js
         *     data: [
         *         [0, 9],
         *         [1, 2],
         *         [2, 8]
         *     ]
         *  ```
         * 
         * 3.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.spline.turboThreshold),
         * this option is not available.
         * 
         *  ```js
         *     data: [{
         *         x: 1,
         *         y: 9,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         y: 0,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         * 
         * @type {Array<Object|Array|Number>}
         * @extends series.line.data
         * @sample {highcharts} highcharts/chart/reflow-true/
         *         Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/
         *         Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
         *         Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/
         *         Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/
         *         Config objects
         * @product highcharts highstock
         * @apioption series.spline.data
         */

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var areaProto = H.seriesTypes.area.prototype,
            defaultPlotOptions = H.defaultPlotOptions,
            LegendSymbolMixin = H.LegendSymbolMixin,
            seriesType = H.seriesType;
        /**
         * AreaSplineSeries object
         */
        /**
         * The area spline series is an area series where the graph between the points
         * is smoothed into a spline.
         * 
         * @extends plotOptions.area
         * @excluding step
         * @sample {highcharts} highcharts/demo/areaspline/ Area spline chart
         * @sample {highstock} stock/demo/areaspline/ Area spline chart
         * @product highcharts highstock
         * @apioption plotOptions.areaspline
         */
        seriesType('areaspline', 'spline', defaultPlotOptions.area, {
            getStackPoints: areaProto.getStackPoints,
            getGraphPath: areaProto.getGraphPath,
            drawGraph: areaProto.drawGraph,
            drawLegendSymbol: LegendSymbolMixin.drawRectangle
        });
        /**
         * A `areaspline` series. If the [type](#series.areaspline.type) option
         * is not specified, it is inherited from [chart.type](#chart.type).
         * 
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * areaspline](#plotOptions.areaspline).
         * 
         * @type {Object}
         * @extends series,plotOptions.areaspline
         * @excluding dataParser,dataURL
         * @product highcharts highstock
         * @apioption series.areaspline
         */


        /**
         * An array of data points for the series. For the `areaspline` series
         * type, points can be given in the following ways:
         * 
         * 1.  An array of numerical values. In this case, the numerical values
         * will be interpreted as `y` options. The `x` values will be automatically
         * calculated, either starting at 0 and incremented by 1, or from `pointStart`
         * and `pointInterval` given in the series options. If the axis has
         * categories, these will be used. Example:
         * 
         *  ```js
         *  data: [0, 5, 3, 5]
         *  ```
         * 
         * 2.  An array of arrays with 2 values. In this case, the values correspond
         * to `x,y`. If the first value is a string, it is applied as the name
         * of the point, and the `x` value is inferred.
         * 
         *  ```js
         *     data: [
         *         [0, 10],
         *         [1, 9],
         *         [2, 3]
         *     ]
         *  ```
         * 
         * 3.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.areaspline.turboThreshold),
         * this option is not available.
         * 
         *  ```js
         *     data: [{
         *         x: 1,
         *         y: 4,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         y: 4,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         * 
         * @type {Array<Object|Array|Number>}
         * @extends series.line.data
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
         * @product highcharts highstock
         * @apioption series.areaspline.data
         */



    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var animObject = H.animObject,
            color = H.color,
            each = H.each,
            extend = H.extend,
            isNumber = H.isNumber,
            LegendSymbolMixin = H.LegendSymbolMixin,
            merge = H.merge,
            noop = H.noop,
            pick = H.pick,
            Series = H.Series,
            seriesType = H.seriesType,
            svg = H.svg;
        /**
         * The column series type.
         *
         * @constructor seriesTypes.column
         * @augments Series
         */

        /**
         * Column series display one column per value along an X axis.
         *
         * @sample {highcharts} highcharts/demo/column-basic/ Column chart
         * @sample {highstock} stock/demo/column/ Column chart
         *
         * @extends {plotOptions.line}
         * @product highcharts highstock
         * @excluding connectNulls,dashStyle,gapSize,gapUnit,linecap,lineWidth,marker,
         *          connectEnds,step
         * @optionparent plotOptions.column
         */
        seriesType('column', 'line', {

            /**
             * The corner radius of the border surrounding each column or bar.
             *
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/column-borderradius/
             *         Rounded columns
             * @default 0
             * @product highcharts highstock
             */
            borderRadius: 0,

            /**
             * The width of the border surrounding each column or bar.
             *
             * In styled mode, the stroke width can be set with the `.highcharts-point`
             * rule.
             *
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/column-borderwidth/
             *         2px black border
             * @default 1
             * @product highcharts highstock
             * @apioption plotOptions.column.borderWidth
             */

            /**
             * When using automatic point colors pulled from the `options.colors`
             * collection, this option determines whether the chart should receive
             * one color per series or one color per point.
             *
             * @type {Boolean}
             * @see [series colors](#plotOptions.column.colors)
             * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-false/
             *         False by default
             * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-true/
             *         True
             * @default false
             * @since 2.0
             * @product highcharts highstock
             * @apioption plotOptions.column.colorByPoint
             */

            /**
             * A series specific or series type specific color set to apply instead
             * of the global [colors](#colors) when [colorByPoint](#plotOptions.
             * column.colorByPoint) is true.
             *
             * @type {Array<Color>}
             * @since 3.0
             * @product highcharts highstock
             * @apioption plotOptions.column.colors
             */

            /**
             * When true, each column edge is rounded to its nearest pixel in order
             * to render sharp on screen. In some cases, when there are a lot of
             * densely packed columns, this leads to visible difference in column
             * widths or distance between columns. In these cases, setting `crisp`
             * to `false` may look better, even though each column is rendered
             * blurry.
             *
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/column-crisp-false/
             *         Crisp is false
             * @default true
             * @since 5.0.10
             * @product highcharts highstock
             */
            crisp: true,

            /**
             * Padding between each value groups, in x axis units.
             *
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/column-grouppadding-default/
             *         0.2 by default
             * @sample {highcharts} highcharts/plotoptions/column-grouppadding-none/
             *         No group padding - all columns are evenly spaced
             * @default 0.2
             * @product highcharts highstock
             */
            groupPadding: 0.2,

            /**
             * Whether to group non-stacked columns or to let them render independent
             * of each other. Non-grouped columns will be laid out individually
             * and overlap each other.
             *
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/column-grouping-false/
             *         Grouping disabled
             * @sample {highstock} highcharts/plotoptions/column-grouping-false/
             *         Grouping disabled
             * @default true
             * @since 2.3.0
             * @product highcharts highstock
             * @apioption plotOptions.column.grouping
             */

            marker: null, // point options are specified in the base options

            /**
             * The maximum allowed pixel width for a column, translated to the height
             * of a bar in a bar chart. This prevents the columns from becoming
             * too wide when there is a small number of points in the chart.
             *
             * @type {Number}
             * @see [pointWidth](#plotOptions.column.pointWidth)
             * @sample {highcharts} highcharts/plotoptions/column-maxpointwidth-20/
             *         Limited to 50
             * @sample {highstock} highcharts/plotoptions/column-maxpointwidth-20/
             *         Limited to 50
             * @default null
             * @since 4.1.8
             * @product highcharts highstock
             * @apioption plotOptions.column.maxPointWidth
             */

            /**
             * Padding between each column or bar, in x axis units.
             *
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/column-pointpadding-default/
             *         0.1 by default
             * @sample {highcharts} highcharts/plotoptions/column-pointpadding-025/
             *         0.25
             * @sample {highcharts} highcharts/plotoptions/column-pointpadding-none/
             *         0 for tightly packed columns
             * @default 0.1
             * @product highcharts highstock
             */
            pointPadding: 0.1,

            /**
             * A pixel value specifying a fixed width for each column or bar. When
             * `null`, the width is calculated from the `pointPadding` and
             * `groupPadding`.
             *
             * @type {Number}
             * @see [maxPointWidth](#plotOptions.column.maxPointWidth)
             * @sample {highcharts} highcharts/plotoptions/column-pointwidth-20/
             *         20px wide columns regardless of chart width or the amount of data
             *         points
             * @default null
             * @since 1.2.5
             * @product highcharts highstock
             * @apioption plotOptions.column.pointWidth
             */

            /**
             * The minimal height for a column or width for a bar. By default,
             * 0 values are not shown. To visualize a 0 (or close to zero) point,
             * set the minimal point length to a pixel value like 3\. In stacked
             * column charts, minPointLength might not be respected for tightly
             * packed values.
             *
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/column-minpointlength/
             *         Zero base value
             * @sample {highcharts} highcharts/plotoptions/column-minpointlength-pos-and-neg/
             *         Positive and negative close to zero values
             * @default 0
             * @product highcharts highstock
             */
            minPointLength: 0,

            /**
             * When the series contains less points than the crop threshold, all
             * points are drawn, event if the points fall outside the visible plot
             * area at the current zoom. The advantage of drawing all points (including
             * markers and columns), is that animation is performed on updates.
             * On the other hand, when the series contains more points than the
             * crop threshold, the series data is cropped to only contain points
             * that fall within the plot area. The advantage of cropping away invisible
             * points is to increase performance on large series. .
             *
             * @type {Number}
             * @default 50
             * @product highcharts highstock
             */
            cropThreshold: 50,

            /**
             * The X axis range that each point is valid for. This determines the
             * width of the column. On a categorized axis, the range will be 1
             * by default (one category unit). On linear and datetime axes, the
             * range will be computed as the distance between the two closest data
             * points.
             *
             * The default `null` means it is computed automatically, but this option
             * can be used to override the automatic value.
             *
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/column-pointrange/
             *         Set the point range to one day on a data set with one week
             *         between the points
             * @default null
             * @since 2.3
             * @product highcharts highstock
             */
            pointRange: null,

            states: {

                /**
                 * @extends plotOptions.series.states.hover
                 * @excluding halo,lineWidth,lineWidthPlus,marker
                 * @product highcharts highstock
                 */
                hover: {

                    /**
                     * @ignore-option
                     */
                    halo: false,
                    /**
                     * A specific border color for the hovered point. Defaults to
                     * inherit the normal state border color.
                     *
                     * @type {Color}
                     * @product highcharts
                     * @apioption plotOptions.column.states.hover.borderColor
                     */

                    /**
                     * A specific color for the hovered point.
                     *
                     * @type {Color}
                     * @default undefined
                     * @product highcharts
                     * @apioption plotOptions.column.states.hover.color
                     */



                    /**
                     * How much to brighten the point on interaction. Requires the main
                     * color to be defined in hex or rgb(a) format.
                     *
                     * In styled mode, the hover brightening is by default replaced
                     * with a fill-opacity set in the `.highcharts-point:hover` rule.
                     *
                     * @type {Number}
                     * @sample {highcharts} highcharts/plotoptions/column-states-hover-brightness/
                     *         Brighten by 0.5
                     * @default 0.1
                     * @product highcharts highstock
                     */
                    brightness: 0.1


                },


                select: {
                    color: '#cccccc',
                    borderColor: '#000000'
                }

            },

            dataLabels: {
                align: null, // auto
                verticalAlign: null, // auto
                y: null
            },

            /**
             * When this is true, the series will not cause the Y axis to cross
             * the zero plane (or [threshold](#plotOptions.series.threshold) option)
             * unless the data actually crosses the plane.
             *
             * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
             * 3 will make the Y axis show negative values according to the `minPadding`
             * option. If `softThreshold` is `true`, the Y axis starts at 0.
             *
             * @type {Boolean}
             * @default {highcharts} true
             * @default {highstock} false
             * @since 4.1.9
             * @product highcharts highstock
             */
            softThreshold: false,

            // false doesn't work well: http://jsfiddle.net/highcharts/hz8fopan/14/
            /**	@ignore */
            startFromThreshold: true,

            stickyTracking: false,

            tooltip: {
                distance: 6
            },

            /**
             * The Y axis value to serve as the base for the columns, for distinguishing
             * between values above and below a threshold. If `null`, the columns
             * extend from the padding Y axis minimum.
             *
             * @type {Number}
             * @default 0
             * @since 2.0
             * @product highcharts
             */
            threshold: 0,


            /**
             * The color of the border surrounding each column or bar.
             *
             * In styled mode, the border stroke can be set with the `.highcharts-point`
             * rule.
             *
             * @type {Color}
             * @sample {highcharts} highcharts/plotoptions/column-bordercolor/
             *         Dark gray border
             * @default #ffffff
             * @product highcharts highstock
             */
            borderColor: '#ffffff'
            // borderWidth: 1


        }, /** @lends seriesTypes.column.prototype */ {
            cropShoulder: 0,
            // When tooltip is not shared, this series (and derivatives) requires direct
            // touch/hover. KD-tree does not apply.
            directTouch: true,
            trackerGroups: ['group', 'dataLabelsGroup'],
            // use separate negative stacks, unlike area stacks where a negative point
            // is substracted from previous (#1910)
            negStacks: true,

            /**
             * Initialize the series. Extends the basic Series.init method by
             * marking other series of the same type as dirty.
             *
             * @function #init
             * @memberOf seriesTypes.column
             *
             */
            init: function() {
                Series.prototype.init.apply(this, arguments);

                var series = this,
                    chart = series.chart;

                // if the series is added dynamically, force redraw of other
                // series affected by a new column
                if (chart.hasRendered) {
                    each(chart.series, function(otherSeries) {
                        if (otherSeries.type === series.type) {
                            otherSeries.isDirty = true;
                        }
                    });
                }
            },

            /**
             * Return the width and x offset of the columns adjusted for grouping,
             * groupPadding, pointPadding, pointWidth etc.
             */
            getColumnMetrics: function() {

                var series = this,
                    options = series.options,
                    xAxis = series.xAxis,
                    yAxis = series.yAxis,
                    reversedXAxis = xAxis.reversed,
                    stackKey,
                    stackGroups = {},
                    columnCount = 0;

                // Get the total number of column type series. This is called on every
                // series. Consider moving this logic to a chart.orderStacks() function
                // and call it on init, addSeries and removeSeries
                if (options.grouping === false) {
                    columnCount = 1;
                } else {
                    each(series.chart.series, function(otherSeries) {
                        var otherOptions = otherSeries.options,
                            otherYAxis = otherSeries.yAxis,
                            columnIndex;
                        if (
                            otherSeries.type === series.type &&
                            (
                                otherSeries.visible ||
                                !series.chart.options.chart.ignoreHiddenSeries
                            ) &&
                            yAxis.len === otherYAxis.len &&
                            yAxis.pos === otherYAxis.pos
                        ) { // #642, #2086
                            if (otherOptions.stacking) {
                                stackKey = otherSeries.stackKey;
                                if (stackGroups[stackKey] === undefined) {
                                    stackGroups[stackKey] = columnCount++;
                                }
                                columnIndex = stackGroups[stackKey];
                            } else if (otherOptions.grouping !== false) { // #1162
                                columnIndex = columnCount++;
                            }
                            otherSeries.columnIndex = columnIndex;
                        }
                    });
                }

                var categoryWidth = Math.min(
                        Math.abs(xAxis.transA) * (
                            xAxis.ordinalSlope ||
                            options.pointRange ||
                            xAxis.closestPointRange ||
                            xAxis.tickInterval ||
                            1
                        ), // #2610
                        xAxis.len // #1535
                    ),
                    groupPadding = categoryWidth * options.groupPadding,
                    groupWidth = categoryWidth - 2 * groupPadding,
                    pointOffsetWidth = groupWidth / (columnCount || 1),
                    pointWidth = Math.min(
                        options.maxPointWidth || xAxis.len,
                        pick(
                            options.pointWidth,
                            pointOffsetWidth * (1 - 2 * options.pointPadding)
                        )
                    ),
                    pointPadding = (pointOffsetWidth - pointWidth) / 2,
                    // #1251, #3737
                    colIndex = (series.columnIndex || 0) + (reversedXAxis ? 1 : 0),
                    pointXOffset =
                    pointPadding +
                    (
                        groupPadding +
                        colIndex * pointOffsetWidth -
                        (categoryWidth / 2)
                    ) * (reversedXAxis ? -1 : 1);

                // Save it for reading in linked series (Error bars particularly)
                series.columnMetrics = {
                    width: pointWidth,
                    offset: pointXOffset
                };
                return series.columnMetrics;

            },

            /**
             * Make the columns crisp. The edges are rounded to the nearest full pixel.
             */
            crispCol: function(x, y, w, h) {
                var chart = this.chart,
                    borderWidth = this.borderWidth,
                    xCrisp = -(borderWidth % 2 ? 0.5 : 0),
                    yCrisp = borderWidth % 2 ? 0.5 : 1,
                    right,
                    bottom,
                    fromTop;

                if (chart.inverted && chart.renderer.isVML) {
                    yCrisp += 1;
                }

                // Horizontal. We need to first compute the exact right edge, then round
                // it and compute the width from there.
                if (this.options.crisp) {
                    right = Math.round(x + w) + xCrisp;
                    x = Math.round(x) + xCrisp;
                    w = right - x;
                }

                // Vertical
                bottom = Math.round(y + h) + yCrisp;
                fromTop = Math.abs(y) <= 0.5 && bottom > 0.5; // #4504, #4656
                y = Math.round(y) + yCrisp;
                h = bottom - y;

                // Top edges are exceptions
                if (fromTop && h) { // #5146
                    y -= 1;
                    h += 1;
                }

                return {
                    x: x,
                    y: y,
                    width: w,
                    height: h
                };
            },

            /**
             * Translate each point to the plot area coordinate system and find shape
             * positions
             */
            translate: function() {
                var series = this,
                    chart = series.chart,
                    options = series.options,
                    dense = series.dense =
                    series.closestPointRange * series.xAxis.transA < 2,
                    borderWidth = series.borderWidth = pick(
                        options.borderWidth,
                        dense ? 0 : 1 // #3635
                    ),
                    yAxis = series.yAxis,
                    threshold = options.threshold,
                    translatedThreshold = series.translatedThreshold =
                    yAxis.getThreshold(threshold),
                    minPointLength = pick(options.minPointLength, 5),
                    metrics = series.getColumnMetrics(),
                    pointWidth = metrics.width,
                    // postprocessed for border width
                    seriesBarW = series.barW =
                    Math.max(pointWidth, 1 + 2 * borderWidth),
                    pointXOffset = series.pointXOffset = metrics.offset;

                if (chart.inverted) {
                    translatedThreshold -= 0.5; // #3355
                }

                // When the pointPadding is 0, we want the columns to be packed tightly,
                // so we allow individual columns to have individual sizes. When
                // pointPadding is greater, we strive for equal-width columns (#2694).
                if (options.pointPadding) {
                    seriesBarW = Math.ceil(seriesBarW);
                }

                Series.prototype.translate.apply(series);

                // Record the new values
                each(series.points, function(point) {
                    var yBottom = pick(point.yBottom, translatedThreshold),
                        safeDistance = 999 + Math.abs(yBottom),
                        plotY = Math.min(
                            Math.max(-safeDistance, point.plotY),
                            yAxis.len + safeDistance
                        ), // Don't draw too far outside plot area (#1303, #2241, #4264)
                        barX = point.plotX + pointXOffset,
                        barW = seriesBarW,
                        barY = Math.min(plotY, yBottom),
                        up,
                        barH = Math.max(plotY, yBottom) - barY;

                    // Handle options.minPointLength
                    if (minPointLength && Math.abs(barH) < minPointLength) {
                        barH = minPointLength;
                        up = (!yAxis.reversed && !point.negative) ||
                            (yAxis.reversed && point.negative);

                        // Reverse zeros if there's no positive value in the series
                        // in visible range (#7046)
                        if (
                            point.y === threshold &&
                            series.dataMax <= threshold &&
                            yAxis.min < threshold // and if there's room for it (#7311)
                        ) {
                            up = !up;
                        }

                        // If stacked...
                        barY = Math.abs(barY - translatedThreshold) > minPointLength ?
                            // ...keep position
                            yBottom - minPointLength :
                            // #1485, #4051
                            translatedThreshold - (up ? minPointLength : 0);
                    }

                    // Cache for access in polar
                    point.barX = barX;
                    point.pointWidth = pointWidth;

                    // Fix the tooltip on center of grouped columns (#1216, #424, #3648)
                    point.tooltipPos = chart.inverted ? [
                        yAxis.len + yAxis.pos - chart.plotLeft - plotY,
                        series.xAxis.len - barX - barW / 2, barH
                    ] : [barX + barW / 2, plotY + yAxis.pos - chart.plotTop, barH];

                    // Register shape type and arguments to be used in drawPoints
                    point.shapeType = 'rect';
                    point.shapeArgs = series.crispCol.apply(
                        series,
                        point.isNull ?
                        // #3169, drilldown from null must have a position to work
                        // from #6585, dataLabel should be placed on xAxis, not
                        // floating in the middle of the chart
                        [barX, translatedThreshold, barW, 0] : [barX, barY, barW, barH]
                    );
                });

            },

            getSymbol: noop,

            /**
             * Use a solid rectangle like the area series types
             */
            drawLegendSymbol: LegendSymbolMixin.drawRectangle,


            /**
             * Columns have no graph
             */
            drawGraph: function() {
                this.group[
                    this.dense ? 'addClass' : 'removeClass'
                ]('highcharts-dense-data');
            },


            /**
             * Get presentational attributes
             */
            pointAttribs: function(point, state) {
                var options = this.options,
                    stateOptions,
                    ret,
                    p2o = this.pointAttrToOptions || {},
                    strokeOption = p2o.stroke || 'borderColor',
                    strokeWidthOption = p2o['stroke-width'] || 'borderWidth',
                    fill = (point && point.color) || this.color,
                    stroke = (point && point[strokeOption]) || options[strokeOption] ||
                    this.color || fill, // set to fill when borderColor null
                    strokeWidth = (point && point[strokeWidthOption]) ||
                    options[strokeWidthOption] || this[strokeWidthOption] || 0,
                    dashstyle = options.dashStyle,
                    zone,
                    brightness;

                // Handle zone colors
                if (point && this.zones.length) {
                    zone = point.getZone();
                    // When zones are present, don't use point.color (#4267). Changed
                    // order (#6527)
                    fill = point.options.color || (zone && zone.color) || this.color;
                }

                // Select or hover states
                if (state) {
                    stateOptions = merge(
                        options.states[state],
                        // #6401
                        point.options.states && point.options.states[state] || {}
                    );
                    brightness = stateOptions.brightness;
                    fill = stateOptions.color ||
                        (
                            brightness !== undefined &&
                            color(fill).brighten(stateOptions.brightness).get()
                        ) ||
                        fill;
                    stroke = stateOptions[strokeOption] || stroke;
                    strokeWidth = stateOptions[strokeWidthOption] || strokeWidth;
                    dashstyle = stateOptions.dashStyle || dashstyle;
                }

                ret = {
                    'fill': fill,
                    'stroke': stroke,
                    'stroke-width': strokeWidth
                };

                if (dashstyle) {
                    ret.dashstyle = dashstyle;
                }

                return ret;
            },


            /**
             * Draw the columns. For bars, the series.group is rotated, so the same
             * coordinates apply for columns and bars. This method is inherited by
             * scatter series.
             */
            drawPoints: function() {
                var series = this,
                    chart = this.chart,
                    options = series.options,
                    renderer = chart.renderer,
                    animationLimit = options.animationLimit || 250,
                    shapeArgs;

                // draw the columns
                each(series.points, function(point) {
                    var plotY = point.plotY,
                        graphic = point.graphic;

                    if (isNumber(plotY) && point.y !== null) {
                        shapeArgs = point.shapeArgs;

                        if (graphic) { // update
                            graphic[
                                chart.pointCount < animationLimit ? 'animate' : 'attr'
                            ](
                                merge(shapeArgs)
                            );

                        } else {
                            point.graphic = graphic =
                                renderer[point.shapeType](shapeArgs)
                                .add(point.group || series.group);
                        }

                        // Border radius is not stylable (#6900)
                        if (options.borderRadius) {
                            graphic.attr({
                                r: options.borderRadius
                            });
                        }


                        // Presentational
                        graphic
                            .attr(series.pointAttribs(
                                point,
                                point.selected && 'select'
                            ))
                            .shadow(
                                options.shadow,
                                null,
                                options.stacking && !options.borderRadius
                            );


                        graphic.addClass(point.getClassName(), true);


                    } else if (graphic) {
                        point.graphic = graphic.destroy(); // #1269
                    }
                });
            },

            /**
             * Animate the column heights one by one from zero
             * @param {Boolean} init Whether to initialize the animation or run it
             */
            animate: function(init) {
                var series = this,
                    yAxis = this.yAxis,
                    options = series.options,
                    inverted = this.chart.inverted,
                    attr = {},
                    translateProp = inverted ? 'translateX' : 'translateY',
                    translateStart,
                    translatedThreshold;

                if (svg) { // VML is too slow anyway
                    if (init) {
                        attr.scaleY = 0.001;
                        translatedThreshold = Math.min(
                            yAxis.pos + yAxis.len,
                            Math.max(yAxis.pos, yAxis.toPixels(options.threshold))
                        );
                        if (inverted) {
                            attr.translateX = translatedThreshold - yAxis.len;
                        } else {
                            attr.translateY = translatedThreshold;
                        }
                        series.group.attr(attr);

                    } else { // run the animation
                        translateStart = series.group.attr(translateProp);
                        series.group.animate({
                                scaleY: 1
                            },
                            extend(animObject(series.options.animation), {
                                // Do the scale synchronously to ensure smooth updating
                                // (#5030, #7228)
                                step: function(val, fx) {

                                    attr[translateProp] =
                                        translateStart +
                                        fx.pos * (yAxis.pos - translateStart);
                                    series.group.attr(attr);
                                }
                            }));

                        // delete this function to allow it only once
                        series.animate = null;
                    }
                }
            },

            /**
             * Remove this series from the chart
             */
            remove: function() {
                var series = this,
                    chart = series.chart;

                // column and bar series affects other series of the same type
                // as they are either stacked or grouped
                if (chart.hasRendered) {
                    each(chart.series, function(otherSeries) {
                        if (otherSeries.type === series.type) {
                            otherSeries.isDirty = true;
                        }
                    });
                }

                Series.prototype.remove.apply(series, arguments);
            }
        });


        /**
         * A `column` series. If the [type](#series.column.type) option is
         * not specified, it is inherited from [chart.type](#chart.type).
         *
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * column](#plotOptions.column).
         *
         * @type {Object}
         * @extends series,plotOptions.column
         * @excluding dataParser,dataURL,marker
         * @product highcharts highstock
         * @apioption series.column
         */

        /**
         * An array of data points for the series. For the `column` series type,
         * points can be given in the following ways:
         *
         * 1.  An array of numerical values. In this case, the numerical values
         * will be interpreted as `y` options. The `x` values will be automatically
         * calculated, either starting at 0 and incremented by 1, or from `pointStart`
         * and `pointInterval` given in the series options. If the axis has
         * categories, these will be used. Example:
         *
         *  ```js
         *  data: [0, 5, 3, 5]
         *  ```
         *
         * 2.  An array of arrays with 2 values. In this case, the values correspond
         * to `x,y`. If the first value is a string, it is applied as the name
         * of the point, and the `x` value is inferred.
         *
         *  ```js
         *     data: [
         *         [0, 6],
         *         [1, 2],
         *         [2, 6]
         *     ]
         *  ```
         *
         * 3.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.column.turboThreshold),
         * this option is not available.
         *
         *  ```js
         *     data: [{
         *         x: 1,
         *         y: 9,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         y: 6,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         *
         * @type {Array<Object|Array|Number>}
         * @extends series.line.data
         * @excluding marker
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/
         *         Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
         *         Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/
         *         Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/
         *         Config objects
         * @product highcharts highstock
         * @apioption series.column.data
         */


    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */

        var seriesType = H.seriesType;

        /**
         * The Bar series class
         */
        seriesType('bar', 'column', null, {
            inverted: true
        });
        /**
         * A bar series is a special type of column series where the columns are
         * horizontal.
         *
         * @sample highcharts/demo/bar-basic/ Bar chart
         * @extends {plotOptions.column}
         * @product highcharts
         * @optionparent plotOptions.bar
         */


        /**
         * A `bar` series. If the [type](#series.bar.type) option is not specified,
         * it is inherited from [chart.type](#chart.type).
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * bar](#plotOptions.bar).
         * 
         * @type {Object}
         * @extends series,plotOptions.bar
         * @excluding dataParser,dataURL
         * @product highcharts
         * @apioption series.bar
         */

        /**
         * An array of data points for the series. For the `bar` series type,
         * points can be given in the following ways:
         * 
         * 1.  An array of numerical values. In this case, the numerical values
         * will be interpreted as `y` options. The `x` values will be automatically
         * calculated, either starting at 0 and incremented by 1, or from `pointStart`
         * and `pointInterval` given in the series options. If the axis has
         * categories, these will be used. Example:
         * 
         *  ```js
         *  data: [0, 5, 3, 5]
         *  ```
         * 
         * 2.  An array of arrays with 2 values. In this case, the values correspond
         * to `x,y`. If the first value is a string, it is applied as the name
         * of the point, and the `x` value is inferred.
         * 
         *  ```js
         *     data: [
         *         [0, 5],
         *         [1, 10],
         *         [2, 3]
         *     ]
         *  ```
         * 
         * 3.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.bar.turboThreshold),
         * this option is not available.
         * 
         *  ```js
         *     data: [{
         *         x: 1,
         *         y: 1,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         y: 10,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         * 
         * @type {Array<Object|Array|Number>}
         * @extends series.column.data
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
         * @product highcharts
         * @apioption series.bar.data
         */

        /**
         * Alignment of the data label relative to the data point.
         * 
         * @type {String}
         * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/
         *         Data labels inside the bar
         * @default left
         * @product highcharts
         * @apioption plotOptions.bar.dataLabels.align
         */

        /**
         * The x position of the data label relative to the data point.
         * 
         * @type {Number}
         * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/
         *         Data labels inside the bar
         * @default 5
         * @product highcharts
         * @apioption plotOptions.bar.dataLabels.x
         */

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var Series = H.Series,
            seriesType = H.seriesType;

        /**
         * A scatter plot uses cartesian coordinates to display values for two variables
         * for a set of data.
         *
         * @sample {highcharts} highcharts/demo/scatter/ Scatter plot
         * 
         * @extends {plotOptions.line}
         * @product highcharts highstock
         * @optionparent plotOptions.scatter
         */
        seriesType('scatter', 'line', {

            /**
             * The width of the line connecting the data points.
             * 
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-none/
             *         0 by default
             * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-1/
             *         1px
             * @default 0
             * @product highcharts highstock
             */
            lineWidth: 0,

            findNearestPointBy: 'xy',
            marker: {
                enabled: true // Overrides auto-enabling in line series (#3647)
            },

            /**
             * Sticky tracking of mouse events. When true, the `mouseOut` event
             * on a series isn't triggered until the mouse moves over another series,
             * or out of the plot area. When false, the `mouseOut` event on a series
             * is triggered when the mouse leaves the area around the series' graph
             * or markers. This also implies the tooltip. When `stickyTracking`
             * is false and `tooltip.shared` is false, the tooltip will be hidden
             * when moving the mouse between series.
             * 
             * @type {Boolean}
             * @default false
             * @product highcharts highstock
             * @apioption plotOptions.scatter.stickyTracking
             */

            /**
             * A configuration object for the tooltip rendering of each single
             * series. Properties are inherited from <a class="internal">#tooltip</a>.
             * Overridable properties are `headerFormat`, `pointFormat`, `yDecimals`,
             * `xDateFormat`, `yPrefix` and `ySuffix`. Unlike other series, in
             * a scatter plot the series.name by default shows in the headerFormat
             * and point.x and point.y in the pointFormat.
             * 
             * @product highcharts highstock
             */
            tooltip: {

                headerFormat: '<span style="color:{point.color}">\u25CF</span> ' +
                    '<span style="font-size: 0.85em"> {series.name}</span><br/>',


                pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>'
            }

            // Prototype members
        }, {
            sorted: false,
            requireSorting: false,
            noSharedTooltip: true,
            trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],
            takeOrdinalPosition: false, // #2342
            drawGraph: function() {
                if (this.options.lineWidth) {
                    Series.prototype.drawGraph.call(this);
                }
            }
        });

        /**
         * A `scatter` series. If the [type](#series.scatter.type) option is
         * not specified, it is inherited from [chart.type](#chart.type).
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * scatter](#plotOptions.scatter).
         * 
         * @type {Object}
         * @extends series,plotOptions.scatter
         * @excluding dataParser,dataURL,stack
         * @product highcharts highstock
         * @apioption series.scatter
         */

        /**
         * An array of data points for the series. For the `scatter` series
         * type, points can be given in the following ways:
         * 
         * 1.  An array of numerical values. In this case, the numerical values
         * will be interpreted as `y` options. The `x` values will be automatically
         * calculated, either starting at 0 and incremented by 1, or from `pointStart`
         * and `pointInterval` given in the series options. If the axis has
         * categories, these will be used. Example:
         * 
         *  ```js
         *  data: [0, 5, 3, 5]
         *  ```
         * 
         * 2.  An array of arrays with 2 values. In this case, the values correspond
         * to `x,y`. If the first value is a string, it is applied as the name
         * of the point, and the `x` value is inferred.
         * 
         *  ```js
         *     data: [
         *         [0, 0],
         *         [1, 8],
         *         [2, 9]
         *     ]
         *  ```
         * 
         * 3.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.scatter.turboThreshold),
         * this option is not available.
         * 
         *  ```js
         *     data: [{
         *         x: 1,
         *         y: 2,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         y: 4,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         * 
         * @type {Array<Object|Array|Number>}
         * @extends series.line.data
         * @sample {highcharts} highcharts/chart/reflow-true/
         *         Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/
         *         Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
         *         Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/
         *         Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/
         *         Config objects
         * @product highcharts highstock
         * @apioption series.scatter.data
         */


    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var deg2rad = H.deg2rad,
            isNumber = H.isNumber,
            pick = H.pick,
            relativeLength = H.relativeLength;
        H.CenteredSeriesMixin = {
            /**
             * Get the center of the pie based on the size and center options relative to the
             * plot area. Borrowed by the polar and gauge series types.
             */
            getCenter: function() {

                var options = this.options,
                    chart = this.chart,
                    slicingRoom = 2 * (options.slicedOffset || 0),
                    handleSlicingRoom,
                    plotWidth = chart.plotWidth - 2 * slicingRoom,
                    plotHeight = chart.plotHeight - 2 * slicingRoom,
                    centerOption = options.center,
                    positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],
                    smallestSize = Math.min(plotWidth, plotHeight),
                    i,
                    value;

                for (i = 0; i < 4; ++i) {
                    value = positions[i];
                    handleSlicingRoom = i < 2 || (i === 2 && /%$/.test(value));

                    // i == 0: centerX, relative to width
                    // i == 1: centerY, relative to height
                    // i == 2: size, relative to smallestSize
                    // i == 3: innerSize, relative to size
                    positions[i] = relativeLength(value, [plotWidth, plotHeight, smallestSize, positions[2]][i]) +
                        (handleSlicingRoom ? slicingRoom : 0);

                }
                // innerSize cannot be larger than size (#3632)
                if (positions[3] > positions[2]) {
                    positions[3] = positions[2];
                }
                return positions;
            },
            /**
             * getStartAndEndRadians - Calculates start and end angles in radians.
             * Used in series types such as pie and sunburst.
             *
             * @param  {Number} start Start angle in degrees.
             * @param  {Number} end Start angle in degrees.
             * @return {object} Returns an object containing start and end angles as
             * radians.
             */
            getStartAndEndRadians: function getStartAndEndRadians(start, end) {
                var startAngle = isNumber(start) ? start : 0, // must be a number
                    endAngle = (
                        (
                            isNumber(end) && // must be a number
                            end > startAngle && // must be larger than the start angle
                            // difference must be less than 360 degrees
                            (end - startAngle) < 360
                        ) ?
                        end :
                        startAngle + 360
                    ),
                    correction = -90;
                return {
                    start: deg2rad * (startAngle + correction),
                    end: deg2rad * (endAngle + correction)
                };
            }
        };

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var addEvent = H.addEvent,
            CenteredSeriesMixin = H.CenteredSeriesMixin,
            defined = H.defined,
            each = H.each,
            extend = H.extend,
            getStartAndEndRadians = CenteredSeriesMixin.getStartAndEndRadians,
            inArray = H.inArray,
            LegendSymbolMixin = H.LegendSymbolMixin,
            noop = H.noop,
            pick = H.pick,
            Point = H.Point,
            Series = H.Series,
            seriesType = H.seriesType,
            seriesTypes = H.seriesTypes,
            setAnimation = H.setAnimation;

        /**
         * The pie series type.
         *
         * @constructor seriesTypes.pie
         * @augments Series
         */

        /**
         * A pie chart is a circular graphic which is divided into slices to illustrate
         * numerical proportion.
         *
         * @sample highcharts/demo/pie-basic/ Pie chart
         * 
         * @extends {plotOptions.line}
         * @excluding animationLimit,boostThreshold,connectEnds,connectNulls,
         *          cropThreshold,dashStyle,findNearestPointBy,getExtremesFromAll,
         *          lineWidth,marker,negativeColor,pointInterval,pointIntervalUnit,
         *          pointPlacement,pointStart,softThreshold,stacking,step,threshold,
         *          turboThreshold,zoneAxis,zones
         * @product highcharts
         * @optionparent plotOptions.pie
         */
        seriesType('pie', 'line', {

            /**
             * The center of the pie chart relative to the plot area. Can be percentages
             * or pixel values. The default behaviour (as of 3.0) is to center
             * the pie so that all slices and data labels are within the plot area.
             * As a consequence, the pie may actually jump around in a chart with
             * dynamic values, as the data labels move. In that case, the center
             * should be explicitly set, for example to `["50%", "50%"]`.
             * 
             * @type {Array<String|Number>}
             * @sample {highcharts} highcharts/plotoptions/pie-center/ Centered at 100, 100
             * @default [null, null]
             * @product highcharts
             */
            center: [null, null],

            clip: false,

            /** @ignore */
            colorByPoint: true, // always true for pies

            /**
             * A series specific or series type specific color set to use instead
             * of the global [colors](#colors).
             * 
             * @type {Array<Color>}
             * @sample {highcharts} highcharts/demo/pie-monochrome/ Set default colors for all pies
             * @since 3.0
             * @product highcharts
             * @apioption plotOptions.pie.colors
             */

            /**
             * @extends plotOptions.series.dataLabels
             * @excluding align,allowOverlap,staggerLines,step
             * @product highcharts
             */
            dataLabels: {
                /**
                 * The color of the line connecting the data label to the pie slice.
                 * The default color is the same as the point's color.
                 * 
                 * In styled mode, the connector stroke is given in the
                 * `.highcharts-data-label-connector` class.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorcolor/ Blue connectors
                 * @sample {highcharts} highcharts/css/pie-point/ Styled connectors
                 * @default {point.color}
                 * @since 2.1
                 * @product highcharts
                 * @apioption plotOptions.pie.dataLabels.connectorColor
                 */

                /**
                 * The distance from the data label to the connector.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorpadding/ No padding
                 * @default 5
                 * @since 2.1
                 * @product highcharts
                 * @apioption plotOptions.pie.dataLabels.connectorPadding
                 */

                /**
                 * The width of the line connecting the data label to the pie slice.
                 * 
                 * 
                 * In styled mode, the connector stroke width is given in the
                 * `.highcharts-data-label-connector` class.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorwidth-disabled/ Disable the connector
                 * @sample {highcharts} highcharts/css/pie-point/ Styled connectors
                 * @default 1
                 * @since 2.1
                 * @product highcharts
                 * @apioption plotOptions.pie.dataLabels.connectorWidth
                 */

                /**
                 * The distance of the data label from the pie's edge. Negative numbers
                 * put the data label on top of the pie slices. Connectors are only
                 * shown for data labels outside the pie.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-distance/ Data labels on top of the pie
                 * @default 30
                 * @since 2.1
                 * @product highcharts
                 */
                distance: 30,

                /**
                 * Enable or disable the data labels.
                 * 
                 * @type {Boolean}
                 * @since 2.1
                 * @product highcharts
                 */
                enabled: true,

                formatter: function() { // #2945
                    return this.point.isNull ? undefined : this.point.name;
                },

                /**
                 * Whether to render the connector as a soft arc or a line with sharp
                 * break.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-true/ Soft
                 * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-false/ Non soft
                 * @since 2.1.7
                 * @product highcharts
                 * @apioption plotOptions.pie.dataLabels.softConnector
                 */

                x: 0
                // y: 0
            },

            /**
             * The end angle of the pie in degrees where 0 is top and 90 is right.
             * Defaults to `startAngle` plus 360.
             * 
             * @type {Number}
             * @sample {highcharts} highcharts/demo/pie-semi-circle/ Semi-circle donut
             * @default null
             * @since 1.3.6
             * @product highcharts
             * @apioption plotOptions.pie.endAngle
             */

            /**
             * Equivalent to [chart.ignoreHiddenSeries](#chart.ignoreHiddenSeries),
             * this option tells whether the series shall be redrawn as if the
             * hidden point were `null`.
             * 
             * The default value changed from `false` to `true` with Highcharts
             * 3.0.
             * 
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/pie-ignorehiddenpoint/ True, the hiddden point is ignored
             * @default true
             * @since 2.3.0
             * @product highcharts
             */
            ignoreHiddenPoint: true,

            /**
             * The size of the inner diameter for the pie. A size greater than 0
             * renders a donut chart. Can be a percentage or pixel value. Percentages
             * are relative to the pie size. Pixel values are given as integers.
             * 
             * 
             * Note: in Highcharts < 4.1.2, the percentage was relative to the plot
             * area, not the pie size.
             * 
             * @type {String|Number}
             * @sample {highcharts} highcharts/plotoptions/pie-innersize-80px/ 80px inner size
             * @sample {highcharts} highcharts/plotoptions/pie-innersize-50percent/ 50% of the plot area
             * @sample {highcharts} highcharts/demo/3d-pie-donut/ 3D donut
             * @default 0
             * @since 2.0
             * @product highcharts
             * @apioption plotOptions.pie.innerSize
             */

            /** @ignore */
            legendType: 'point',

            /**	 @ignore */
            marker: null, // point options are specified in the base options

            /**
             * The minimum size for a pie in response to auto margins. The pie will
             * try to shrink to make room for data labels in side the plot area,
             *  but only to this size.
             * 
             * @type {Number}
             * @default 80
             * @since 3.0
             * @product highcharts
             * @apioption plotOptions.pie.minSize
             */

            /**
             * The diameter of the pie relative to the plot area. Can be a percentage
             * or pixel value. Pixel values are given as integers. The default
             * behaviour (as of 3.0) is to scale to the plot area and give room
             * for data labels within the plot area. As a consequence, the size
             * of the pie may vary when points are updated and data labels more
             * around. In that case it is best to set a fixed value, for example
             * `"75%"`.
             * 
             * @type {String|Number}
             * @sample {highcharts} highcharts/plotoptions/pie-size/ Smaller pie
             * @default  
             * @product highcharts
             */
            size: null,

            /**
             * Whether to display this particular series or series type in the
             * legend. Since 2.1, pies are not shown in the legend by default.
             * 
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/series-showinlegend/ One series in the legend, one hidden
             * @product highcharts
             */
            showInLegend: false,

            /**
             * If a point is sliced, moved out from the center, how many pixels
             * should it be moved?.
             * 
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/pie-slicedoffset-20/ 20px offset
             * @default 10
             * @product highcharts
             */
            slicedOffset: 10,

            /**
             * The start angle of the pie slices in degrees where 0 is top and 90
             * right.
             * 
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/pie-startangle-90/ Start from right
             * @default 0
             * @since 2.3.4
             * @product highcharts
             * @apioption plotOptions.pie.startAngle
             */

            /**
             * Sticky tracking of mouse events. When true, the `mouseOut` event
             * on a series isn't triggered until the mouse moves over another series,
             * or out of the plot area. When false, the `mouseOut` event on a
             * series is triggered when the mouse leaves the area around the series'
             * graph or markers. This also implies the tooltip. When `stickyTracking`
             * is false and `tooltip.shared` is false, the tooltip will be hidden
             * when moving the mouse between series.
             * 
             * @product highcharts
             */
            stickyTracking: false,

            tooltip: {
                followPointer: true
            },


            /**
             * The color of the border surrounding each slice. When `null`, the
             * border takes the same color as the slice fill. This can be used
             * together with a `borderWidth` to fill drawing gaps created by antialiazing
             * artefacts in borderless pies.
             * 
             * In styled mode, the border stroke is given in the `.highcharts-point` class.
             * 
             * @type {Color}
             * @sample {highcharts} highcharts/plotoptions/pie-bordercolor-black/ Black border
             * @default #ffffff
             * @product highcharts
             */
            borderColor: '#ffffff',

            /**
             * The width of the border surrounding each slice.
             * 
             * When setting the border width to 0, there may be small gaps between
             * the slices due to SVG antialiasing artefacts. To work around this,
             * keep the border width at 0.5 or 1, but set the `borderColor` to
             * `null` instead.
             * 
             * In styled mode, the border stroke width is given in the `.highcharts-point` class.
             * 
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/pie-borderwidth/ 3px border
             * @default 1
             * @product highcharts
             */
            borderWidth: 1,

            states: {

                /**
                 * @extends plotOptions.series.states.hover
                 * @product highcharts
                 */
                hover: {

                    /**
                     * How much to brighten the point on interaction. Requires the main
                     * color to be defined in hex or rgb(a) format.
                     * 
                     * In styled mode, the hover brightness is by default replaced
                     * by a fill-opacity given in the `.highcharts-point-hover` class.
                     * 
                     * @type {Number}
                     * @sample {highcharts} highcharts/plotoptions/pie-states-hover-brightness/ Brightened by 0.5
                     * @default 0.1
                     * @product highcharts
                     */
                    brightness: 0.1,

                    shadow: false
                }
            }


        }, /** @lends seriesTypes.pie.prototype */ {
            isCartesian: false,
            requireSorting: false,
            directTouch: true,
            noSharedTooltip: true,
            trackerGroups: ['group', 'dataLabelsGroup'],
            axisTypes: [],
            pointAttribs: seriesTypes.column.prototype.pointAttribs,
            /**
             * Animate the pies in
             */
            animate: function(init) {
                var series = this,
                    points = series.points,
                    startAngleRad = series.startAngleRad;

                if (!init) {
                    each(points, function(point) {
                        var graphic = point.graphic,
                            args = point.shapeArgs;

                        if (graphic) {
                            // start values
                            graphic.attr({
                                r: point.startR || (series.center[3] / 2), // animate from inner radius (#779)
                                start: startAngleRad,
                                end: startAngleRad
                            });

                            // animate
                            graphic.animate({
                                r: args.r,
                                start: args.start,
                                end: args.end
                            }, series.options.animation);
                        }
                    });

                    // delete this function to allow it only once
                    series.animate = null;
                }
            },

            /**
             * Recompute total chart sum and update percentages of points.
             */
            updateTotals: function() {
                var i,
                    total = 0,
                    points = this.points,
                    len = points.length,
                    point,
                    ignoreHiddenPoint = this.options.ignoreHiddenPoint;

                // Get the total sum
                for (i = 0; i < len; i++) {
                    point = points[i];
                    total += (ignoreHiddenPoint && !point.visible) ?
                        0 :
                        point.isNull ? 0 : point.y;
                }
                this.total = total;

                // Set each point's properties
                for (i = 0; i < len; i++) {
                    point = points[i];
                    point.percentage = (total > 0 && (point.visible || !ignoreHiddenPoint)) ? point.y / total * 100 : 0;
                    point.total = total;
                }
            },

            /**
             * Extend the generatePoints method by adding total and percentage properties to each point
             */
            generatePoints: function() {
                Series.prototype.generatePoints.call(this);
                this.updateTotals();
            },

            /**
             * Do translation for pie slices
             */
            translate: function(positions) {
                this.generatePoints();

                var series = this,
                    cumulative = 0,
                    precision = 1000, // issue #172
                    options = series.options,
                    slicedOffset = options.slicedOffset,
                    connectorOffset = slicedOffset + (options.borderWidth || 0),
                    finalConnectorOffset,
                    start,
                    end,
                    angle,
                    radians = getStartAndEndRadians(options.startAngle, options.endAngle),
                    startAngleRad = series.startAngleRad = radians.start,
                    endAngleRad = series.endAngleRad = radians.end,
                    circ = endAngleRad - startAngleRad, // 2 * Math.PI,
                    points = series.points,
                    radiusX, // the x component of the radius vector for a given point
                    radiusY,
                    labelDistance = options.dataLabels.distance,
                    ignoreHiddenPoint = options.ignoreHiddenPoint,
                    i,
                    len = points.length,
                    point;

                // Get positions - either an integer or a percentage string must be given.
                // If positions are passed as a parameter, we're in a recursive loop for adjusting
                // space for data labels.
                if (!positions) {
                    series.center = positions = series.getCenter();
                }

                // Utility for getting the x value from a given y, used for anticollision
                // logic in data labels.
                // Added point for using specific points' label distance.
                series.getX = function(y, left, point) {
                    angle = Math.asin(Math.min((y - positions[1]) / (positions[2] / 2 + point.labelDistance), 1));
                    return positions[0] +
                        (left ? -1 : 1) *
                        (Math.cos(angle) * (positions[2] / 2 + point.labelDistance));
                };

                // Calculate the geometry for each point
                for (i = 0; i < len; i++) {

                    point = points[i];

                    // Used for distance calculation for specific point.
                    point.labelDistance = pick(
                        point.options.dataLabels && point.options.dataLabels.distance,
                        labelDistance
                    );

                    // Saved for later dataLabels distance calculation.
                    series.maxLabelDistance = Math.max(series.maxLabelDistance || 0, point.labelDistance);

                    // set start and end angle
                    start = startAngleRad + (cumulative * circ);
                    if (!ignoreHiddenPoint || point.visible) {
                        cumulative += point.percentage / 100;
                    }
                    end = startAngleRad + (cumulative * circ);

                    // set the shape
                    point.shapeType = 'arc';
                    point.shapeArgs = {
                        x: positions[0],
                        y: positions[1],
                        r: positions[2] / 2,
                        innerR: positions[3] / 2,
                        start: Math.round(start * precision) / precision,
                        end: Math.round(end * precision) / precision
                    };

                    // The angle must stay within -90 and 270 (#2645)
                    angle = (end + start) / 2;
                    if (angle > 1.5 * Math.PI) {
                        angle -= 2 * Math.PI;
                    } else if (angle < -Math.PI / 2) {
                        angle += 2 * Math.PI;
                    }

                    // Center for the sliced out slice
                    point.slicedTranslation = {
                        translateX: Math.round(Math.cos(angle) * slicedOffset),
                        translateY: Math.round(Math.sin(angle) * slicedOffset)
                    };

                    // set the anchor point for tooltips
                    radiusX = Math.cos(angle) * positions[2] / 2;
                    radiusY = Math.sin(angle) * positions[2] / 2;
                    point.tooltipPos = [
                        positions[0] + radiusX * 0.7,
                        positions[1] + radiusY * 0.7
                    ];

                    point.half = angle < -Math.PI / 2 || angle > Math.PI / 2 ? 1 : 0;
                    point.angle = angle;

                    // Set the anchor point for data labels. Use point.labelDistance 
                    // instead of labelDistance // #1174
                    // finalConnectorOffset - not override connectorOffset value.
                    finalConnectorOffset = Math.min(connectorOffset, point.labelDistance / 5); // #1678
                    point.labelPos = [
                        positions[0] + radiusX + Math.cos(angle) * point.labelDistance, // first break of connector
                        positions[1] + radiusY + Math.sin(angle) * point.labelDistance, // a/a
                        positions[0] + radiusX + Math.cos(angle) * finalConnectorOffset, // second break, right outside pie
                        positions[1] + radiusY + Math.sin(angle) * finalConnectorOffset, // a/a
                        positions[0] + radiusX, // landing point for connector
                        positions[1] + radiusY, // a/a
                        point.labelDistance < 0 ? // alignment
                        'center' :
                        point.half ? 'right' : 'left', // alignment
                        angle // center angle
                    ];

                }
            },

            drawGraph: null,

            /**
             * Draw the data points
             */
            drawPoints: function() {
                var series = this,
                    chart = series.chart,
                    renderer = chart.renderer,
                    groupTranslation,
                    graphic,
                    pointAttr,
                    shapeArgs;


                var shadow = series.options.shadow;
                if (shadow && !series.shadowGroup) {
                    series.shadowGroup = renderer.g('shadow')
                        .add(series.group);
                }


                // draw the slices
                each(series.points, function(point) {
                    graphic = point.graphic;
                    if (!point.isNull) {
                        shapeArgs = point.shapeArgs;


                        // If the point is sliced, use special translation, else use
                        // plot area traslation
                        groupTranslation = point.getTranslate();


                        // Put the shadow behind all points
                        var shadowGroup = point.shadowGroup;
                        if (shadow && !shadowGroup) {
                            shadowGroup = point.shadowGroup = renderer.g('shadow')
                                .add(series.shadowGroup);
                        }

                        if (shadowGroup) {
                            shadowGroup.attr(groupTranslation);
                        }
                        pointAttr = series.pointAttribs(point, point.selected && 'select');


                        // Draw the slice
                        if (graphic) {
                            graphic
                                .setRadialReference(series.center)

                                .attr(pointAttr)

                                .animate(extend(shapeArgs, groupTranslation));
                        } else {

                            point.graphic = graphic = renderer[point.shapeType](shapeArgs)
                                .setRadialReference(series.center)
                                .attr(groupTranslation)
                                .add(series.group);

                            if (!point.visible) {
                                graphic.attr({
                                    visibility: 'hidden'
                                });
                            }


                            graphic
                                .attr(pointAttr)
                                .attr({
                                    'stroke-linejoin': 'round'
                                })
                                .shadow(shadow, shadowGroup);

                        }

                        graphic.addClass(point.getClassName());

                    } else if (graphic) {
                        point.graphic = graphic.destroy();
                    }
                });

            },


            searchPoint: noop,

            /**
             * Utility for sorting data labels
             */
            sortByAngle: function(points, sign) {
                points.sort(function(a, b) {
                    return a.angle !== undefined && (b.angle - a.angle) * sign;
                });
            },

            /**
             * Use a simple symbol from LegendSymbolMixin
             */
            drawLegendSymbol: LegendSymbolMixin.drawRectangle,

            /**
             * Use the getCenter method from drawLegendSymbol
             */
            getCenter: CenteredSeriesMixin.getCenter,

            /**
             * Pies don't have point marker symbols
             */
            getSymbol: noop


            /**
             * @constructor seriesTypes.pie.prototype.pointClass
             * @extends {Point}
             */
        }, /** @lends seriesTypes.pie.prototype.pointClass.prototype */ {
            /**
             * Initiate the pie slice
             */
            init: function() {

                Point.prototype.init.apply(this, arguments);

                var point = this,
                    toggleSlice;

                point.name = pick(point.name, 'Slice');

                // add event listener for select
                toggleSlice = function(e) {
                    point.slice(e.type === 'select');
                };
                addEvent(point, 'select', toggleSlice);
                addEvent(point, 'unselect', toggleSlice);

                return point;
            },

            /**
             * Negative points are not valid (#1530, #3623, #5322)
             */
            isValid: function() {
                return H.isNumber(this.y, true) && this.y >= 0;
            },

            /**
             * Toggle the visibility of the pie slice
             * @param {Boolean} vis Whether to show the slice or not. If undefined, the
             *    visibility is toggled
             */
            setVisible: function(vis, redraw) {
                var point = this,
                    series = point.series,
                    chart = series.chart,
                    ignoreHiddenPoint = series.options.ignoreHiddenPoint;

                redraw = pick(redraw, ignoreHiddenPoint);

                if (vis !== point.visible) {

                    // If called without an argument, toggle visibility
                    point.visible = point.options.visible = vis = vis === undefined ? !point.visible : vis;
                    series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data

                    // Show and hide associated elements. This is performed regardless of redraw or not,
                    // because chart.redraw only handles full series.
                    each(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function(key) {
                        if (point[key]) {
                            point[key][vis ? 'show' : 'hide'](true);
                        }
                    });

                    if (point.legendItem) {
                        chart.legend.colorizeItem(point, vis);
                    }

                    // #4170, hide halo after hiding point
                    if (!vis && point.state === 'hover') {
                        point.setState('');
                    }

                    // Handle ignore hidden slices
                    if (ignoreHiddenPoint) {
                        series.isDirty = true;
                    }

                    if (redraw) {
                        chart.redraw();
                    }
                }
            },

            /**
             * Set or toggle whether the slice is cut out from the pie
             * @param {Boolean} sliced When undefined, the slice state is toggled
             * @param {Boolean} redraw Whether to redraw the chart. True by default.
             */
            slice: function(sliced, redraw, animation) {
                var point = this,
                    series = point.series,
                    chart = series.chart;

                setAnimation(animation, chart);

                // redraw is true by default
                redraw = pick(redraw, true);

                // if called without an argument, toggle
                point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced;
                series.options.data[inArray(point, series.data)] = point.options; // update userOptions.data

                point.graphic.animate(this.getTranslate());


                if (point.shadowGroup) {
                    point.shadowGroup.animate(this.getTranslate());
                }

            },

            getTranslate: function() {
                return this.sliced ? this.slicedTranslation : {
                    translateX: 0,
                    translateY: 0
                };
            },

            haloPath: function(size) {
                var shapeArgs = this.shapeArgs;

                return this.sliced || !this.visible ? [] :
                    this.series.chart.renderer.symbols.arc(
                        shapeArgs.x,
                        shapeArgs.y,
                        shapeArgs.r + size,
                        shapeArgs.r + size, {
                            innerR: this.shapeArgs.r,
                            start: shapeArgs.start,
                            end: shapeArgs.end
                        }
                    );
            }
        });

        /**
         * A `pie` series. If the [type](#series.pie.type) option is not specified,
         * it is inherited from [chart.type](#chart.type).
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * pie](#plotOptions.pie).
         * 
         * @type {Object}
         * @extends series,plotOptions.pie
         * @excluding dataParser,dataURL,stack,xAxis,yAxis
         * @product highcharts
         * @apioption series.pie
         */

        /**
         * An array of data points for the series. For the `pie` series type,
         * points can be given in the following ways:
         * 
         * 1.  An array of numerical values. In this case, the numerical values
         * will be interpreted as `y` options. Example:
         * 
         *  ```js
         *  data: [0, 5, 3, 5]
         *  ```
         * 
         * 2.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.pie.turboThreshold),
         * this option is not available.
         * 
         *  ```js
         *     data: [{
         *     y: 1,
         *     name: "Point2",
         *     color: "#00FF00"
         * }, {
         *     y: 7,
         *     name: "Point1",
         *     color: "#FF00FF"
         * }]</pre>
         * 
         * @type {Array<Object|Number>}
         * @extends series.line.data
         * @excluding marker,x
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
         * @product highcharts
         * @apioption series.pie.data
         */

        /**
         * The sequential index of the data point in the legend.
         * 
         * @type {Number}
         * @product highcharts
         * @apioption series.pie.data.legendIndex
         */

        /**
         * Whether to display a slice offset from the center.
         * 
         * @type {Boolean}
         * @sample {highcharts} highcharts/point/sliced/ One sliced point
         * @product highcharts
         * @apioption series.pie.data.sliced
         */

        /**
         * Fires when the checkbox next to the point name in the legend is clicked.
         * One parameter, event, is passed to the function. The state of the
         * checkbox is found by event.checked. The checked item is found by
         * event.item. Return false to prevent the default action which is to
         * toggle the select state of the series.
         * 
         * @type {Function}
         * @context Point
         * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/
         *         Alert checkbox status
         * @since 1.2.0
         * @product highcharts
         * @apioption plotOptions.pie.events.checkboxClick
         */

        /**
         * Not applicable to pies, as the legend item is per point. See point.
         * events.
         * 
         * @type {Function}
         * @since 1.2.0
         * @product highcharts
         * @apioption plotOptions.pie.events.legendItemClick
         */

        /**
         * Fires when the legend item belonging to the pie point (slice) is
         * clicked. The `this` keyword refers to the point itself. One parameter,
         * `event`, is passed to the function, containing common event information. The
         * default action is to toggle the visibility of the point. This can be
         * prevented by calling `event.preventDefault()`.
         * 
         * @type {Function}
         * @sample {highcharts} highcharts/plotoptions/pie-point-events-legenditemclick/
         *         Confirm toggle visibility
         * @since 1.2.0
         * @product highcharts
         * @apioption plotOptions.pie.point.events.legendItemClick
         */

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var addEvent = H.addEvent,
            arrayMax = H.arrayMax,
            defined = H.defined,
            each = H.each,
            extend = H.extend,
            format = H.format,
            map = H.map,
            merge = H.merge,
            noop = H.noop,
            pick = H.pick,
            relativeLength = H.relativeLength,
            Series = H.Series,
            seriesTypes = H.seriesTypes,
            stableSort = H.stableSort;

        /* eslint max-len: ["warn", 80, 4] */
        /**
         * General distribution algorithm for distributing labels of differing size
         * along a confined length in two dimensions. The algorithm takes an array of
         * objects containing a size, a target and a rank. It will place the labels as
         * close as possible to their targets, skipping the lowest ranked labels if
         * necessary.
         */
        H.distribute = function(boxes, len) {

            var i,
                overlapping = true,
                origBoxes = boxes, // Original array will be altered with added .pos
                restBoxes = [], // The outranked overshoot
                box,
                target,
                total = 0;

            function sortByTarget(a, b) {
                return a.target - b.target;
            }

            // If the total size exceeds the len, remove those boxes with the lowest
            // rank
            i = boxes.length;
            while (i--) {
                total += boxes[i].size;
            }

            // Sort by rank, then slice away overshoot
            if (total > len) {
                stableSort(boxes, function(a, b) {
                    return (b.rank || 0) - (a.rank || 0);
                });
                i = 0;
                total = 0;
                while (total <= len) {
                    total += boxes[i].size;
                    i++;
                }
                restBoxes = boxes.splice(i - 1, boxes.length);
            }

            // Order by target
            stableSort(boxes, sortByTarget);


            // So far we have been mutating the original array. Now
            // create a copy with target arrays
            boxes = map(boxes, function(box) {
                return {
                    size: box.size,
                    targets: [box.target],
                    align: pick(box.align, 0.5)
                };
            });

            while (overlapping) {
                // Initial positions: target centered in box
                i = boxes.length;
                while (i--) {
                    box = boxes[i];
                    // Composite box, average of targets
                    target = (
                        Math.min.apply(0, box.targets) +
                        Math.max.apply(0, box.targets)
                    ) / 2;
                    box.pos = Math.min(
                        Math.max(0, target - box.size * box.align),
                        len - box.size
                    );
                }

                // Detect overlap and join boxes
                i = boxes.length;
                overlapping = false;
                while (i--) {
                    // Overlap
                    if (i > 0 && boxes[i - 1].pos + boxes[i - 1].size > boxes[i].pos) {
                        // Add this size to the previous box
                        boxes[i - 1].size += boxes[i].size;
                        boxes[i - 1].targets = boxes[i - 1]
                            .targets
                            .concat(boxes[i].targets);
                        boxes[i - 1].align = 0.5;

                        // Overlapping right, push left
                        if (boxes[i - 1].pos + boxes[i - 1].size > len) {
                            boxes[i - 1].pos = len - boxes[i - 1].size;
                        }
                        boxes.splice(i, 1); // Remove this item
                        overlapping = true;
                    }
                }
            }

            // Now the composite boxes are placed, we need to put the original boxes
            // within them
            i = 0;
            each(boxes, function(box) {
                var posInCompositeBox = 0;
                each(box.targets, function() {
                    origBoxes[i].pos = box.pos + posInCompositeBox;
                    posInCompositeBox += origBoxes[i].size;
                    i++;
                });
            });

            // Add the rest (hidden) boxes and sort by target
            origBoxes.push.apply(origBoxes, restBoxes);
            stableSort(origBoxes, sortByTarget);
        };


        /**
         * Draw the data labels
         */
        Series.prototype.drawDataLabels = function() {
            var series = this,
                seriesOptions = series.options,
                options = seriesOptions.dataLabels,
                points = series.points,
                pointOptions,
                generalOptions,
                hasRendered = series.hasRendered || 0,
                str,
                dataLabelsGroup,
                defer = pick(options.defer, !!seriesOptions.animation),
                renderer = series.chart.renderer;

            /*
             * Handle the dataLabels.filter option.
             */
            function applyFilter(point, options) {
                var filter = options.filter,
                    op,
                    prop,
                    val;
                if (filter) {
                    op = filter.operator;
                    prop = point[filter.property];
                    val = filter.value;
                    if (
                        (op === '>' && prop > val) ||
                        (op === '<' && prop < val) ||
                        (op === '>=' && prop >= val) ||
                        (op === '<=' && prop <= val) ||
                        (op === '==' && prop == val) || // eslint-disable-line eqeqeq
                        (op === '===' && prop === val)
                    ) {
                        return true;
                    }
                    return false;
                }
                return true;
            }

            if (options.enabled || series._hasPointLabels) {

                // Process default alignment of data labels for columns
                if (series.dlProcessOptions) {
                    series.dlProcessOptions(options);
                }

                // Create a separate group for the data labels to avoid rotation
                dataLabelsGroup = series.plotGroup(
                    'dataLabelsGroup',
                    'data-labels',
                    defer && !hasRendered ? 'hidden' : 'visible', // #5133
                    options.zIndex || 6
                );

                if (defer) {
                    dataLabelsGroup.attr({
                        opacity: +hasRendered
                    }); // #3300
                    if (!hasRendered) {
                        addEvent(series, 'afterAnimate', function() {
                            if (series.visible) { // #2597, #3023, #3024
                                dataLabelsGroup.show(true);
                            }
                            dataLabelsGroup[
                                seriesOptions.animation ? 'animate' : 'attr'
                            ]({
                                opacity: 1
                            }, {
                                duration: 200
                            });
                        });
                    }
                }

                // Make the labels for each point
                generalOptions = options;
                each(points, function(point) {
                    var enabled,
                        dataLabel = point.dataLabel,
                        labelConfig,
                        attr,
                        rotation,
                        connector = point.connector,
                        isNew = !dataLabel,
                        style,
                        formatString;

                    // Determine if each data label is enabled
                    // @note dataLabelAttribs (like pointAttribs) would eradicate
                    // the need for dlOptions, and simplify the section below.
                    pointOptions = point.dlOptions || // dlOptions is used in treemaps
                        (point.options && point.options.dataLabels);
                    enabled = pick(
                        pointOptions && pointOptions.enabled,
                        generalOptions.enabled
                    ) && !point.isNull; // #2282, #4641, #7112

                    if (enabled) {
                        enabled = applyFilter(point, pointOptions || options) === true;
                    }

                    if (enabled) {
                        // Create individual options structure that can be extended
                        // without affecting others
                        options = merge(generalOptions, pointOptions);
                        labelConfig = point.getLabelConfig();
                        formatString = (
                            options[point.formatPrefix + 'Format'] ||
                            options.format
                        );

                        str = defined(formatString) ?
                            format(formatString, labelConfig) :
                            (
                                options[point.formatPrefix + 'Formatter'] ||
                                options.formatter
                            ).call(labelConfig, options);

                        style = options.style;
                        rotation = options.rotation;

                        // Determine the color
                        style.color = pick(
                            options.color,
                            style.color,
                            series.color,
                            '#000000'
                        );
                        // Get automated contrast color
                        if (style.color === 'contrast') {
                            point.contrastColor =
                                renderer.getContrast(point.color || series.color);
                            style.color = options.inside ||
                                pick(point.labelDistance, options.distance) < 0 ||
                                !!seriesOptions.stacking ?
                                point.contrastColor :
                                '#000000';
                        }
                        if (seriesOptions.cursor) {
                            style.cursor = seriesOptions.cursor;
                        }


                        attr = {

                            fill: options.backgroundColor,
                            stroke: options.borderColor,
                            'stroke-width': options.borderWidth,

                            r: options.borderRadius || 0,
                            rotation: rotation,
                            padding: options.padding,
                            zIndex: 1
                        };

                        // Remove unused attributes (#947)
                        H.objectEach(attr, function(val, name) {
                            if (val === undefined) {
                                delete attr[name];
                            }
                        });
                    }
                    // If the point is outside the plot area, destroy it. #678, #820
                    if (dataLabel && (!enabled || !defined(str))) {
                        point.dataLabel = dataLabel = dataLabel.destroy();
                        if (connector) {
                            point.connector = connector.destroy();
                        }
                        // Individual labels are disabled if the are explicitly disabled
                        // in the point options, or if they fall outside the plot area.
                    } else if (enabled && defined(str)) {
                        // create new label
                        if (!dataLabel) {
                            dataLabel = point.dataLabel = renderer[
                                rotation ? 'text' : 'label' // labels don't rotate
                            ](
                                str,
                                0, -9999,
                                options.shape,
                                null,
                                null,
                                options.useHTML,
                                null,
                                'data-label'
                            );
                            dataLabel.addClass(
                                'highcharts-data-label-color-' + point.colorIndex +
                                ' ' + (options.className || '') +
                                (options.useHTML ? 'highcharts-tracker' : '') // #3398
                            );
                        } else {
                            attr.text = str;
                        }
                        dataLabel.attr(attr);

                        // Styles must be applied before add in order to read text
                        // bounding box
                        dataLabel.css(style).shadow(options.shadow);


                        if (!dataLabel.added) {
                            dataLabel.add(dataLabelsGroup);
                        }
                        // Now the data label is created and placed at 0,0, so we need
                        // to align it
                        series.alignDataLabel(point, dataLabel, options, null, isNew);
                    }
                });
            }
        };

        /**
         * Align each individual data label
         */
        Series.prototype.alignDataLabel = function(
            point,
            dataLabel,
            options,
            alignTo,
            isNew
        ) {
            var chart = this.chart,
                inverted = chart.inverted,
                plotX = pick(point.dlBox && point.dlBox.centerX, point.plotX, -9999),
                plotY = pick(point.plotY, -9999),
                bBox = dataLabel.getBBox(),
                fontSize,
                baseline,
                rotation = options.rotation,
                normRotation,
                negRotation,
                align = options.align,
                rotCorr, // rotation correction
                // Math.round for rounding errors (#2683), alignTo to allow column
                // labels (#2700)
                visible =
                this.visible &&
                (
                    point.series.forceDL ||
                    chart.isInsidePlot(plotX, Math.round(plotY), inverted) ||
                    (
                        alignTo && chart.isInsidePlot(
                            plotX,
                            inverted ?
                            alignTo.x + 1 :
                            alignTo.y + alignTo.height - 1,
                            inverted
                        )
                    )
                ),
                alignAttr, // the final position;
                justify = pick(options.overflow, 'justify') === 'justify';

            if (visible) {


                fontSize = options.style.fontSize;


                baseline = chart.renderer.fontMetrics(fontSize, dataLabel).b;

                // The alignment box is a singular point
                alignTo = extend({
                    x: inverted ? this.yAxis.len - plotY : plotX,
                    y: Math.round(inverted ? this.xAxis.len - plotX : plotY),
                    width: 0,
                    height: 0
                }, alignTo);

                // Add the text size for alignment calculation
                extend(options, {
                    width: bBox.width,
                    height: bBox.height
                });

                // Allow a hook for changing alignment in the last moment, then do the
                // alignment
                if (rotation) {
                    justify = false; // Not supported for rotated text
                    rotCorr = chart.renderer.rotCorr(baseline, rotation); // #3723
                    alignAttr = {
                        x: alignTo.x + options.x + alignTo.width / 2 + rotCorr.x,
                        y: (
                            alignTo.y +
                            options.y + {
                                top: 0,
                                middle: 0.5,
                                bottom: 1
                            }[options.verticalAlign] *
                            alignTo.height
                        )
                    };
                    dataLabel[isNew ? 'attr' : 'animate'](alignAttr)
                        .attr({ // #3003
                            align: align
                        });

                    // Compensate for the rotated label sticking out on the sides
                    normRotation = (rotation + 720) % 360;
                    negRotation = normRotation > 180 && normRotation < 360;

                    if (align === 'left') {
                        alignAttr.y -= negRotation ? bBox.height : 0;
                    } else if (align === 'center') {
                        alignAttr.x -= bBox.width / 2;
                        alignAttr.y -= bBox.height / 2;
                    } else if (align === 'right') {
                        alignAttr.x -= bBox.width;
                        alignAttr.y -= negRotation ? 0 : bBox.height;
                    }


                } else {
                    dataLabel.align(options, null, alignTo);
                    alignAttr = dataLabel.alignAttr;
                }

                // Handle justify or crop
                if (justify) {
                    point.isLabelJustified = this.justifyDataLabel(
                        dataLabel,
                        options,
                        alignAttr,
                        bBox,
                        alignTo,
                        isNew
                    );

                    // Now check that the data label is within the plot area
                } else if (pick(options.crop, true)) {
                    visible =
                        chart.isInsidePlot(
                            alignAttr.x,
                            alignAttr.y
                        ) &&
                        chart.isInsidePlot(
                            alignAttr.x + bBox.width,
                            alignAttr.y + bBox.height
                        );
                }

                // When we're using a shape, make it possible with a connector or an
                // arrow pointing to thie point
                if (options.shape && !rotation) {
                    dataLabel[isNew ? 'attr' : 'animate']({
                        anchorX: inverted ? chart.plotWidth - point.plotY : point.plotX,
                        anchorY: inverted ? chart.plotHeight - point.plotX : point.plotY
                    });
                }
            }

            // Show or hide based on the final aligned position
            if (!visible) {
                dataLabel.attr({
                    y: -9999
                });
                dataLabel.placed = false; // don't animate back in
            }

        };

        /**
         * If data labels fall partly outside the plot area, align them back in, in a
         * way that doesn't hide the point.
         */
        Series.prototype.justifyDataLabel = function(
            dataLabel,
            options,
            alignAttr,
            bBox,
            alignTo,
            isNew
        ) {
            var chart = this.chart,
                align = options.align,
                verticalAlign = options.verticalAlign,
                off,
                justified,
                padding = dataLabel.box ? 0 : (dataLabel.padding || 0);

            // Off left
            off = alignAttr.x + padding;
            if (off < 0) {
                if (align === 'right') {
                    options.align = 'left';
                } else {
                    options.x = -off;
                }
                justified = true;
            }

            // Off right
            off = alignAttr.x + bBox.width - padding;
            if (off > chart.plotWidth) {
                if (align === 'left') {
                    options.align = 'right';
                } else {
                    options.x = chart.plotWidth - off;
                }
                justified = true;
            }

            // Off top
            off = alignAttr.y + padding;
            if (off < 0) {
                if (verticalAlign === 'bottom') {
                    options.verticalAlign = 'top';
                } else {
                    options.y = -off;
                }
                justified = true;
            }

            // Off bottom
            off = alignAttr.y + bBox.height - padding;
            if (off > chart.plotHeight) {
                if (verticalAlign === 'top') {
                    options.verticalAlign = 'bottom';
                } else {
                    options.y = chart.plotHeight - off;
                }
                justified = true;
            }

            if (justified) {
                dataLabel.placed = !isNew;
                dataLabel.align(options, null, alignTo);
            }

            return justified;
        };

        /**
         * Override the base drawDataLabels method by pie specific functionality
         */
        if (seriesTypes.pie) {
            seriesTypes.pie.prototype.drawDataLabels = function() {
                var series = this,
                    data = series.data,
                    point,
                    chart = series.chart,
                    options = series.options.dataLabels,
                    connectorPadding = pick(options.connectorPadding, 10),
                    connectorWidth = pick(options.connectorWidth, 1),
                    plotWidth = chart.plotWidth,
                    plotHeight = chart.plotHeight,
                    connector,
                    seriesCenter = series.center,
                    radius = seriesCenter[2] / 2,
                    centerY = seriesCenter[1],
                    dataLabel,
                    dataLabelWidth,
                    labelPos,
                    labelHeight,
                    // divide the points into right and left halves for anti collision
                    halves = [
                        [], // right
                        [] // left
                    ],
                    x,
                    y,
                    visibility,
                    j,
                    overflow = [0, 0, 0, 0]; // top, right, bottom, left

                // get out if not enabled
                if (!series.visible || (!options.enabled && !series._hasPointLabels)) {
                    return;
                }

                // Reset all labels that have been shortened
                each(data, function(point) {
                    if (point.dataLabel && point.visible && point.dataLabel.shortened) {
                        point.dataLabel
                            .attr({
                                width: 'auto'
                            }).css({
                                width: 'auto',
                                textOverflow: 'clip'
                            });
                        point.dataLabel.shortened = false;
                    }
                });


                // run parent method
                Series.prototype.drawDataLabels.apply(series);

                each(data, function(point) {
                    if (point.dataLabel && point.visible) { // #407, #2510

                        // Arrange points for detection collision
                        halves[point.half].push(point);

                        // Reset positions (#4905)
                        point.dataLabel._pos = null;
                    }
                });

                /* Loop over the points in each half, starting from the top and bottom
                 * of the pie to detect overlapping labels.
                 */
                each(halves, function(points, i) {

                    var top,
                        bottom,
                        length = points.length,
                        positions = [],
                        naturalY,
                        sideOverflow,
                        positionsIndex, // Point index in positions array.
                        size;

                    if (!length) {
                        return;
                    }

                    // Sort by angle
                    series.sortByAngle(points, i - 0.5);
                    // Only do anti-collision when we have dataLabels outside the pie 
                    // and have connectors. (#856)
                    if (series.maxLabelDistance > 0) {
                        top = Math.max(
                            0,
                            centerY - radius - series.maxLabelDistance
                        );
                        bottom = Math.min(
                            centerY + radius + series.maxLabelDistance,
                            chart.plotHeight
                        );
                        each(points, function(point) {
                            // check if specific points' label is outside the pie
                            if (point.labelDistance > 0 && point.dataLabel) {
                                // point.top depends on point.labelDistance value
                                // Used for calculation of y value in getX method 
                                point.top = Math.max(
                                    0,
                                    centerY - radius - point.labelDistance
                                );
                                point.bottom = Math.min(
                                    centerY + radius + point.labelDistance,
                                    chart.plotHeight
                                );
                                size = point.dataLabel.getBBox().height || 21;

                                // point.positionsIndex is needed for getting index of 
                                // parameter related to specific point inside positions 
                                // array - not every point is in positions array.
                                point.positionsIndex = positions.push({
                                    target: point.labelPos[1] - point.top + size / 2,
                                    size: size,
                                    rank: point.y
                                }) - 1;
                            }
                        });
                        H.distribute(positions, bottom + size - top);
                    }

                    // Now the used slots are sorted, fill them up sequentially
                    for (j = 0; j < length; j++) {

                        point = points[j];
                        positionsIndex = point.positionsIndex;
                        labelPos = point.labelPos;
                        dataLabel = point.dataLabel;
                        visibility = point.visible === false ? 'hidden' : 'inherit';
                        naturalY = labelPos[1];
                        y = naturalY;

                        if (positions && defined(positions[positionsIndex])) {
                            if (positions[positionsIndex].pos === undefined) {
                                visibility = 'hidden';
                            } else {
                                labelHeight = positions[positionsIndex].size;
                                y = point.top + positions[positionsIndex].pos;
                            }
                        }

                        // It is needed to delete point.positionIndex for 
                        // dynamically added points etc.

                        delete point.positionIndex;

                        // get the x - use the natural x position for labels near the 
                        // top and bottom, to prevent the top and botton slice
                        // connectors from touching each other on either side
                        if (options.justify) {
                            x = seriesCenter[0] +
                                (i ? -1 : 1) * (radius + point.labelDistance);
                        } else {
                            x = series.getX(
                                y < point.top + 2 || y > point.bottom - 2 ?
                                naturalY :
                                y,
                                i,
                                point
                            );
                        }


                        // Record the placement and visibility
                        dataLabel._attr = {
                            visibility: visibility,
                            align: labelPos[6]
                        };
                        dataLabel._pos = {
                            x: (
                                x +
                                options.x +
                                ({
                                    left: connectorPadding,
                                    right: -connectorPadding
                                }[labelPos[6]] || 0)
                            ),

                            // 10 is for the baseline (label vs text)
                            y: y + options.y - 10
                        };
                        labelPos.x = x;
                        labelPos.y = y;


                        // Detect overflowing data labels
                        if (pick(options.crop, true)) {
                            dataLabelWidth = dataLabel.getBBox().width;

                            sideOverflow = null;
                            // Overflow left
                            if (x - dataLabelWidth < connectorPadding) {
                                sideOverflow = Math.round(
                                    dataLabelWidth - x + connectorPadding
                                );
                                overflow[3] = Math.max(sideOverflow, overflow[3]);

                                // Overflow right
                            } else if (
                                x + dataLabelWidth >
                                plotWidth - connectorPadding
                            ) {
                                sideOverflow = Math.round(
                                    x + dataLabelWidth - plotWidth + connectorPadding
                                );
                                overflow[1] = Math.max(sideOverflow, overflow[1]);
                            }

                            // Overflow top
                            if (y - labelHeight / 2 < 0) {
                                overflow[0] = Math.max(
                                    Math.round(-y + labelHeight / 2),
                                    overflow[0]
                                );

                                // Overflow left
                            } else if (y + labelHeight / 2 > plotHeight) {
                                overflow[2] = Math.max(
                                    Math.round(y + labelHeight / 2 - plotHeight),
                                    overflow[2]
                                );
                            }
                            dataLabel.sideOverflow = sideOverflow;
                        }
                    } // for each point
                }); // for each half

                // Do not apply the final placement and draw the connectors until we
                // have verified that labels are not spilling over.
                if (
                    arrayMax(overflow) === 0 ||
                    this.verifyDataLabelOverflow(overflow)
                ) {

                    // Place the labels in the final position
                    this.placeDataLabels();

                    // Draw the connectors
                    if (connectorWidth) {
                        each(this.points, function(point) {
                            var isNew;

                            connector = point.connector;
                            dataLabel = point.dataLabel;

                            if (
                                dataLabel &&
                                dataLabel._pos &&
                                point.visible &&
                                point.labelDistance > 0
                            ) {
                                visibility = dataLabel._attr.visibility;

                                isNew = !connector;

                                if (isNew) {
                                    point.connector = connector = chart.renderer.path()
                                        .addClass('highcharts-data-label-connector ' +
                                            ' highcharts-color-' + point.colorIndex)
                                        .add(series.dataLabelsGroup);


                                    connector.attr({
                                        'stroke-width': connectorWidth,
                                        'stroke': (
                                            options.connectorColor ||
                                            point.color ||
                                            '#666666'
                                        )
                                    });

                                }
                                connector[isNew ? 'attr' : 'animate']({
                                    d: series.connectorPath(point.labelPos)
                                });
                                connector.attr('visibility', visibility);

                            } else if (connector) {
                                point.connector = connector.destroy();
                            }
                        });
                    }
                }
            };

            /**
             * Extendable method for getting the path of the connector between the data
             * label and the pie slice.
             */
            seriesTypes.pie.prototype.connectorPath = function(labelPos) {
                var x = labelPos.x,
                    y = labelPos.y;
                return pick(this.options.dataLabels.softConnector, true) ? [
                    'M',
                    // end of the string at the label
                    x + (labelPos[6] === 'left' ? 5 : -5), y,
                    'C',
                    x, y, // first break, next to the label
                    2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],
                    labelPos[2], labelPos[3], // second break
                    'L',
                    labelPos[4], labelPos[5] // base
                ] : [
                    'M',
                    // end of the string at the label
                    x + (labelPos[6] === 'left' ? 5 : -5), y,
                    'L',
                    labelPos[2], labelPos[3], // second break
                    'L',
                    labelPos[4], labelPos[5] // base
                ];
            };

            /**
             * Perform the final placement of the data labels after we have verified
             * that they fall within the plot area.
             */
            seriesTypes.pie.prototype.placeDataLabels = function() {
                each(this.points, function(point) {
                    var dataLabel = point.dataLabel,
                        _pos;
                    if (dataLabel && point.visible) {
                        _pos = dataLabel._pos;
                        if (_pos) {

                            // Shorten data labels with ellipsis if they still overflow
                            // after the pie has reached minSize (#223).
                            if (dataLabel.sideOverflow) {
                                dataLabel._attr.width =
                                    dataLabel.getBBox().width - dataLabel.sideOverflow;
                                dataLabel.css({
                                    width: dataLabel._attr.width + 'px',
                                    textOverflow: 'ellipsis'
                                });
                                dataLabel.shortened = true;
                            }

                            dataLabel.attr(dataLabel._attr);
                            dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos);
                            dataLabel.moved = true;
                        } else if (dataLabel) {
                            dataLabel.attr({
                                y: -9999
                            });
                        }
                    }
                }, this);
            };

            seriesTypes.pie.prototype.alignDataLabel = noop;

            /**
             * Verify whether the data labels are allowed to draw, or we should run more
             * translation and data label positioning to keep them inside the plot area.
             * Returns true when data labels are ready to draw.
             */
            seriesTypes.pie.prototype.verifyDataLabelOverflow = function(overflow) {

                var center = this.center,
                    options = this.options,
                    centerOption = options.center,
                    minSize = options.minSize || 80,
                    newSize = minSize,
                    // If a size is set, return true and don't try to shrink the pie
                    // to fit the labels.
                    ret = options.size !== null;

                if (!ret) {
                    // Handle horizontal size and center
                    if (centerOption[0] !== null) { // Fixed center
                        newSize = Math.max(center[2] -
                            Math.max(overflow[1], overflow[3]), minSize);

                    } else { // Auto center
                        newSize = Math.max(
                            // horizontal overflow
                            center[2] - overflow[1] - overflow[3],
                            minSize
                        );
                        // horizontal center
                        center[0] += (overflow[3] - overflow[1]) / 2;
                    }

                    // Handle vertical size and center
                    if (centerOption[1] !== null) { // Fixed center
                        newSize = Math.max(Math.min(newSize, center[2] -
                            Math.max(overflow[0], overflow[2])), minSize);

                    } else { // Auto center
                        newSize = Math.max(
                            Math.min(
                                newSize,
                                // vertical overflow
                                center[2] - overflow[0] - overflow[2]
                            ),
                            minSize
                        );
                        // vertical center
                        center[1] += (overflow[0] - overflow[2]) / 2;
                    }

                    // If the size must be decreased, we need to run translate and
                    // drawDataLabels again
                    if (newSize < center[2]) {
                        center[2] = newSize;
                        center[3] = Math.min( // #3632
                            relativeLength(options.innerSize || 0, newSize),
                            newSize
                        );
                        this.translate(center);

                        if (this.drawDataLabels) {
                            this.drawDataLabels();
                        }
                        // Else, return true to indicate that the pie and its labels is
                        // within the plot area
                    } else {
                        ret = true;
                    }
                }
                return ret;
            };
        }

        if (seriesTypes.column) {

            /**
             * Override the basic data label alignment by adjusting for the position of
             * the column
             */
            seriesTypes.column.prototype.alignDataLabel = function(
                point,
                dataLabel,
                options,
                alignTo,
                isNew
            ) {
                var inverted = this.chart.inverted,
                    series = point.series,
                    // data label box for alignment
                    dlBox = point.dlBox || point.shapeArgs,
                    below = pick(
                        point.below, // range series
                        point.plotY > pick(this.translatedThreshold, series.yAxis.len)
                    ),
                    // draw it inside the box?
                    inside = pick(options.inside, !!this.options.stacking),
                    overshoot;

                // Align to the column itself, or the top of it
                if (dlBox) { // Area range uses this method but not alignTo
                    alignTo = merge(dlBox);

                    if (alignTo.y < 0) {
                        alignTo.height += alignTo.y;
                        alignTo.y = 0;
                    }
                    overshoot = alignTo.y + alignTo.height - series.yAxis.len;
                    if (overshoot > 0) {
                        alignTo.height -= overshoot;
                    }

                    if (inverted) {
                        alignTo = {
                            x: series.yAxis.len - alignTo.y - alignTo.height,
                            y: series.xAxis.len - alignTo.x - alignTo.width,
                            width: alignTo.height,
                            height: alignTo.width
                        };
                    }

                    // Compute the alignment box
                    if (!inside) {
                        if (inverted) {
                            alignTo.x += below ? 0 : alignTo.width;
                            alignTo.width = 0;
                        } else {
                            alignTo.y += below ? alignTo.height : 0;
                            alignTo.height = 0;
                        }
                    }
                }


                // When alignment is undefined (typically columns and bars), display the
                // individual point below or above the point depending on the threshold
                options.align = pick(
                    options.align, !inverted || inside ? 'center' : below ? 'right' : 'left'
                );
                options.verticalAlign = pick(
                    options.verticalAlign,
                    inverted || inside ? 'middle' : below ? 'top' : 'bottom'
                );

                // Call the parent method
                Series.prototype.alignDataLabel.call(
                    this,
                    point,
                    dataLabel,
                    options,
                    alignTo,
                    isNew
                );

                // If label was justified and we have contrast, set it:
                if (point.isLabelJustified && point.contrastColor) {
                    point.dataLabel.css({
                        color: point.contrastColor
                    });
                }
            };
        }

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2009-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        /**
         * Highcharts module to hide overlapping data labels. This module is included in
         * Highcharts.
         */
        var Chart = H.Chart,
            each = H.each,
            objectEach = H.objectEach,
            pick = H.pick,
            addEvent = H.addEvent;

        // Collect potensial overlapping data labels. Stack labels probably don't need
        // to be considered because they are usually accompanied by data labels that lie
        // inside the columns.
        addEvent(Chart.prototype, 'render', function collectAndHide() {
            var labels = [];

            // Consider external label collectors
            each(this.labelCollectors || [], function(collector) {
                labels = labels.concat(collector());
            });

            each(this.yAxis || [], function(yAxis) {
                if (
                    yAxis.options.stackLabels &&
                    !yAxis.options.stackLabels.allowOverlap
                ) {
                    objectEach(yAxis.stacks, function(stack) {
                        objectEach(stack, function(stackItem) {
                            labels.push(stackItem.label);
                        });
                    });
                }
            });

            each(this.series || [], function(series) {
                var dlOptions = series.options.dataLabels,
                    // Range series have two collections
                    collections = series.dataLabelCollections || ['dataLabel'];

                if (
                    (dlOptions.enabled || series._hasPointLabels) &&
                    !dlOptions.allowOverlap &&
                    series.visible
                ) { // #3866
                    each(collections, function(coll) {
                        each(series.points, function(point) {
                            if (point[coll]) {
                                point[coll].labelrank = pick(
                                    point.labelrank,
                                    point.shapeArgs && point.shapeArgs.height
                                ); // #4118
                                labels.push(point[coll]);
                            }
                        });
                    });
                }
            });
            this.hideOverlappingLabels(labels);
        });

        /**
         * Hide overlapping labels. Labels are moved and faded in and out on zoom to
         * provide a smooth visual imression.
         */
        Chart.prototype.hideOverlappingLabels = function(labels) {

            var len = labels.length,
                label,
                i,
                j,
                label1,
                label2,
                isIntersecting,
                pos1,
                pos2,
                parent1,
                parent2,
                padding,
                bBox,
                intersectRect = function(x1, y1, w1, h1, x2, y2, w2, h2) {
                    return !(
                        x2 > x1 + w1 ||
                        x2 + w2 < x1 ||
                        y2 > y1 + h1 ||
                        y2 + h2 < y1
                    );
                };

            for (i = 0; i < len; i++) {
                label = labels[i];
                if (label) {

                    // Mark with initial opacity
                    label.oldOpacity = label.opacity;
                    label.newOpacity = 1;

                    // Get width and height if pure text nodes (stack labels)
                    if (!label.width) {
                        bBox = label.getBBox();
                        label.width = bBox.width;
                        label.height = bBox.height;
                    }
                }
            }

            // Prevent a situation in a gradually rising slope, that each label will
            // hide the previous one because the previous one always has lower rank.
            labels.sort(function(a, b) {
                return (b.labelrank || 0) - (a.labelrank || 0);
            });

            // Detect overlapping labels
            for (i = 0; i < len; i++) {
                label1 = labels[i];

                for (j = i + 1; j < len; ++j) {
                    label2 = labels[j];
                    if (
                        label1 && label2 &&
                        label1 !== label2 && // #6465, polar chart with connectEnds
                        label1.placed && label2.placed &&
                        label1.newOpacity !== 0 && label2.newOpacity !== 0
                    ) {
                        pos1 = label1.alignAttr;
                        pos2 = label2.alignAttr;
                        // Different panes have different positions
                        parent1 = label1.parentGroup;
                        parent2 = label2.parentGroup;
                        // Substract the padding if no background or border (#4333)
                        padding = 2 * (label1.box ? 0 : (label1.padding || 0));
                        isIntersecting = intersectRect(
                            pos1.x + parent1.translateX,
                            pos1.y + parent1.translateY,
                            label1.width - padding,
                            label1.height - padding,
                            pos2.x + parent2.translateX,
                            pos2.y + parent2.translateY,
                            label2.width - padding,
                            label2.height - padding
                        );

                        if (isIntersecting) {
                            (label1.labelrank < label2.labelrank ? label1 : label2)
                            .newOpacity = 0;
                        }
                    }
                }
            }

            // Hide or show
            each(labels, function(label) {
                var complete,
                    newOpacity;

                if (label) {
                    newOpacity = label.newOpacity;

                    if (label.oldOpacity !== newOpacity && label.placed) {

                        // Make sure the label is completely hidden to avoid catching
                        // clicks (#4362)
                        if (newOpacity) {
                            label.show(true);
                        } else {
                            complete = function() {
                                label.hide();
                            };
                        }

                        // Animate or set the opacity					
                        label.alignAttr.opacity = newOpacity;
                        label[label.isOld ? 'animate' : 'attr'](
                            label.alignAttr,
                            null,
                            complete
                        );

                    }
                    label.isOld = true;
                }
            });
        };

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var addEvent = H.addEvent,
            Chart = H.Chart,
            createElement = H.createElement,
            css = H.css,
            defaultOptions = H.defaultOptions,
            defaultPlotOptions = H.defaultPlotOptions,
            each = H.each,
            extend = H.extend,
            fireEvent = H.fireEvent,
            hasTouch = H.hasTouch,
            inArray = H.inArray,
            isObject = H.isObject,
            Legend = H.Legend,
            merge = H.merge,
            pick = H.pick,
            Point = H.Point,
            Series = H.Series,
            seriesTypes = H.seriesTypes,
            svg = H.svg,
            TrackerMixin;

        /**
         * TrackerMixin for points and graphs.
         */
        TrackerMixin = H.TrackerMixin = {

            /**
             * Draw the tracker for a point.
             */
            drawTrackerPoint: function() {
                var series = this,
                    chart = series.chart,
                    pointer = chart.pointer,
                    onMouseOver = function(e) {
                        var point = pointer.getPointFromEvent(e);
                        // undefined on graph in scatterchart
                        if (point !== undefined) {
                            pointer.isDirectTouch = true;
                            point.onMouseOver(e);
                        }
                    };

                // Add reference to the point
                each(series.points, function(point) {
                    if (point.graphic) {
                        point.graphic.element.point = point;
                    }
                    if (point.dataLabel) {
                        if (point.dataLabel.div) {
                            point.dataLabel.div.point = point;
                        } else {
                            point.dataLabel.element.point = point;
                        }
                    }
                });

                // Add the event listeners, we need to do this only once
                if (!series._hasTracking) {
                    each(series.trackerGroups, function(key) {
                        if (series[key]) { // we don't always have dataLabelsGroup
                            series[key]
                                .addClass('highcharts-tracker')
                                .on('mouseover', onMouseOver)
                                .on('mouseout', function(e) {
                                    pointer.onTrackerMouseOut(e);
                                });
                            if (hasTouch) {
                                series[key].on('touchstart', onMouseOver);
                            }


                            if (series.options.cursor) {
                                series[key]
                                    .css(css)
                                    .css({
                                        cursor: series.options.cursor
                                    });
                            }

                        }
                    });
                    series._hasTracking = true;
                }
            },

            /**
             * Draw the tracker object that sits above all data labels and markers to
             * track mouse events on the graph or points. For the line type charts
             * the tracker uses the same graphPath, but with a greater stroke width
             * for better control.
             */
            drawTrackerGraph: function() {
                var series = this,
                    options = series.options,
                    trackByArea = options.trackByArea,
                    trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath),
                    trackerPathLength = trackerPath.length,
                    chart = series.chart,
                    pointer = chart.pointer,
                    renderer = chart.renderer,
                    snap = chart.options.tooltip.snap,
                    tracker = series.tracker,
                    i,
                    onMouseOver = function() {
                        if (chart.hoverSeries !== series) {
                            series.onMouseOver();
                        }
                    },
                    /*
                     * Empirical lowest possible opacities for TRACKER_FILL for an element to stay invisible but clickable
                     * IE6: 0.002
                     * IE7: 0.002
                     * IE8: 0.002
                     * IE9: 0.00000000001 (unlimited)
                     * IE10: 0.0001 (exporting only)
                     * FF: 0.00000000001 (unlimited)
                     * Chrome: 0.000001
                     * Safari: 0.000001
                     * Opera: 0.00000000001 (unlimited)
                     */
                    TRACKER_FILL = 'rgba(192,192,192,' + (svg ? 0.0001 : 0.002) + ')';

                // Extend end points. A better way would be to use round linecaps,
                // but those are not clickable in VML.
                if (trackerPathLength && !trackByArea) {
                    i = trackerPathLength + 1;
                    while (i--) {
                        if (trackerPath[i] === 'M') { // extend left side
                            trackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], 'L');
                        }
                        if ((i && trackerPath[i] === 'M') || i === trackerPathLength) { // extend right side
                            trackerPath.splice(i, 0, 'L', trackerPath[i - 2] + snap, trackerPath[i - 1]);
                        }
                    }
                }

                // draw the tracker
                if (tracker) {
                    tracker.attr({
                        d: trackerPath
                    });
                } else if (series.graph) { // create

                    series.tracker = renderer.path(trackerPath)
                        .attr({
                            'stroke-linejoin': 'round', // #1225
                            visibility: series.visible ? 'visible' : 'hidden',
                            stroke: TRACKER_FILL,
                            fill: trackByArea ? TRACKER_FILL : 'none',
                            'stroke-width': series.graph.strokeWidth() + (trackByArea ? 0 : 2 * snap),
                            zIndex: 2
                        })
                        .add(series.group);

                    // The tracker is added to the series group, which is clipped, but is covered
                    // by the marker group. So the marker group also needs to capture events.
                    each([series.tracker, series.markerGroup], function(tracker) {
                        tracker.addClass('highcharts-tracker')
                            .on('mouseover', onMouseOver)
                            .on('mouseout', function(e) {
                                pointer.onTrackerMouseOut(e);
                            });


                        if (options.cursor) {
                            tracker.css({
                                cursor: options.cursor
                            });
                        }


                        if (hasTouch) {
                            tracker.on('touchstart', onMouseOver);
                        }
                    });
                }
            }
        };
        /* End TrackerMixin */


        /**
         * Add tracking event listener to the series group, so the point graphics
         * themselves act as trackers
         */

        if (seriesTypes.column) {
            seriesTypes.column.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
        }

        if (seriesTypes.pie) {
            seriesTypes.pie.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
        }

        if (seriesTypes.scatter) {
            seriesTypes.scatter.prototype.drawTracker = TrackerMixin.drawTrackerPoint;
        }

        /*
         * Extend Legend for item events
         */
        extend(Legend.prototype, {

            setItemEvents: function(item, legendItem, useHTML) {
                var legend = this,
                    boxWrapper = legend.chart.renderer.boxWrapper,
                    activeClass = 'highcharts-legend-' + (item.series ? 'point' : 'series') + '-active';

                // Set the events on the item group, or in case of useHTML, the item itself (#1249)
                (useHTML ? legendItem : item.legendGroup).on('mouseover', function() {
                        item.setState('hover');

                        // A CSS class to dim or hide other than the hovered series
                        boxWrapper.addClass(activeClass);


                        legendItem.css(legend.options.itemHoverStyle);

                    })
                    .on('mouseout', function() {

                        legendItem.css(merge(item.visible ? legend.itemStyle : legend.itemHiddenStyle));


                        // A CSS class to dim or hide other than the hovered series
                        boxWrapper.removeClass(activeClass);

                        item.setState();
                    })
                    .on('click', function(event) {
                        var strLegendItemClick = 'legendItemClick',
                            fnLegendItemClick = function() {
                                if (item.setVisible) {
                                    item.setVisible();
                                }
                            };

                        // Pass over the click/touch event. #4.
                        event = {
                            browserEvent: event
                        };

                        // click the name or symbol
                        if (item.firePointEvent) { // point
                            item.firePointEvent(strLegendItemClick, event, fnLegendItemClick);
                        } else {
                            fireEvent(item, strLegendItemClick, event, fnLegendItemClick);
                        }
                    });
            },

            createCheckboxForItem: function(item) {
                var legend = this;

                item.checkbox = createElement('input', {
                    type: 'checkbox',
                    checked: item.selected,
                    defaultChecked: item.selected // required by IE7
                }, legend.options.itemCheckboxStyle, legend.chart.container);

                addEvent(item.checkbox, 'click', function(event) {
                    var target = event.target;
                    fireEvent(
                        item.series || item,
                        'checkboxClick', { // #3712
                            checked: target.checked,
                            item: item
                        },
                        function() {
                            item.select();
                        }
                    );
                });
            }
        });



        // Add pointer cursor to legend itemstyle in defaultOptions
        defaultOptions.legend.itemStyle.cursor = 'pointer';



        /*
         * Extend the Chart object with interaction
         */

        extend(Chart.prototype, /** @lends Chart.prototype */ {
            /**
             * Display the zoom button.
             *
             * @private
             */
            showResetZoom: function() {
                var chart = this,
                    lang = defaultOptions.lang,
                    btnOptions = chart.options.chart.resetZoomButton,
                    theme = btnOptions.theme,
                    states = theme.states,
                    alignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox';

                function zoomOut() {
                    chart.zoomOut();
                }

                this.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, zoomOut, theme, states && states.hover)
                    .attr({
                        align: btnOptions.position.align,
                        title: lang.resetZoomTitle
                    })
                    .addClass('highcharts-reset-zoom')
                    .add()
                    .align(btnOptions.position, false, alignTo);

            },

            /**
             * Zoom out to 1:1.
             *
             * @private
             */
            zoomOut: function() {
                var chart = this;
                fireEvent(chart, 'selection', {
                    resetSelection: true
                }, function() {
                    chart.zoom();
                });
            },

            /**
             * Zoom into a given portion of the chart given by axis coordinates.
             * @param {Object} event
             *
             * @private
             */
            zoom: function(event) {
                var chart = this,
                    hasZoomed,
                    pointer = chart.pointer,
                    displayButton = false,
                    resetZoomButton;

                // If zoom is called with no arguments, reset the axes
                if (!event || event.resetSelection) {
                    each(chart.axes, function(axis) {
                        hasZoomed = axis.zoom();
                    });
                    pointer.initiated = false; // #6804

                } else { // else, zoom in on all axes
                    each(event.xAxis.concat(event.yAxis), function(axisData) {
                        var axis = axisData.axis,
                            isXAxis = axis.isXAxis;

                        // don't zoom more than minRange
                        if (pointer[isXAxis ? 'zoomX' : 'zoomY']) {
                            hasZoomed = axis.zoom(axisData.min, axisData.max);
                            if (axis.displayBtn) {
                                displayButton = true;
                            }
                        }
                    });
                }

                // Show or hide the Reset zoom button
                resetZoomButton = chart.resetZoomButton;
                if (displayButton && !resetZoomButton) {
                    chart.showResetZoom();
                } else if (!displayButton && isObject(resetZoomButton)) {
                    chart.resetZoomButton = resetZoomButton.destroy();
                }


                // Redraw
                if (hasZoomed) {
                    chart.redraw(
                        pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation
                    );
                }
            },

            /**
             * Pan the chart by dragging the mouse across the pane. This function is
             * called on mouse move, and the distance to pan is computed from chartX
             * compared to the first chartX position in the dragging operation.
             *
             * @private
             */
            pan: function(e, panning) {

                var chart = this,
                    hoverPoints = chart.hoverPoints,
                    doRedraw;

                // remove active points for shared tooltip
                if (hoverPoints) {
                    each(hoverPoints, function(point) {
                        point.setState();
                    });
                }

                each(panning === 'xy' ? [1, 0] : [1], function(isX) { // xy is used in maps
                    var axis = chart[isX ? 'xAxis' : 'yAxis'][0],
                        horiz = axis.horiz,
                        mousePos = e[horiz ? 'chartX' : 'chartY'],
                        mouseDown = horiz ? 'mouseDownX' : 'mouseDownY',
                        startPos = chart[mouseDown],
                        halfPointRange = (axis.pointRange || 0) / 2,
                        extremes = axis.getExtremes(),
                        panMin = axis.toValue(startPos - mousePos, true) +
                        halfPointRange,
                        panMax = axis.toValue(startPos + axis.len - mousePos, true) -
                        halfPointRange,
                        flipped = panMax < panMin,
                        newMin = flipped ? panMax : panMin,
                        newMax = flipped ? panMin : panMax,
                        paddedMin = Math.min(
                            extremes.dataMin,
                            axis.toValue(
                                axis.toPixels(extremes.min) - axis.minPixelPadding
                            )
                        ),
                        paddedMax = Math.max(
                            extremes.dataMax,
                            axis.toValue(
                                axis.toPixels(extremes.max) + axis.minPixelPadding
                            )
                        ),
                        spill;

                    // If the new range spills over, either to the min or max, adjust
                    // the new range.
                    spill = paddedMin - newMin;
                    if (spill > 0) {
                        newMax += spill;
                        newMin = paddedMin;
                    }
                    spill = newMax - paddedMax;
                    if (spill > 0) {
                        newMax = paddedMax;
                        newMin -= spill;
                    }

                    // Set new extremes if they are actually new
                    if (axis.series.length && newMin !== extremes.min && newMax !== extremes.max) {
                        axis.setExtremes(
                            newMin,
                            newMax,
                            false,
                            false, {
                                trigger: 'pan'
                            }
                        );
                        doRedraw = true;
                    }

                    chart[mouseDown] = mousePos; // set new reference for next run
                });

                if (doRedraw) {
                    chart.redraw(false);
                }
                css(chart.container, {
                    cursor: 'move'
                });
            }
        });

        /*
         * Extend the Point object with interaction
         */
        extend(Point.prototype, /** @lends Highcharts.Point.prototype */ {
            /**
             * Toggle the selection status of a point.
             * @param  {Boolean} [selected]
             *         When `true`, the point is selected. When `false`, the point is
             *         unselected. When `null` or `undefined`, the selection state is
             *         toggled.
             * @param  {Boolean} [accumulate=false]
             *         When `true`, the selection is added to other selected points.
             *         When `false`, other selected points are deselected. Internally in
             *         Highcharts, when {@link http://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect|allowPointSelect}
             *         is `true`, selected points are accumulated on Control, Shift or
             *         Cmd clicking the point.
             *
             * @see    Highcharts.Chart#getSelectedPoints
             *
             * @sample highcharts/members/point-select/
             *         Select a point from a button
             * @sample highcharts/chart/events-selection-points/
             *         Select a range of points through a drag selection
             * @sample maps/series/data-id/
             *         Select a point in Highmaps
             */
            select: function(selected, accumulate) {
                var point = this,
                    series = point.series,
                    chart = series.chart;

                selected = pick(selected, !point.selected);

                // fire the event with the default handler
                point.firePointEvent(selected ? 'select' : 'unselect', {
                    accumulate: accumulate
                }, function() {

                    /**
                     * Whether the point is selected or not. 
                     * @see Point#select
                     * @see Chart#getSelectedPoints
                     * @memberof Point
                     * @name selected
                     * @type {Boolean}
                     */
                    point.selected = point.options.selected = selected;
                    series.options.data[inArray(point, series.data)] = point.options;

                    point.setState(selected && 'select');

                    // unselect all other points unless Ctrl or Cmd + click
                    if (!accumulate) {
                        each(chart.getSelectedPoints(), function(loopPoint) {
                            if (loopPoint.selected && loopPoint !== point) {
                                loopPoint.selected = loopPoint.options.selected = false;
                                series.options.data[inArray(loopPoint, series.data)] = loopPoint.options;
                                loopPoint.setState('');
                                loopPoint.firePointEvent('unselect');
                            }
                        });
                    }
                });
            },

            /**
             * Runs on mouse over the point. Called internally from mouse and touch
             * events.
             * 
             * @param {Object} e The event arguments
             */
            onMouseOver: function(e) {
                var point = this,
                    series = point.series,
                    chart = series.chart,
                    pointer = chart.pointer;
                e = e ?
                    pointer.normalize(e) :
                    // In cases where onMouseOver is called directly without an event
                    pointer.getChartCoordinatesFromPoint(point, chart.inverted);
                pointer.runPointActions(e, point);
            },

            /**
             * Runs on mouse out from the point. Called internally from mouse and touch
             * events.
             */
            onMouseOut: function() {
                var point = this,
                    chart = point.series.chart;
                point.firePointEvent('mouseOut');
                each(chart.hoverPoints || [], function(p) {
                    p.setState();
                });
                chart.hoverPoints = chart.hoverPoint = null;
            },

            /**
             * Import events from the series' and point's options. Only do it on
             * demand, to save processing time on hovering.
             *
             * @private
             */
            importEvents: function() {
                if (!this.hasImportedEvents) {
                    var point = this,
                        options = merge(point.series.options.point, point.options),
                        events = options.events;

                    point.events = events;

                    H.objectEach(events, function(event, eventType) {
                        addEvent(point, eventType, event);
                    });
                    this.hasImportedEvents = true;

                }
            },

            /**
             * Set the point's state.
             * @param  {String} [state]
             *         The new state, can be one of `''` (an empty string), `hover` or
             *         `select`.
             */
            setState: function(state, move) {
                var point = this,
                    plotX = Math.floor(point.plotX), // #4586
                    plotY = point.plotY,
                    series = point.series,
                    stateOptions = series.options.states[state] || {},
                    markerOptions = defaultPlotOptions[series.type].marker &&
                    series.options.marker,
                    normalDisabled = markerOptions && markerOptions.enabled === false,
                    markerStateOptions = (markerOptions && markerOptions.states &&
                        markerOptions.states[state]) || {},
                    stateDisabled = markerStateOptions.enabled === false,
                    stateMarkerGraphic = series.stateMarkerGraphic,
                    pointMarker = point.marker || {},
                    chart = series.chart,
                    halo = series.halo,
                    haloOptions,
                    markerAttribs,
                    hasMarkers = markerOptions && series.markerAttribs,
                    newSymbol;

                state = state || ''; // empty string

                if (
                    // already has this state
                    (state === point.state && !move) ||

                    // selected points don't respond to hover
                    (point.selected && state !== 'select') ||

                    // series' state options is disabled
                    (stateOptions.enabled === false) ||

                    // general point marker's state options is disabled
                    (state && (
                        stateDisabled ||
                        (normalDisabled && markerStateOptions.enabled === false)
                    )) ||

                    // individual point marker's state options is disabled
                    (
                        state &&
                        pointMarker.states &&
                        pointMarker.states[state] &&
                        pointMarker.states[state].enabled === false
                    ) // #1610

                ) {
                    return;
                }

                if (hasMarkers) {
                    markerAttribs = series.markerAttribs(point, state);
                }

                // Apply hover styles to the existing point
                if (point.graphic) {

                    if (point.state) {
                        point.graphic.removeClass('highcharts-point-' + point.state);
                    }
                    if (state) {
                        point.graphic.addClass('highcharts-point-' + state);
                    }


                    point.graphic.animate(
                        series.pointAttribs(point, state),
                        pick(
                            chart.options.chart.animation,
                            stateOptions.animation
                        )
                    );


                    if (markerAttribs) {
                        point.graphic.animate(
                            markerAttribs,
                            pick(
                                chart.options.chart.animation, // Turn off globally
                                markerStateOptions.animation,
                                markerOptions.animation
                            )
                        );
                    }

                    // Zooming in from a range with no markers to a range with markers
                    if (stateMarkerGraphic) {
                        stateMarkerGraphic.hide();
                    }
                } else {
                    // if a graphic is not applied to each point in the normal state, create a shared
                    // graphic for the hover state
                    if (state && markerStateOptions) {
                        newSymbol = pointMarker.symbol || series.symbol;

                        // If the point has another symbol than the previous one, throw away the
                        // state marker graphic and force a new one (#1459)
                        if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) {
                            stateMarkerGraphic = stateMarkerGraphic.destroy();
                        }

                        // Add a new state marker graphic
                        if (!stateMarkerGraphic) {
                            if (newSymbol) {
                                series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(
                                        newSymbol,
                                        markerAttribs.x,
                                        markerAttribs.y,
                                        markerAttribs.width,
                                        markerAttribs.height
                                    )
                                    .add(series.markerGroup);
                                stateMarkerGraphic.currentSymbol = newSymbol;
                            }

                            // Move the existing graphic
                        } else {
                            stateMarkerGraphic[move ? 'animate' : 'attr']({ // #1054
                                x: markerAttribs.x,
                                y: markerAttribs.y
                            });
                        }

                        if (stateMarkerGraphic) {
                            stateMarkerGraphic.attr(series.pointAttribs(point, state));
                        }

                    }

                    if (stateMarkerGraphic) {
                        stateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY, chart.inverted) ? 'show' : 'hide'](); // #2450
                        stateMarkerGraphic.element.point = point; // #4310
                    }
                }

                // Show me your halo
                haloOptions = stateOptions.halo;
                if (haloOptions && haloOptions.size) {
                    if (!halo) {
                        series.halo = halo = chart.renderer.path()
                            // #5818, #5903, #6705
                            .add((point.graphic || stateMarkerGraphic).parentGroup);
                    }
                    halo[move ? 'animate' : 'attr']({
                        d: point.haloPath(haloOptions.size)
                    });
                    halo.attr({
                        'class': 'highcharts-halo highcharts-color-' +
                            pick(point.colorIndex, series.colorIndex)
                    });
                    halo.point = point; // #6055


                    halo.attr(extend({
                        'fill': point.color || series.color,
                        'fill-opacity': haloOptions.opacity,
                        'zIndex': -1 // #4929, IE8 added halo above everything
                    }, haloOptions.attributes));


                } else if (halo && halo.point && halo.point.haloPath) {
                    // Animate back to 0 on the current halo point (#6055)
                    halo.animate({
                        d: halo.point.haloPath(0)
                    });
                }

                point.state = state;
            },

            /**
             * Get the path definition for the halo, which is usually a shadow-like
             * circle around the currently hovered point.
             * @param  {Number} size
             *         The radius of the circular halo.
             * @return {Array} The path definition
             */
            haloPath: function(size) {
                var series = this.series,
                    chart = series.chart;

                return chart.renderer.symbols.circle(
                    Math.floor(this.plotX) - size,
                    this.plotY - size,
                    size * 2,
                    size * 2
                );
            }
        });

        /*
         * Extend the Series object with interaction
         */

        extend(Series.prototype, /** @lends Highcharts.Series.prototype */ {
            /**
             * Runs on mouse over the series graphical items.
             */
            onMouseOver: function() {
                var series = this,
                    chart = series.chart,
                    hoverSeries = chart.hoverSeries;

                // set normal state to previous series
                if (hoverSeries && hoverSeries !== series) {
                    hoverSeries.onMouseOut();
                }

                // trigger the event, but to save processing time,
                // only if defined
                if (series.options.events.mouseOver) {
                    fireEvent(series, 'mouseOver');
                }

                // hover this
                series.setState('hover');
                chart.hoverSeries = series;
            },

            /**
             * Runs on mouse out of the series graphical items.
             */
            onMouseOut: function() {
                // trigger the event only if listeners exist
                var series = this,
                    options = series.options,
                    chart = series.chart,
                    tooltip = chart.tooltip,
                    hoverPoint = chart.hoverPoint;

                chart.hoverSeries = null; // #182, set to null before the mouseOut event fires

                // trigger mouse out on the point, which must be in this series
                if (hoverPoint) {
                    hoverPoint.onMouseOut();
                }

                // fire the mouse out event
                if (series && options.events.mouseOut) {
                    fireEvent(series, 'mouseOut');
                }


                // hide the tooltip
                if (tooltip && !series.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) {
                    tooltip.hide();
                }

                // set normal state
                series.setState();
            },

            /**
             * Set the state of the series. Called internally on mouse interaction and
             * select operations, but it can also be called directly to visually
             * highlight a series.
             *
             * @param  {String} [state]
             *         Can be either `hover`, `select` or undefined to set to normal
             *         state.
             */
            setState: function(state) {
                var series = this,
                    options = series.options,
                    graph = series.graph,
                    stateOptions = options.states,
                    lineWidth = options.lineWidth,
                    attribs,
                    i = 0;

                state = state || '';

                if (series.state !== state) {

                    // Toggle class names
                    each([
                        series.group,
                        series.markerGroup,
                        series.dataLabelsGroup
                    ], function(group) {
                        if (group) {
                            // Old state
                            if (series.state) {
                                group.removeClass('highcharts-series-' + series.state);
                            }
                            // New state
                            if (state) {
                                group.addClass('highcharts-series-' + state);
                            }
                        }
                    });

                    series.state = state;



                    if (stateOptions[state] && stateOptions[state].enabled === false) {
                        return;
                    }

                    if (state) {
                        lineWidth = stateOptions[state].lineWidth || lineWidth + (stateOptions[state].lineWidthPlus || 0); // #4035
                    }

                    if (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML
                        attribs = {
                            'stroke-width': lineWidth
                        };

                        // Animate the graph stroke-width. By default a quick animation
                        // to hover, slower to un-hover.
                        graph.animate(
                            attribs,
                            pick(
                                series.chart.options.chart.animation,
                                stateOptions[state] && stateOptions[state].animation
                            )
                        );
                        while (series['zone-graph-' + i]) {
                            series['zone-graph-' + i].attr(attribs);
                            i = i + 1;
                        }
                    }

                }
            },

            /**
             * Show or hide the series.
             *
             * @param  {Boolean} [visible]
             *         True to show the series, false to hide. If undefined, the
             *         visibility is toggled.
             * @param  {Boolean} [redraw=true]
             *         Whether to redraw the chart after the series is altered. If doing
             *         more operations on the chart, it is a good idea to set redraw to
             *         false and call {@link Chart#redraw|chart.redraw()} after.
             */
            setVisible: function(vis, redraw) {
                var series = this,
                    chart = series.chart,
                    legendItem = series.legendItem,
                    showOrHide,
                    ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,
                    oldVisibility = series.visible;

                // if called without an argument, toggle visibility
                series.visible = vis = series.options.visible = series.userOptions.visible = vis === undefined ? !oldVisibility : vis; // #5618
                showOrHide = vis ? 'show' : 'hide';

                // show or hide elements
                each(['group', 'dataLabelsGroup', 'markerGroup', 'tracker', 'tt'], function(key) {
                    if (series[key]) {
                        series[key][showOrHide]();
                    }
                });


                // hide tooltip (#1361)
                if (chart.hoverSeries === series || (chart.hoverPoint && chart.hoverPoint.series) === series) {
                    series.onMouseOut();
                }


                if (legendItem) {
                    chart.legend.colorizeItem(series, vis);
                }


                // rescale or adapt to resized chart
                series.isDirty = true;
                // in a stack, all other series are affected
                if (series.options.stacking) {
                    each(chart.series, function(otherSeries) {
                        if (otherSeries.options.stacking && otherSeries.visible) {
                            otherSeries.isDirty = true;
                        }
                    });
                }

                // show or hide linked series
                each(series.linkedSeries, function(otherSeries) {
                    otherSeries.setVisible(vis, false);
                });

                if (ignoreHiddenSeries) {
                    chart.isDirtyBox = true;
                }
                if (redraw !== false) {
                    chart.redraw();
                }

                fireEvent(series, showOrHide);
            },

            /**
             * Show the series if hidden.
             *
             * @sample highcharts/members/series-hide/
             *         Toggle visibility from a button
             */
            show: function() {
                this.setVisible(true);
            },

            /**
             * Hide the series if visible. If the {@link
             * https://api.highcharts.com/highcharts/chart.ignoreHiddenSeries|
             * chart.ignoreHiddenSeries} option is true, the chart is redrawn without
             * this series.
             *
             * @sample highcharts/members/series-hide/
             *         Toggle visibility from a button
             */
            hide: function() {
                this.setVisible(false);
            },


            /**
             * Select or unselect the series. This means its {@link
             * Highcharts.Series.selected|selected} property is set, the checkbox in the
             * legend is toggled and when selected, the series is returned by the
             * {@link Highcharts.Chart#getSelectedSeries} function.
             *
             * @param  {Boolean} [selected]
             *         True to select the series, false to unselect. If	undefined, the
             *         selection state is toggled.
             *
             * @sample highcharts/members/series-select/
             *         Select a series from a button
             */
            select: function(selected) {
                var series = this;

                series.selected = selected = (selected === undefined) ?
                    !series.selected :
                    selected;

                if (series.checkbox) {
                    series.checkbox.checked = selected;
                }

                fireEvent(series, selected ? 'select' : 'unselect');
            },

            drawTracker: TrackerMixin.drawTrackerGraph
        });

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var Chart = H.Chart,
            each = H.each,
            inArray = H.inArray,
            isArray = H.isArray,
            isObject = H.isObject,
            pick = H.pick,
            splat = H.splat;


        /**
         * Allows setting a set of rules to apply for different screen or chart
         * sizes. Each rule specifies additional chart options.
         * 
         * @sample {highstock} stock/demo/responsive/ Stock chart
         * @sample highcharts/responsive/axis/ Axis
         * @sample highcharts/responsive/legend/ Legend
         * @sample highcharts/responsive/classname/ Class name
         * @since 5.0.0
         * @apioption responsive
         */

        /**
         * A set of rules for responsive settings. The rules are executed from
         * the top down.
         * 
         * @type {Array<Object>}
         * @sample {highcharts} highcharts/responsive/axis/ Axis changes
         * @sample {highstock} highcharts/responsive/axis/ Axis changes
         * @sample {highmaps} highcharts/responsive/axis/ Axis changes
         * @since 5.0.0
         * @apioption responsive.rules
         */

        /**
         * A full set of chart options to apply as overrides to the general
         * chart options. The chart options are applied when the given rule
         * is active.
         * 
         * A special case is configuration objects that take arrays, for example
         * [xAxis](#xAxis), [yAxis](#yAxis) or [series](#series). For these
         * collections, an `id` option is used to map the new option set to
         * an existing object. If an existing object of the same id is not found,
         * the item of the same indexupdated. So for example, setting `chartOptions`
         * with two series items without an `id`, will cause the existing chart's
         * two series to be updated with respective options.
         * 
         * @type {Object}
         * @sample {highstock} stock/demo/responsive/ Stock chart
         * @sample highcharts/responsive/axis/ Axis
         * @sample highcharts/responsive/legend/ Legend
         * @sample highcharts/responsive/classname/ Class name
         * @since 5.0.0
         * @apioption responsive.rules.chartOptions
         */

        /**
         * Under which conditions the rule applies.
         * 
         * @type {Object}
         * @since 5.0.0
         * @apioption responsive.rules.condition
         */

        /**
         * A callback function to gain complete control on when the responsive
         * rule applies. Return `true` if it applies. This opens for checking
         * against other metrics than the chart size, or example the document
         * size or other elements.
         * 
         * @type {Function}
         * @context Chart
         * @since 5.0.0
         * @apioption responsive.rules.condition.callback
         */

        /**
         * The responsive rule applies if the chart height is less than this.
         * 
         * @type {Number}
         * @since 5.0.0
         * @apioption responsive.rules.condition.maxHeight
         */

        /**
         * The responsive rule applies if the chart width is less than this.
         * 
         * @type {Number}
         * @sample highcharts/responsive/axis/ Max width is 500
         * @since 5.0.0
         * @apioption responsive.rules.condition.maxWidth
         */

        /**
         * The responsive rule applies if the chart height is greater than this.
         * 
         * @type {Number}
         * @default 0
         * @since 5.0.0
         * @apioption responsive.rules.condition.minHeight
         */

        /**
         * The responsive rule applies if the chart width is greater than this.
         * 
         * @type {Number}
         * @default 0
         * @since 5.0.0
         * @apioption responsive.rules.condition.minWidth
         */

        /**
         * Update the chart based on the current chart/document size and options for
         * responsiveness.
         */
        Chart.prototype.setResponsive = function(redraw) {
            var options = this.options.responsive,
                ruleIds = [],
                currentResponsive = this.currentResponsive,
                currentRuleIds;

            if (options && options.rules) {
                each(options.rules, function(rule) {
                    if (rule._id === undefined) {
                        rule._id = H.uniqueKey();
                    }

                    this.matchResponsiveRule(rule, ruleIds, redraw);
                }, this);
            }

            // Merge matching rules
            var mergedOptions = H.merge.apply(0, H.map(ruleIds, function(ruleId) {
                return H.find(options.rules, function(rule) {
                    return rule._id === ruleId;
                }).chartOptions;
            }));

            // Stringified key for the rules that currently apply.
            ruleIds = ruleIds.toString() || undefined;
            currentRuleIds = currentResponsive && currentResponsive.ruleIds;


            // Changes in what rules apply
            if (ruleIds !== currentRuleIds) {

                // Undo previous rules. Before we apply a new set of rules, we need to
                // roll back completely to base options (#6291).
                if (currentResponsive) {
                    this.update(currentResponsive.undoOptions, redraw);
                }

                if (ruleIds) {
                    // Get undo-options for matching rules
                    this.currentResponsive = {
                        ruleIds: ruleIds,
                        mergedOptions: mergedOptions,
                        undoOptions: this.currentOptions(mergedOptions)
                    };

                    this.update(mergedOptions, redraw);

                } else {
                    this.currentResponsive = undefined;
                }
            }
        };

        /**
         * Handle a single responsiveness rule
         */
        Chart.prototype.matchResponsiveRule = function(rule, matches) {
            var condition = rule.condition,
                fn = condition.callback || function() {
                    return this.chartWidth <= pick(condition.maxWidth, Number.MAX_VALUE) &&
                        this.chartHeight <= pick(condition.maxHeight, Number.MAX_VALUE) &&
                        this.chartWidth >= pick(condition.minWidth, 0) &&
                        this.chartHeight >= pick(condition.minHeight, 0);
                };

            if (fn.call(this)) {
                matches.push(rule._id);
            }

        };

        /**
         * Get the current values for a given set of options. Used before we update
         * the chart with a new responsiveness rule.
         * TODO: Restore axis options (by id?)
         */
        Chart.prototype.currentOptions = function(options) {

            var ret = {};

            /**
             * Recurse over a set of options and its current values,
             * and store the current values in the ret object.
             */
            function getCurrent(options, curr, ret, depth) {
                var i;
                H.objectEach(options, function(val, key) {
                    if (!depth && inArray(key, ['series', 'xAxis', 'yAxis']) > -1) {
                        val = splat(val);

                        ret[key] = [];

                        // Iterate over collections like series, xAxis or yAxis and map
                        // the items by index.
                        for (i = 0; i < val.length; i++) {
                            if (curr[key][i]) { // Item exists in current data (#6347)
                                ret[key][i] = {};
                                getCurrent(
                                    val[i],
                                    curr[key][i],
                                    ret[key][i],
                                    depth + 1
                                );
                            }
                        }
                    } else if (isObject(val)) {
                        ret[key] = isArray(val) ? [] : {};
                        getCurrent(val, curr[key] || {}, ret[key], depth + 1);
                    } else {
                        ret[key] = curr[key] || null;
                    }
                });
            }

            getCurrent(options, this.options, ret, 0);
            return ret;
        };

    }(Highcharts));
    return Highcharts
}));
/**
 * @license Highcharts JS v6.0.3 (2017-11-14)
 *
 * (c) 2009-2016 Torstein Honsi
 *
 * License: www.highcharts.com/license
 */

'use strict';
(function(factory) {
    if (typeof module === 'object' && module.exports) {
        module.exports = factory;
    } else {
        factory(Highcharts);
    }
}(function(Highcharts) {
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var deg2rad = H.deg2rad,
            isNumber = H.isNumber,
            pick = H.pick,
            relativeLength = H.relativeLength;
        H.CenteredSeriesMixin = {
            /**
             * Get the center of the pie based on the size and center options relative to the
             * plot area. Borrowed by the polar and gauge series types.
             */
            getCenter: function() {

                var options = this.options,
                    chart = this.chart,
                    slicingRoom = 2 * (options.slicedOffset || 0),
                    handleSlicingRoom,
                    plotWidth = chart.plotWidth - 2 * slicingRoom,
                    plotHeight = chart.plotHeight - 2 * slicingRoom,
                    centerOption = options.center,
                    positions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],
                    smallestSize = Math.min(plotWidth, plotHeight),
                    i,
                    value;

                for (i = 0; i < 4; ++i) {
                    value = positions[i];
                    handleSlicingRoom = i < 2 || (i === 2 && /%$/.test(value));

                    // i == 0: centerX, relative to width
                    // i == 1: centerY, relative to height
                    // i == 2: size, relative to smallestSize
                    // i == 3: innerSize, relative to size
                    positions[i] = relativeLength(value, [plotWidth, plotHeight, smallestSize, positions[2]][i]) +
                        (handleSlicingRoom ? slicingRoom : 0);

                }
                // innerSize cannot be larger than size (#3632)
                if (positions[3] > positions[2]) {
                    positions[3] = positions[2];
                }
                return positions;
            },
            /**
             * getStartAndEndRadians - Calculates start and end angles in radians.
             * Used in series types such as pie and sunburst.
             *
             * @param  {Number} start Start angle in degrees.
             * @param  {Number} end Start angle in degrees.
             * @return {object} Returns an object containing start and end angles as
             * radians.
             */
            getStartAndEndRadians: function getStartAndEndRadians(start, end) {
                var startAngle = isNumber(start) ? start : 0, // must be a number
                    endAngle = (
                        (
                            isNumber(end) && // must be a number
                            end > startAngle && // must be larger than the start angle
                            // difference must be less than 360 degrees
                            (end - startAngle) < 360
                        ) ?
                        end :
                        startAngle + 360
                    ),
                    correction = -90;
                return {
                    start: deg2rad * (startAngle + correction),
                    end: deg2rad * (endAngle + correction)
                };
            }
        };

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var CenteredSeriesMixin = H.CenteredSeriesMixin,
            each = H.each,
            extend = H.extend,
            merge = H.merge,
            splat = H.splat;
        /**
         * The Pane object allows options that are common to a set of X and Y axes.
         *
         * In the future, this can be extended to basic Highcharts and Highstock.
         *
         */
        function Pane(options, chart) {
            this.init(options, chart);
        }

        // Extend the Pane prototype
        extend(Pane.prototype, {

            coll: 'pane', // Member of chart.pane

            /**
             * Initiate the Pane object
             */
            init: function(options, chart) {
                this.chart = chart;
                this.background = [];

                chart.pane.push(this);

                this.setOptions(options);
            },

            setOptions: function(options) {

                // Set options. Angular charts have a default background (#3318)
                this.options = options = merge(
                    this.defaultOptions,
                    this.chart.angular ? {
                        background: {}
                    } : undefined,
                    options
                );
            },

            /**
             * Render the pane with its backgrounds.
             */
            render: function() {

                var options = this.options,
                    backgroundOption = this.options.background,
                    renderer = this.chart.renderer,
                    len,
                    i;

                if (!this.group) {
                    this.group = renderer.g('pane-group')
                        .attr({
                            zIndex: options.zIndex || 0
                        })
                        .add();
                }

                this.updateCenter();

                // Render the backgrounds
                if (backgroundOption) {
                    backgroundOption = splat(backgroundOption);

                    len = Math.max(
                        backgroundOption.length,
                        this.background.length || 0
                    );

                    for (i = 0; i < len; i++) {
                        if (backgroundOption[i] && this.axis) { // #6641 - if axis exists, chart is circular and apply background
                            this.renderBackground(
                                merge(
                                    this.defaultBackgroundOptions,
                                    backgroundOption[i]
                                ),
                                i
                            );
                        } else if (this.background[i]) {
                            this.background[i] = this.background[i].destroy();
                            this.background.splice(i, 1);
                        }
                    }
                }
            },

            /**
             * Render an individual pane background.
             * @param  {Object} backgroundOptions Background options
             * @param  {number} i The index of the background in this.backgrounds
             */
            renderBackground: function(backgroundOptions, i) {
                var method = 'animate';

                if (!this.background[i]) {
                    this.background[i] = this.chart.renderer.path()
                        .add(this.group);
                    method = 'attr';
                }

                this.background[i][method]({
                    'd': this.axis.getPlotBandPath(
                        backgroundOptions.from,
                        backgroundOptions.to,
                        backgroundOptions
                    )
                }).attr({

                    'fill': backgroundOptions.backgroundColor,
                    'stroke': backgroundOptions.borderColor,
                    'stroke-width': backgroundOptions.borderWidth,

                    'class': 'highcharts-pane ' + (backgroundOptions.className || '')
                });

            },

            /**
             * The pane serves as a container for axes and backgrounds for circular 
             * gauges and polar charts.
             * @since 2.3.0
             * @optionparent pane
             */
            defaultOptions: {
                /**
                 * The center of a polar chart or angular gauge, given as an array
                 * of [x, y] positions. Positions can be given as integers that transform
                 * to pixels, or as percentages of the plot area size.
                 * 
                 * @type {Array<String|Number>}
                 * @sample {highcharts} highcharts/demo/gauge-vu-meter/
                 *         Two gauges with different center
                 * @default ["50%", "50%"]
                 * @since 2.3.0
                 * @product highcharts
                 */
                center: ['50%', '50%'],

                /**
                 * The size of the pane, either as a number defining pixels, or a
                 * percentage defining a percentage of the plot are.
                 * 
                 * @type {Number|String}
                 * @sample {highcharts} highcharts/demo/gauge-vu-meter/ Smaller size
                 * @default 85%
                 * @product highcharts
                 */
                size: '85%',

                /**
                 * The start angle of the polar X axis or gauge axis, given in degrees
                 * where 0 is north. Defaults to 0.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/demo/gauge-vu-meter/
                 *         VU-meter with custom start and end angle
                 * @since 2.3.0
                 * @product highcharts
                 */
                startAngle: 0

                /**
                 * The end angle of the polar X axis or gauge value axis, given in degrees
                 * where 0 is north. Defaults to [startAngle](#pane.startAngle) + 360.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/demo/gauge-vu-meter/
                 *         VU-meter with custom start and end angle
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption pane.endAngle
                 */
            },

            /**
             * An array of background items for the pane.
             * @type Array.<Object>
             * @sample {highcharts} highcharts/demo/gauge-speedometer/
             *         Speedometer gauge with multiple backgrounds
             * @optionparent pane.background
             */
            defaultBackgroundOptions: {
                /**
                 * The class name for this background.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/css/pane/ Panes styled by CSS
                 * @sample {highstock} highcharts/css/pane/ Panes styled by CSS
                 * @sample {highmaps} highcharts/css/pane/ Panes styled by CSS
                 * @default highcharts-pane
                 * @since 5.0.0
                 * @apioption pane.background.className
                 */

                /**
                 * Tha shape of the pane background. When `solid`, the background
                 * is circular. When `arc`, the background extends only from the min
                 * to the max of the value axis.
                 * 
                 * @validvalue ["solid", "arc"]
                 * @type {String}
                 * @default solid
                 * @since 2.3.0
                 * @product highcharts
                 */
                shape: 'circle',


                /**
                 * The pixel border width of the pane background.
                 * 
                 * @type {Number}
                 * @default 1
                 * @since 2.3.0
                 * @product highcharts
                 */
                borderWidth: 1,

                /**
                 * The pane background border color.
                 * 
                 * @type {Color}
                 * @default #cccccc
                 * @since 2.3.0
                 * @product highcharts
                 */
                borderColor: '#cccccc',

                /**
                 * The background color or gradient for the pane.
                 * 
                 * @type {Color}
                 * @since 2.3.0
                 * @product highcharts
                 */
                backgroundColor: {
                    /**
                     * Definition of the gradient, similar to SVG: object literal holds
                     * start position (x1, y1) and the end position (x2, y2) relative
                     * to the shape, where 0 means top/left and 1 is bottom/right.
                     * All positions are floats between 0 and 1.
                     *
                     * @type {Object}
                     */
                    linearGradient: {
                        x1: 0,
                        y1: 0,
                        x2: 0,
                        y2: 1
                    },
                    /**
                     * The stops is an array of tuples, where the first
                     * item is a float between 0 and 1 assigning the relative position in
                     * the gradient, and the second item is the color.
                     *
                     * @default [[0, #ffffff], [1, #e6e6e6]]
                     * @type {Array<Array>}
                     */
                    stops: [
                        [0, '#ffffff'],
                        [1, '#e6e6e6']
                    ]
                },


                /** @ignore */
                from: -Number.MAX_VALUE, // corrected to axis min

                /**
                 * The inner radius of the pane background. Can be either numeric
                 * (pixels) or a percentage string.
                 * 
                 * @type {Number|String}
                 * @default 0
                 * @since 2.3.0
                 * @product highcharts
                 */
                innerRadius: 0,

                /** @ignore */
                to: Number.MAX_VALUE, // corrected to axis max

                /**
                 * The outer radius of the circular pane background. Can be either
                 * numeric (pixels) or a percentage string.
                 * 
                 * @type {Number|String}
                 * @default 105%
                 * @since 2.3.0
                 * @product highcharts
                 */
                outerRadius: '105%'
            },

            /**
             * Gets the center for the pane and its axis.
             */
            updateCenter: function(axis) {
                this.center = (axis || this.axis || {}).center =
                    CenteredSeriesMixin.getCenter.call(this);
            },

            /**
             * Destroy the pane item
             * /
            destroy: function () {
            	H.erase(this.chart.pane, this);
            	each(this.background, function (background) {
            		background.destroy();
            	});
            	this.background.length = 0;
            	this.group = this.group.destroy();
            },
            */

            /**
             * Update the pane item with new options
             * @param  {Object} options New pane options
             */
            update: function(options, redraw) {

                merge(true, this.options, options);
                this.setOptions(this.options);
                this.render();
                each(this.chart.axes, function(axis) {
                    if (axis.pane === this) {
                        axis.pane = null;
                        axis.update({}, redraw);
                    }
                }, this);
            }

        });

        H.Pane = Pane;

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var Axis = H.Axis,
            each = H.each,
            extend = H.extend,
            map = H.map,
            merge = H.merge,
            noop = H.noop,
            pick = H.pick,
            pInt = H.pInt,
            Tick = H.Tick,
            wrap = H.wrap,


            hiddenAxisMixin, // @todo Extract this to a new file
            radialAxisMixin, // @todo Extract this to a new file
            axisProto = Axis.prototype,
            tickProto = Tick.prototype;

        /**
         * Augmented methods for the x axis in order to hide it completely, used for the X axis in gauges
         */
        hiddenAxisMixin = {
            getOffset: noop,
            redraw: function() {
                this.isDirty = false; // prevent setting Y axis dirty
            },
            render: function() {
                this.isDirty = false; // prevent setting Y axis dirty
            },
            setScale: noop,
            setCategories: noop,
            setTitle: noop
        };

        /**
         * Augmented methods for the value axis
         */
        radialAxisMixin = {

            /**
             * The default options extend defaultYAxisOptions
             */
            defaultRadialGaugeOptions: {
                labels: {
                    align: 'center',
                    x: 0,
                    y: null // auto
                },
                minorGridLineWidth: 0,
                minorTickInterval: 'auto',
                minorTickLength: 10,
                minorTickPosition: 'inside',
                minorTickWidth: 1,
                tickLength: 10,
                tickPosition: 'inside',
                tickWidth: 2,
                title: {
                    rotation: 0
                },
                zIndex: 2 // behind dials, points in the series group
            },

            // Circular axis around the perimeter of a polar chart
            defaultRadialXOptions: {
                gridLineWidth: 1, // spokes
                labels: {
                    align: null, // auto
                    distance: 15,
                    x: 0,
                    y: null // auto
                },
                maxPadding: 0,
                minPadding: 0,
                showLastLabel: false,
                tickLength: 0
            },

            // Radial axis, like a spoke in a polar chart
            defaultRadialYOptions: {
                gridLineInterpolation: 'circle',
                labels: {
                    align: 'right',
                    x: -3,
                    y: -2
                },
                showLastLabel: false,
                title: {
                    x: 4,
                    text: null,
                    rotation: 90
                }
            },

            /**
             * Merge and set options
             */
            setOptions: function(userOptions) {

                var options = this.options = merge(
                    this.defaultOptions,
                    this.defaultRadialOptions,
                    userOptions
                );

                // Make sure the plotBands array is instanciated for each Axis (#2649)
                if (!options.plotBands) {
                    options.plotBands = [];
                }

            },

            /**
             * Wrap the getOffset method to return zero offset for title or labels in a radial
             * axis
             */
            getOffset: function() {
                // Call the Axis prototype method (the method we're in now is on the instance)
                axisProto.getOffset.call(this);

                // Title or label offsets are not counted
                this.chart.axisOffset[this.side] = 0;

            },


            /**
             * Get the path for the axis line. This method is also referenced in the getPlotLinePath
             * method.
             */
            getLinePath: function(lineWidth, radius) {
                var center = this.center,
                    end,
                    chart = this.chart,
                    r = pick(radius, center[2] / 2 - this.offset),
                    path;

                if (this.isCircular || radius !== undefined) {
                    path = this.chart.renderer.symbols.arc(
                        this.left + center[0],
                        this.top + center[1],
                        r,
                        r, {
                            start: this.startAngleRad,
                            end: this.endAngleRad,
                            open: true,
                            innerR: 0
                        }
                    );

                    // Bounds used to position the plotLine label next to the line
                    // (#7117)
                    path.xBounds = [this.left + center[0]];
                    path.yBounds = [this.top + center[1] - r];

                } else {
                    end = this.postTranslate(this.angleRad, r);
                    path = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y];
                }
                return path;
            },

            /**
             * Override setAxisTranslation by setting the translation to the difference
             * in rotation. This allows the translate method to return angle for
             * any given value.
             */
            setAxisTranslation: function() {

                // Call uber method
                axisProto.setAxisTranslation.call(this);

                // Set transA and minPixelPadding
                if (this.center) { // it's not defined the first time
                    if (this.isCircular) {

                        this.transA = (this.endAngleRad - this.startAngleRad) /
                            ((this.max - this.min) || 1);


                    } else {
                        this.transA = (this.center[2] / 2) / ((this.max - this.min) || 1);
                    }

                    if (this.isXAxis) {
                        this.minPixelPadding = this.transA * this.minPointOffset;
                    } else {
                        // This is a workaround for regression #2593, but categories still don't position correctly.
                        this.minPixelPadding = 0;
                    }
                }
            },

            /**
             * In case of auto connect, add one closestPointRange to the max value right before
             * tickPositions are computed, so that ticks will extend passed the real max.
             */
            beforeSetTickPositions: function() {
                // If autoConnect is true, polygonal grid lines are connected, and one closestPointRange
                // is added to the X axis to prevent the last point from overlapping the first.
                this.autoConnect = this.isCircular && pick(this.userMax, this.options.max) === undefined &&
                    this.endAngleRad - this.startAngleRad === 2 * Math.PI;

                if (this.autoConnect) {
                    this.max += (this.categories && 1) || this.pointRange || this.closestPointRange || 0; // #1197, #2260
                }
            },

            /**
             * Override the setAxisSize method to use the arc's circumference as length. This
             * allows tickPixelInterval to apply to pixel lengths along the perimeter
             */
            setAxisSize: function() {

                axisProto.setAxisSize.call(this);

                if (this.isRadial) {

                    // Set the center array
                    this.pane.updateCenter(this);

                    // The sector is used in Axis.translate to compute the translation of reversed axis points (#2570)
                    if (this.isCircular) {
                        this.sector = this.endAngleRad - this.startAngleRad;
                    }

                    // Axis len is used to lay out the ticks
                    this.len = this.width = this.height = this.center[2] * pick(this.sector, 1) / 2;


                }
            },

            /**
             * Returns the x, y coordinate of a point given by a value and a pixel distance
             * from center
             */
            getPosition: function(value, length) {
                return this.postTranslate(
                    this.isCircular ? this.translate(value) : this.angleRad, // #2848
                    pick(this.isCircular ? length : this.translate(value), this.center[2] / 2) - this.offset
                );
            },

            /**
             * Translate from intermediate plotX (angle), plotY (axis.len - radius) to final chart coordinates.
             */
            postTranslate: function(angle, radius) {

                var chart = this.chart,
                    center = this.center;

                angle = this.startAngleRad + angle;

                return {
                    x: chart.plotLeft + center[0] + Math.cos(angle) * radius,
                    y: chart.plotTop + center[1] + Math.sin(angle) * radius
                };

            },

            /**
             * Find the path for plot bands along the radial axis
             */
            getPlotBandPath: function(from, to, options) {
                var center = this.center,
                    startAngleRad = this.startAngleRad,
                    fullRadius = center[2] / 2,
                    radii = [
                        pick(options.outerRadius, '100%'),
                        options.innerRadius,
                        pick(options.thickness, 10)
                    ],
                    offset = Math.min(this.offset, 0),
                    percentRegex = /%$/,
                    start,
                    end,
                    open,
                    isCircular = this.isCircular, // X axis in a polar chart
                    ret;

                // Polygonal plot bands
                if (this.options.gridLineInterpolation === 'polygon') {
                    ret = this.getPlotLinePath(from).concat(this.getPlotLinePath(to, true));

                    // Circular grid bands
                } else {

                    // Keep within bounds
                    from = Math.max(from, this.min);
                    to = Math.min(to, this.max);

                    // Plot bands on Y axis (radial axis) - inner and outer radius depend on to and from
                    if (!isCircular) {
                        radii[0] = this.translate(from);
                        radii[1] = this.translate(to);
                    }

                    // Convert percentages to pixel values
                    radii = map(radii, function(radius) {
                        if (percentRegex.test(radius)) {
                            radius = (pInt(radius, 10) * fullRadius) / 100;
                        }
                        return radius;
                    });

                    // Handle full circle
                    if (options.shape === 'circle' || !isCircular) {
                        start = -Math.PI / 2;
                        end = Math.PI * 1.5;
                        open = true;
                    } else {
                        start = startAngleRad + this.translate(from);
                        end = startAngleRad + this.translate(to);
                    }

                    radii[0] -= offset; // #5283
                    radii[2] -= offset; // #5283

                    ret = this.chart.renderer.symbols.arc(
                        this.left + center[0],
                        this.top + center[1],
                        radii[0],
                        radii[0], {
                            start: Math.min(start, end), // Math is for reversed yAxis (#3606)
                            end: Math.max(start, end),
                            innerR: pick(radii[1], radii[0] - radii[2]),
                            open: open
                        }
                    );
                }

                return ret;
            },

            /**
             * Find the path for plot lines perpendicular to the radial axis.
             */
            getPlotLinePath: function(value, reverse) {
                var axis = this,
                    center = axis.center,
                    chart = axis.chart,
                    end = axis.getPosition(value),
                    xAxis,
                    xy,
                    tickPositions,
                    ret;

                // Spokes
                if (axis.isCircular) {
                    ret = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y];

                    // Concentric circles
                } else if (axis.options.gridLineInterpolation === 'circle') {
                    value = axis.translate(value);
                    if (value) { // a value of 0 is in the center
                        ret = axis.getLinePath(0, value);
                    }
                    // Concentric polygons
                } else {
                    // Find the X axis in the same pane
                    each(chart.xAxis, function(a) {
                        if (a.pane === axis.pane) {
                            xAxis = a;
                        }
                    });
                    ret = [];
                    value = axis.translate(value);
                    tickPositions = xAxis.tickPositions;
                    if (xAxis.autoConnect) {
                        tickPositions = tickPositions.concat([tickPositions[0]]);
                    }
                    // Reverse the positions for concatenation of polygonal plot bands
                    if (reverse) {
                        tickPositions = [].concat(tickPositions).reverse();
                    }

                    each(tickPositions, function(pos, i) {
                        xy = xAxis.getPosition(pos, value);
                        ret.push(i ? 'L' : 'M', xy.x, xy.y);
                    });

                }
                return ret;
            },

            /**
             * Find the position for the axis title, by default inside the gauge
             */
            getTitlePosition: function() {
                var center = this.center,
                    chart = this.chart,
                    titleOptions = this.options.title;

                return {
                    x: chart.plotLeft + center[0] + (titleOptions.x || 0),
                    y: chart.plotTop + center[1] - ({
                            high: 0.5,
                            middle: 0.25,
                            low: 0
                        }[titleOptions.align] *
                        center[2]) + (titleOptions.y || 0)
                };
            }

        };

        /**
         * Override axisProto.init to mix in special axis instance functions and function overrides
         */
        wrap(axisProto, 'init', function(proceed, chart, userOptions) {
            var angular = chart.angular,
                polar = chart.polar,
                isX = userOptions.isX,
                isHidden = angular && isX,
                isCircular,
                options,
                chartOptions = chart.options,
                paneIndex = userOptions.pane || 0,
                pane = this.pane = chart.pane && chart.pane[paneIndex],
                paneOptions = pane && pane.options;

            // Before prototype.init
            if (angular) {
                extend(this, isHidden ? hiddenAxisMixin : radialAxisMixin);
                isCircular = !isX;
                if (isCircular) {
                    this.defaultRadialOptions = this.defaultRadialGaugeOptions;
                }

            } else if (polar) {
                extend(this, radialAxisMixin);
                isCircular = isX;
                this.defaultRadialOptions = isX ? this.defaultRadialXOptions : merge(this.defaultYAxisOptions, this.defaultRadialYOptions);

            }

            // Disable certain features on angular and polar axes
            if (angular || polar) {
                this.isRadial = true;
                chart.inverted = false;
                chartOptions.chart.zoomType = null;
            } else {
                this.isRadial = false;
            }

            // A pointer back to this axis to borrow geometry
            if (pane && isCircular) {
                pane.axis = this;
            }

            // Run prototype.init
            proceed.call(this, chart, userOptions);

            if (!isHidden && pane && (angular || polar)) {
                options = this.options;

                // Start and end angle options are
                // given in degrees relative to top, while internal computations are
                // in radians relative to right (like SVG).
                this.angleRad = (options.angle || 0) * Math.PI / 180; // Y axis in polar charts
                this.startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180; // Gauges
                this.endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360) - 90) * Math.PI / 180; // Gauges
                this.offset = options.offset || 0;

                this.isCircular = isCircular;

            }

        });

        /**
         * Wrap auto label align to avoid setting axis-wide rotation on radial axes (#4920)
         * @param   {Function} proceed
         * @returns {String} Alignment
         */
        wrap(axisProto, 'autoLabelAlign', function(proceed) {
            if (!this.isRadial) {
                return proceed.apply(this, [].slice.call(arguments, 1));
            } // else return undefined
        });

        /**
         * Add special cases within the Tick class' methods for radial axes.
         */
        wrap(tickProto, 'getPosition', function(proceed, horiz, pos, tickmarkOffset, old) {
            var axis = this.axis;

            return axis.getPosition ?
                axis.getPosition(pos) :
                proceed.call(this, horiz, pos, tickmarkOffset, old);
        });

        /**
         * Wrap the getLabelPosition function to find the center position of the label
         * based on the distance option
         */
        wrap(tickProto, 'getLabelPosition', function(proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
            var axis = this.axis,
                optionsY = labelOptions.y,
                ret,
                centerSlot = 20, // 20 degrees to each side at the top and bottom
                align = labelOptions.align,
                angle = ((axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) / Math.PI * 180) % 360;

            if (axis.isRadial) { // Both X and Y axes in a polar chart
                ret = axis.getPosition(this.pos, (axis.center[2] / 2) + pick(labelOptions.distance, -25));

                // Automatically rotated
                if (labelOptions.rotation === 'auto') {
                    label.attr({
                        rotation: angle
                    });

                    // Vertically centered
                } else if (optionsY === null) {
                    optionsY = axis.chart.renderer.fontMetrics(label.styles.fontSize).b - label.getBBox().height / 2;
                }

                // Automatic alignment
                if (align === null) {
                    if (axis.isCircular) { // Y axis
                        if (this.label.getBBox().width > axis.len * axis.tickInterval / (axis.max - axis.min)) { // #3506
                            centerSlot = 0;
                        }
                        if (angle > centerSlot && angle < 180 - centerSlot) {
                            align = 'left'; // right hemisphere
                        } else if (angle > 180 + centerSlot && angle < 360 - centerSlot) {
                            align = 'right'; // left hemisphere
                        } else {
                            align = 'center'; // top or bottom
                        }
                    } else {
                        align = 'center';
                    }
                    label.attr({
                        align: align
                    });
                }

                ret.x += labelOptions.x;
                ret.y += optionsY;

            } else {
                ret = proceed.call(this, x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
            }
            return ret;
        });

        /**
         * Wrap the getMarkPath function to return the path of the radial marker
         */
        wrap(tickProto, 'getMarkPath', function(proceed, x, y, tickLength, tickWidth, horiz, renderer) {
            var axis = this.axis,
                endPoint,
                ret;

            if (axis.isRadial) {
                endPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength);
                ret = [
                    'M',
                    x,
                    y,
                    'L',
                    endPoint.x,
                    endPoint.y
                ];
            } else {
                ret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer);
            }
            return ret;
        });

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var each = H.each,
            noop = H.noop,
            pick = H.pick,
            defined = H.defined,
            Series = H.Series,
            seriesType = H.seriesType,
            seriesTypes = H.seriesTypes,
            seriesProto = Series.prototype,
            pointProto = H.Point.prototype;

        /**
         * The area range series is a carteseian series with higher and lower values
         * for each point along an X axis, where the area between the values is shaded.
         * Requires `highcharts-more.js`.
         * 
         * @extends plotOptions.area
         * @product highcharts highstock
         * @sample {highcharts} highcharts/demo/arearange/ Area range chart
         * @sample {highstock} stock/demo/arearange/ Area range chart
         * @optionparent plotOptions.arearange
         */
        seriesType('arearange', 'area', {


            /**
             * Pixel width of the arearange graph line.
             * 
             * @type {Number}
             * @default 1
             * @since 2.3.0
             * @product highcharts highstock
             */
            lineWidth: 1,


            /**
             * @default null
             */
            threshold: null,

            tooltip: {


                pointFormat: '<span style="color:{series.color}">\u25CF</span> {series.name}: <b>{point.low}</b> - <b>{point.high}</b><br/>' // eslint-disable-line no-dupe-keys

            },

            /**
             * Whether the whole area or just the line should respond to mouseover
             * tooltips and other mouse or touch events.
             * 
             * @type {Boolean}
             * @default true
             * @since 2.3.0
             * @product highcharts highstock
             */
            trackByArea: true,

            /**
             * Extended data labels for range series types. Range series data labels
             * have no `x` and `y` options. Instead, they have `xLow`, `xHigh`,
             * `yLow` and `yHigh` options to allow the higher and lower data label
             * sets individually.
             * 
             * @type {Object}
             * @extends plotOptions.series.dataLabels
             * @excluding x,y
             * @since 2.3.0
             * @product highcharts highstock
             */
            dataLabels: {

                align: null,
                verticalAlign: null,

                /**
                 * X offset of the lower data labels relative to the point value.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
                 * @sample {highstock} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
                 * @default 0
                 * @since 2.3.0
                 * @product highcharts highstock
                 */
                xLow: 0,

                /**
                 * X offset of the higher data labels relative to the point value.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
                 * @sample {highstock} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
                 * @default 0
                 * @since 2.3.0
                 * @product highcharts highstock
                 */
                xHigh: 0,

                /**
                 * Y offset of the lower data labels relative to the point value.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
                 * @sample {highstock} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
                 * @default 16
                 * @since 2.3.0
                 * @product highcharts highstock
                 */
                yLow: 0,

                /**
                 * Y offset of the higher data labels relative to the point value.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
                 * @sample {highstock} highcharts/plotoptions/arearange-datalabels/ Data labels on range series
                 * @default -6
                 * @since 2.3.0
                 * @product highcharts highstock
                 */
                yHigh: 0
            }

            /**
             * Whether to apply a drop shadow to the graph line. Since 2.3 the shadow
             * can be an object configuration containing `color`, `offsetX`, `offsetY`,
             *  `opacity` and `width`.
             * 
             * @type {Boolean|Object}
             * @product highcharts
             * @apioption plotOptions.arearange.shadow
             */

            // Prototype members
        }, {
            pointArrayMap: ['low', 'high'],
            dataLabelCollections: ['dataLabel', 'dataLabelUpper'],
            toYData: function(point) {
                return [point.low, point.high];
            },
            pointValKey: 'low',
            deferTranslatePolar: true,

            /**
             * Translate a point's plotHigh from the internal angle and radius measures to
             * true plotHigh coordinates. This is an addition of the toXY method found in
             * Polar.js, because it runs too early for arearanges to be considered (#3419).
             */
            highToXY: function(point) {
                // Find the polar plotX and plotY
                var chart = this.chart,
                    xy = this.xAxis.postTranslate(point.rectPlotX, this.yAxis.len - point.plotHigh);
                point.plotHighX = xy.x - chart.plotLeft;
                point.plotHigh = xy.y - chart.plotTop;
                point.plotLowX = point.plotX;
            },

            /**
             * Translate data points from raw values x and y to plotX and plotY
             */
            translate: function() {
                var series = this,
                    yAxis = series.yAxis,
                    hasModifyValue = !!series.modifyValue;

                seriesTypes.area.prototype.translate.apply(series);

                // Set plotLow and plotHigh
                each(series.points, function(point) {

                    var low = point.low,
                        high = point.high,
                        plotY = point.plotY;

                    if (high === null || low === null) {
                        point.isNull = true;
                        point.plotY = null;
                    } else {
                        point.plotLow = plotY;
                        point.plotHigh = yAxis.translate(
                            hasModifyValue ? series.modifyValue(high, point) : high,
                            0,
                            1,
                            0,
                            1
                        );
                        if (hasModifyValue) {
                            point.yBottom = point.plotHigh;
                        }
                    }
                });

                // Postprocess plotHigh
                if (this.chart.polar) {
                    each(this.points, function(point) {
                        series.highToXY(point);
                        point.tooltipPos = [
                            (point.plotHighX + point.plotLowX) / 2,
                            (point.plotHigh + point.plotLow) / 2
                        ];
                    });
                }
            },

            /**
             * Extend the line series' getSegmentPath method by applying the segment
             * path to both lower and higher values of the range
             */
            getGraphPath: function(points) {

                var highPoints = [],
                    highAreaPoints = [],
                    i,
                    getGraphPath = seriesTypes.area.prototype.getGraphPath,
                    point,
                    pointShim,
                    linePath,
                    lowerPath,
                    options = this.options,
                    connectEnds = this.chart.polar && options.connectEnds !== false,
                    connectNulls = options.connectNulls,
                    step = options.step,
                    higherPath,
                    higherAreaPath;

                points = points || this.points;
                i = points.length;

                // Create the top line and the top part of the area fill. The area fill compensates for 
                // null points by drawing down to the lower graph, moving across the null gap and 
                // starting again at the lower graph.
                i = points.length;
                while (i--) {
                    point = points[i];

                    if (!point.isNull &&
                        !connectEnds &&
                        !connectNulls &&
                        (!points[i + 1] || points[i + 1].isNull)
                    ) {
                        highAreaPoints.push({
                            plotX: point.plotX,
                            plotY: point.plotY,
                            doCurve: false // #5186, gaps in areasplinerange fill
                        });
                    }

                    pointShim = {
                        polarPlotY: point.polarPlotY,
                        rectPlotX: point.rectPlotX,
                        yBottom: point.yBottom,
                        plotX: pick(point.plotHighX, point.plotX), // plotHighX is for polar charts
                        plotY: point.plotHigh,
                        isNull: point.isNull
                    };

                    highAreaPoints.push(pointShim);

                    highPoints.push(pointShim);

                    if (!point.isNull &&
                        !connectEnds &&
                        !connectNulls &&
                        (!points[i - 1] || points[i - 1].isNull)
                    ) {
                        highAreaPoints.push({
                            plotX: point.plotX,
                            plotY: point.plotY,
                            doCurve: false // #5186, gaps in areasplinerange fill
                        });
                    }
                }

                // Get the paths
                lowerPath = getGraphPath.call(this, points);
                if (step) {
                    if (step === true) {
                        step = 'left';
                    }
                    options.step = {
                        left: 'right',
                        center: 'center',
                        right: 'left'
                    }[step]; // swap for reading in getGraphPath
                }
                higherPath = getGraphPath.call(this, highPoints);
                higherAreaPath = getGraphPath.call(this, highAreaPoints);
                options.step = step;

                // Create a line on both top and bottom of the range
                linePath = [].concat(lowerPath, higherPath);

                // For the area path, we need to change the 'move' statement into 'lineTo' or 'curveTo'
                if (!this.chart.polar && higherAreaPath[0] === 'M') {
                    higherAreaPath[0] = 'L'; // this probably doesn't work for spline			
                }

                this.graphPath = linePath;
                this.areaPath = lowerPath.concat(higherAreaPath);

                // Prepare for sideways animation
                linePath.isArea = true;
                linePath.xMap = lowerPath.xMap;
                this.areaPath.xMap = lowerPath.xMap;

                return linePath;
            },

            /**
             * Extend the basic drawDataLabels method by running it for both lower and higher
             * values.
             */
            drawDataLabels: function() {

                var data = this.data,
                    length = data.length,
                    i,
                    originalDataLabels = [],
                    dataLabelOptions = this.options.dataLabels,
                    align = dataLabelOptions.align,
                    verticalAlign = dataLabelOptions.verticalAlign,
                    inside = dataLabelOptions.inside,
                    point,
                    up,
                    inverted = this.chart.inverted;

                if (dataLabelOptions.enabled || this._hasPointLabels) {

                    // Step 1: set preliminary values for plotY and dataLabel and draw the upper labels
                    i = length;
                    while (i--) {
                        point = data[i];
                        if (point) {
                            up = inside ? point.plotHigh < point.plotLow : point.plotHigh > point.plotLow;

                            // Set preliminary values
                            point.y = point.high;
                            point._plotY = point.plotY;
                            point.plotY = point.plotHigh;

                            // Store original data labels and set preliminary label objects to be picked up
                            // in the uber method
                            originalDataLabels[i] = point.dataLabel;
                            point.dataLabel = point.dataLabelUpper;

                            // Set the default offset
                            point.below = up;
                            if (inverted) {
                                if (!align) {
                                    dataLabelOptions.align = up ? 'right' : 'left';
                                }
                            } else {
                                if (!verticalAlign) {
                                    dataLabelOptions.verticalAlign = up ? 'top' : 'bottom';
                                }
                            }

                            dataLabelOptions.x = dataLabelOptions.xHigh;
                            dataLabelOptions.y = dataLabelOptions.yHigh;
                        }
                    }

                    if (seriesProto.drawDataLabels) {
                        seriesProto.drawDataLabels.apply(this, arguments); // #1209
                    }

                    // Step 2: reorganize and handle data labels for the lower values
                    i = length;
                    while (i--) {
                        point = data[i];
                        if (point) {
                            up = inside ? point.plotHigh < point.plotLow : point.plotHigh > point.plotLow;

                            // Move the generated labels from step 1, and reassign the original data labels
                            point.dataLabelUpper = point.dataLabel;
                            point.dataLabel = originalDataLabels[i];

                            // Reset values
                            point.y = point.low;
                            point.plotY = point._plotY;

                            // Set the default offset
                            point.below = !up;
                            if (inverted) {
                                if (!align) {
                                    dataLabelOptions.align = up ? 'left' : 'right';
                                }
                            } else {
                                if (!verticalAlign) {
                                    dataLabelOptions.verticalAlign = up ? 'bottom' : 'top';
                                }

                            }

                            dataLabelOptions.x = dataLabelOptions.xLow;
                            dataLabelOptions.y = dataLabelOptions.yLow;
                        }
                    }
                    if (seriesProto.drawDataLabels) {
                        seriesProto.drawDataLabels.apply(this, arguments);
                    }
                }

                dataLabelOptions.align = align;
                dataLabelOptions.verticalAlign = verticalAlign;
            },

            alignDataLabel: function() {
                seriesTypes.column.prototype.alignDataLabel.apply(this, arguments);
            },

            drawPoints: function() {
                var series = this,
                    pointLength = series.points.length,
                    point,
                    i;

                // Draw bottom points
                seriesProto.drawPoints.apply(series, arguments);

                i = 0;
                while (i < pointLength) {
                    point = series.points[i];
                    point.lowerGraphic = point.graphic;
                    point.graphic = point.upperGraphic;
                    point._plotY = point.plotY;
                    point._plotX = point.plotX;
                    point.plotY = point.plotHigh;
                    if (defined(point.plotHighX)) {
                        point.plotX = point.plotHighX;
                    }
                    i++;
                }

                // Draw top points
                seriesProto.drawPoints.apply(series, arguments);

                i = 0;
                while (i < pointLength) {
                    point = series.points[i];
                    point.upperGraphic = point.graphic;
                    point.graphic = point.lowerGraphic;
                    point.plotY = point._plotY;
                    point.plotX = point._plotX;
                    i++;
                }
            },

            setStackedPoints: noop
        }, {
            setState: function() {
                var prevState = this.state,
                    series = this.series,
                    isPolar = series.chart.polar;


                if (!defined(this.plotHigh)) {
                    // Boost doesn't calculate plotHigh
                    this.plotHigh = series.yAxis.toPixels(this.high, true);
                }

                if (!defined(this.plotLow)) {
                    // Boost doesn't calculate plotLow
                    this.plotLow = this.plotY = series.yAxis.toPixels(this.low, true);
                }

                // Bottom state:
                pointProto.setState.apply(this, arguments);

                // Change state also for the top marker
                this.graphic = this.upperGraphic;
                this.plotY = this.plotHigh;

                if (isPolar) {
                    this.plotX = this.plotHighX;
                }

                this.state = prevState;

                if (series.stateMarkerGraphic) {
                    series.lowerStateMarkerGraphic = series.stateMarkerGraphic;
                    series.stateMarkerGraphic = series.upperStateMarkerGraphic;
                }

                pointProto.setState.apply(this, arguments);

                // Now restore defaults
                this.plotY = this.plotLow;
                this.graphic = this.lowerGraphic;

                if (isPolar) {
                    this.plotX = this.plotLowX;
                }

                if (series.stateMarkerGraphic) {
                    series.upperStateMarkerGraphic = series.stateMarkerGraphic;
                    series.stateMarkerGraphic = series.lowerStateMarkerGraphic;
                    // Lower marker is stored at stateMarkerGraphic
                    // to avoid reference duplication (#7021)
                    series.lowerStateMarkerGraphic = undefined;
                }
            },
            haloPath: function() {
                var isPolar = this.series.chart.polar,
                    path = [];

                // Bottom halo
                this.plotY = this.plotLow;
                if (isPolar) {
                    this.plotX = this.plotLowX;
                }

                path = pointProto.haloPath.apply(this, arguments);

                // Top halo
                this.plotY = this.plotHigh;
                if (isPolar) {
                    this.plotX = this.plotHighX;
                }
                path = path.concat(
                    pointProto.haloPath.apply(this, arguments)
                );

                return path;
            },
            destroy: function() {
                if (this.upperGraphic) {
                    this.upperGraphic = this.upperGraphic.destroy();
                }
                return pointProto.destroy.apply(this, arguments);
            }
        });


        /**
         * A `arearange` series. If the [type](#series.arearange.type) option
         * is not specified, it is inherited from [chart.type](#chart.type).
         * 
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * arearange](#plotOptions.arearange).
         * 
         * @type {Object}
         * @extends series,plotOptions.arearange
         * @excluding dataParser,dataURL,stack
         * @product highcharts highstock
         * @apioption series.arearange
         */

        /**
         * An array of data points for the series. For the `arearange` series
         * type, points can be given in the following ways:
         * 
         * 1.  An array of arrays with 3 or 2 values. In this case, the values
         * correspond to `x,low,high`. If the first value is a string, it is
         * applied as the name of the point, and the `x` value is inferred.
         * The `x` value can also be omitted, in which case the inner arrays
         * should be of length 2\. Then the `x` value is automatically calculated,
         * either starting at 0 and incremented by 1, or from `pointStart`
         * and `pointInterval` given in the series options.
         * 
         *  ```js
         *     data: [
         *         [0, 8, 3],
         *         [1, 1, 1],
         *         [2, 6, 8]
         *     ]
         *  ```
         * 
         * 2.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.arearange.turboThreshold),
         * this option is not available.
         * 
         *  ```js
         *     data: [{
         *         x: 1,
         *         low: 9,
         *         high: 0,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         low: 3,
         *         high: 4,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         * 
         * @type {Array<Object|Array>}
         * @extends series.line.data
         * @excluding marker,y
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
         * @product highcharts highstock
         * @apioption series.arearange.data
         */

        /**
         * The high or maximum value for each data point.
         * 
         * @type {Number}
         * @product highcharts highstock
         * @apioption series.arearange.data.high
         */

        /**
         * The low or minimum value for each data point.
         * 
         * @type {Number}
         * @product highcharts highstock
         * @apioption series.arearange.data.low
         */

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */

        var seriesType = H.seriesType,
            seriesTypes = H.seriesTypes;

        /**
         * The area spline range is a cartesian series type with higher and
         * lower Y values along an X axis. The area inside the range is colored, and
         * the graph outlining the area is a smoothed spline. Requires
         * `highcharts-more.js`.
         * 
         * @extends plotOptions.arearange
         * @excluding step
         * @since 2.3.0
         * @sample {highstock} stock/demo/areasplinerange/ Area spline range
         * @sample {highstock} stock/demo/areasplinerange/ Area spline range
         * @product highcharts highstock
         * @apioption plotOptions.areasplinerange
         */
        seriesType('areasplinerange', 'arearange', null, {
            getPointSpline: seriesTypes.spline.prototype.getPointSpline
        });

        /**
         * A `areasplinerange` series. If the [type](#series.areasplinerange.
         * type) option is not specified, it is inherited from [chart.type](#chart.
         * type).
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * areasplinerange](#plotOptions.areasplinerange).
         * 
         * @type {Object}
         * @extends series,plotOptions.areasplinerange
         * @excluding dataParser,dataURL,stack
         * @product highcharts highstock
         * @apioption series.areasplinerange
         */

        /**
         * An array of data points for the series. For the `areasplinerange`
         * series type, points can be given in the following ways:
         * 
         * 1.  An array of arrays with 3 or 2 values. In this case, the values
         * correspond to `x,low,high`. If the first value is a string, it is
         * applied as the name of the point, and the `x` value is inferred.
         * The `x` value can also be omitted, in which case the inner arrays
         * should be of length 2\. Then the `x` value is automatically calculated,
         * either starting at 0 and incremented by 1, or from `pointStart`
         * and `pointInterval` given in the series options.
         * 
         *  ```js
         *     data: [
         *         [0, 0, 5],
         *         [1, 9, 1],
         *         [2, 5, 2]
         *     ]
         *  ```
         * 
         * 2.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.areasplinerange.
         * turboThreshold), this option is not available.
         * 
         *  ```js
         *     data: [{
         *         x: 1,
         *         low: 5,
         *         high: 0,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         low: 4,
         *         high: 1,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         * 
         * @type {Array<Object|Array>}
         * @extends series.arearange.data
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
         * @product highcharts highstock
         * @apioption series.areasplinerange.data
         */

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var defaultPlotOptions = H.defaultPlotOptions,
            each = H.each,
            merge = H.merge,
            noop = H.noop,
            pick = H.pick,
            seriesType = H.seriesType,
            seriesTypes = H.seriesTypes;

        var colProto = seriesTypes.column.prototype;
        /**
         * The column range is a cartesian series type with higher and lower
         * Y values along an X axis. Requires `highcharts-more.js`. To display
         * horizontal bars, set [chart.inverted](#chart.inverted) to `true`.
         *
         * @type {Object}
         * @extends plotOptions.column
         * @excluding negativeColor,stacking,softThreshold,threshold
         * @sample {highcharts} highcharts/demo/columnrange/
         *         Inverted column range
         * @sample {highstock} highcharts/demo/columnrange/
         *         Inverted column range
         * @since 2.3.0
         * @product highcharts highstock
         * @optionparent plotOptions.columnrange
         */
        var columnRangeOptions = {

            pointRange: null,
            marker: null,
            states: {
                hover: {
                    /**
                     * @ignore-option
                     */
                    halo: false
                }
            }

            /**
             * Extended data labels for range series types. Range series data labels
             * have no `x` and `y` options. Instead, they have `xLow`, `xHigh`,
             * `yLow` and `yHigh` options to allow the higher and lower data label
             * sets individually.
             *
             * @type {Object}
             * @extends plotOptions.arearange.dataLabels
             * @since 2.3.0
             * @product highcharts highstock
             * @apioption plotOptions.columnrange.dataLabels
             */
        };
        /**
         * The ColumnRangeSeries class
         */
        seriesType('columnrange', 'arearange', merge(
            defaultPlotOptions.column,
            defaultPlotOptions.arearange,
            columnRangeOptions

        ), {
            /**
             * Translate data points from raw values x and y to plotX and plotY
             */
            translate: function() {
                var series = this,
                    yAxis = series.yAxis,
                    xAxis = series.xAxis,
                    startAngleRad = xAxis.startAngleRad,
                    start,
                    chart = series.chart,
                    isRadial = series.xAxis.isRadial,
                    safeDistance = Math.max(chart.chartWidth, chart.chartHeight) + 999,
                    plotHigh;

                // Don't draw too far outside plot area (#6835)
                function safeBounds(pixelPos) {
                    return Math.min(Math.max(-safeDistance,
                        pixelPos
                    ), safeDistance);
                }


                colProto.translate.apply(series);

                // Set plotLow and plotHigh
                each(series.points, function(point) {
                    var shapeArgs = point.shapeArgs,
                        minPointLength = series.options.minPointLength,
                        heightDifference,
                        height,
                        y;

                    point.plotHigh = plotHigh = safeBounds(
                        yAxis.translate(point.high, 0, 1, 0, 1)
                    );
                    point.plotLow = safeBounds(point.plotY);

                    // adjust shape
                    y = plotHigh;
                    height = pick(point.rectPlotY, point.plotY) - plotHigh;

                    // Adjust for minPointLength
                    if (Math.abs(height) < minPointLength) {
                        heightDifference = (minPointLength - height);
                        height += heightDifference;
                        y -= heightDifference / 2;

                        // Adjust for negative ranges or reversed Y axis (#1457)
                    } else if (height < 0) {
                        height *= -1;
                        y -= height;
                    }

                    if (isRadial) {

                        start = point.barX + startAngleRad;
                        point.shapeType = 'path';
                        point.shapeArgs = {
                            d: series.polarArc(y + height, y, start, start + point.pointWidth)
                        };
                    } else {

                        shapeArgs.height = height;
                        shapeArgs.y = y;

                        point.tooltipPos = chart.inverted ? [
                            yAxis.len + yAxis.pos - chart.plotLeft - y - height / 2,
                            xAxis.len + xAxis.pos - chart.plotTop - shapeArgs.x -
                            shapeArgs.width / 2,
                            height
                        ] : [
                            xAxis.left - chart.plotLeft + shapeArgs.x +
                            shapeArgs.width / 2,
                            yAxis.pos - chart.plotTop + y + height / 2,
                            height
                        ]; // don't inherit from column tooltip position - #3372
                    }
                });
            },
            directTouch: true,
            trackerGroups: ['group', 'dataLabelsGroup'],
            drawGraph: noop,
            getSymbol: noop,
            crispCol: colProto.crispCol,
            drawPoints: colProto.drawPoints,
            drawTracker: colProto.drawTracker,
            getColumnMetrics: colProto.getColumnMetrics,
            pointAttribs: colProto.pointAttribs,

            // Overrides from modules that may be loaded after this module
            animate: function() {
                return colProto.animate.apply(this, arguments);
            },
            polarArc: function() {
                return colProto.polarArc.apply(this, arguments);
            },
            translate3dPoints: function() {
                return colProto.translate3dPoints.apply(this, arguments);
            },
            translate3dShapes: function() {
                return colProto.translate3dShapes.apply(this, arguments);
            }
        }, {
            setState: colProto.pointClass.prototype.setState
        });


        /**
         * A `columnrange` series. If the [type](#series.columnrange.type)
         * option is not specified, it is inherited from [chart.type](#chart.
         * type).
         *
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * columnrange](#plotOptions.columnrange).
         *
         * @type {Object}
         * @extends series,plotOptions.columnrange
         * @excluding dataParser,dataURL,stack
         * @product highcharts highstock
         * @apioption series.columnrange
         */

        /**
         * An array of data points for the series. For the `columnrange` series
         * type, points can be given in the following ways:
         *
         * 1.  An array of arrays with 3 or 2 values. In this case, the values
         * correspond to `x,low,high`. If the first value is a string, it is
         * applied as the name of the point, and the `x` value is inferred.
         * The `x` value can also be omitted, in which case the inner arrays
         * should be of length 2\. Then the `x` value is automatically calculated,
         * either starting at 0 and incremented by 1, or from `pointStart`
         * and `pointInterval` given in the series options.
         *
         *  ```js
         *     data: [
         *         [0, 4, 2],
         *         [1, 2, 1],
         *         [2, 9, 10]
         *     ]
         *  ```
         *
         * 2.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.columnrange.
         * turboThreshold), this option is not available.
         *
         *  ```js
         *     data: [{
         *         x: 1,
         *         low: 0,
         *         high: 4,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         low: 5,
         *         high: 3,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         *
         * @type {Array<Object|Array>}
         * @extends series.arearange.data
         * @excluding marker
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
         * @product highcharts highstock
         * @apioption series.columnrange.data
         */


    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var each = H.each,
            isNumber = H.isNumber,
            merge = H.merge,
            noop = H.noop,
            pick = H.pick,
            pInt = H.pInt,
            Series = H.Series,
            seriesType = H.seriesType,
            TrackerMixin = H.TrackerMixin;


        /** 
         * Gauges are circular plots displaying one or more values with a dial pointing
         * to values along the perimeter.
         *
         * @sample highcharts/demo/gauge-speedometer/ Gauge chart
         * @extends {plotOptions.line}
         * @excluding animationLimit,boostThreshold,connectEnds,connectNulls,cropThreshold,dashStyle,findNearestPointBy,getExtremesFromAll,marker,pointPlacement,softThreshold,stacking,step,threshold,turboThreshold,zoneAxis,zones
         * @product highcharts
         * @optionparent plotOptions.gauge
         */
        seriesType('gauge', 'line', {

            /**
             * Data labels for the gauge. For gauges, the data labels are enabled
             * by default and shown in a bordered box below the point.
             * 
             * @type {Object}
             * @extends plotOptions.series.dataLabels
             * @since 2.3.0
             * @product highcharts
             */
            dataLabels: {

                /**
                 * Enable or disable the data labels.
                 * 
                 * @type {Boolean}
                 * @since 2.3.0
                 * @product highcharts highmaps
                 */
                enabled: true,

                defer: false,

                /**
                 * The y position offset of the label relative to the center of the
                 * gauge.
                 * 
                 * @type {Number}
                 * @default 15
                 * @since 2.3.0
                 * @product highcharts highmaps
                 */
                y: 15,

                /**
                 * The border radius in pixels for the gauge's data label.
                 * 
                 * @type {Number}
                 * @default 3
                 * @since 2.3.0
                 * @product highcharts highmaps
                 */
                borderRadius: 3,

                crop: false,

                /**
                 * The vertical alignment of the data label.
                 * 
                 * @type {String}
                 * @default top
                 * @product highcharts highmaps
                 */
                verticalAlign: 'top',

                /**
                 * The Z index of the data labels. A value of 2 display them behind
                 * the dial.
                 * 
                 * @type {Number}
                 * @default 2
                 * @since 2.1.5
                 * @product highcharts highmaps
                 */
                zIndex: 2,

                // Presentational

                /**
                 * The border width in pixels for the gauge data label.
                 * 
                 * @type {Number}
                 * @default 1
                 * @since 2.3.0
                 * @product highcharts highmaps
                 */
                borderWidth: 1,

                /**
                 * The border color for the data label.
                 * 
                 * @type {Color}
                 * @default #cccccc
                 * @since 2.3.0
                 * @product highcharts highmaps
                 */
                borderColor: '#cccccc'

            },

            /**
             * Options for the dial or arrow pointer of the gauge.
             * 
             * In styled mode, the dial is styled with the `.highcharts-gauge-
             * series .highcharts-dial` rule.
             * 
             * @type {Object}
             * @sample {highcharts} highcharts/css/gauge/ Styled mode
             * @since 2.3.0
             * @product highcharts
             */


            dial: {

                /**
                 * The length of the dial's base part, relative to the total radius
                 * or length of the dial.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/plotoptions/gauge-dial/
                 *         Dial options demonstrated
                 * @default 70%
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption plotOptions.gauge.dial.baseLength
                 */

                /**
                 * The pixel width of the base of the gauge dial. The base is the part
                 * closest to the pivot, defined by baseLength.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/gauge-dial/
                 *         Dial options demonstrated
                 * @default 3
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption plotOptions.gauge.dial.baseWidth
                 */

                /**
                 * The radius or length of the dial, in percentages relative to the
                 * radius of the gauge itself.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/plotoptions/gauge-dial/
                 *         Dial options demonstrated
                 * @default 80%
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption plotOptions.gauge.dial.radius
                 */

                /**
                 * The length of the dial's rear end, the part that extends out on the
                 * other side of the pivot. Relative to the dial's length.
                 * 
                 * @type {String}
                 * @sample {highcharts} highcharts/plotoptions/gauge-dial/ Dial options demonstrated
                 * @default 10%
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption plotOptions.gauge.dial.rearLength
                 */

                /**
                 * The width of the top of the dial, closest to the perimeter. The pivot
                 * narrows in from the base to the top.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/gauge-dial/ Dial options demonstrated
                 * @default 1
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption plotOptions.gauge.dial.topWidth
                 */



                /**
                 * The background or fill color of the gauge's dial.
                 * 
                 * @type {Color}
                 * @sample {highcharts} highcharts/plotoptions/gauge-dial/ Dial options demonstrated
                 * @default #000000
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption plotOptions.gauge.dial.backgroundColor
                 */

                /**
                 * The border color or stroke of the gauge's dial. By default, the borderWidth
                 * is 0, so this must be set in addition to a custom border color.
                 * 
                 * @type {Color}
                 * @sample {highcharts} highcharts/plotoptions/gauge-dial/ Dial options demonstrated
                 * @default #cccccc
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption plotOptions.gauge.dial.borderColor
                 */

                /**
                 * The width of the gauge dial border in pixels.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/gauge-dial/ Dial options demonstrated
                 * @default 0
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption plotOptions.gauge.dial.borderWidth
                 */


            },

            /**
             * Allow the dial to overshoot the end of the perimeter axis by this
             * many degrees. Say if the gauge axis goes from 0 to 60, a value of
             * 100, or 1000, will show 5 degrees beyond the end of the axis.
             * 
             * @type {Number}
             * @see [wrap](#plotOptions.gauge.wrap)
             * @sample {highcharts} highcharts/plotoptions/gauge-overshoot/
             *         Allow 5 degrees overshoot
             * @default 0
             * @since 3.0.10
             * @product highcharts
             * @apioption plotOptions.gauge.overshoot
             */

            /**
             * Options for the pivot or the center point of the gauge.
             * 
             * In styled mode, the pivot is styled with the `.highcharts-gauge-
             * series .highcharts-pivot` rule.
             * 
             * @type {Object}
             * @sample {highcharts} highcharts/css/gauge/ Styled mode
             * @since 2.3.0
             * @product highcharts
             */
            pivot: {

                /**
                 * The pixel radius of the pivot.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/gauge-pivot/ Pivot options demonstrated
                 * @default 5
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption plotOptions.gauge.pivot.radius
                 */



                /**
                 * The border or stroke width of the pivot.
                 * 
                 * @type {Number}
                 * @sample {highcharts} highcharts/plotoptions/gauge-pivot/ Pivot options demonstrated
                 * @default 0
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption plotOptions.gauge.pivot.borderWidth
                 */

                /**
                 * The border or stroke color of the pivot. In able to change this,
                 * the borderWidth must also be set to something other than the default
                 * 0.
                 * 
                 * @type {Color}
                 * @sample {highcharts} highcharts/plotoptions/gauge-pivot/ Pivot options demonstrated
                 * @default #cccccc
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption plotOptions.gauge.pivot.borderColor
                 */

                /**
                 * The background color or fill of the pivot.
                 * 
                 * @type {Color}
                 * @sample {highcharts} highcharts/plotoptions/gauge-pivot/ Pivot options demonstrated
                 * @default #000000
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption plotOptions.gauge.pivot.backgroundColor
                 */

            },

            tooltip: {
                headerFormat: ''
            },

            /**
             * Whether to display this particular series or series type in the
             * legend. Defaults to false for gauge series.
             * 
             * @type {Boolean}
             * @since 2.3.0
             * @product highcharts
             */
            showInLegend: false

            /**
             * When this option is `true`, the dial will wrap around the axes. For
             * instance, in a full-range gauge going from 0 to 360, a value of 400
             * will point to 40\. When `wrap` is `false`, the dial stops at 360.
             * 
             * @type {Boolean}
             * @see [overshoot](#plotOptions.gauge.overshoot)
             * @default true
             * @since 3.0
             * @product highcharts
             * @apioption plotOptions.gauge.wrap
             */



            // Prototype members
        }, {
            // chart.angular will be set to true when a gauge series is present, and this will
            // be used on the axes
            angular: true,
            directTouch: true, // #5063
            drawGraph: noop,
            fixedBox: true,
            forceDL: true,
            noSharedTooltip: true,
            trackerGroups: ['group', 'dataLabelsGroup'],

            /**
             * Calculate paths etc
             */
            translate: function() {

                var series = this,
                    yAxis = series.yAxis,
                    options = series.options,
                    center = yAxis.center;

                series.generatePoints();

                each(series.points, function(point) {

                    var dialOptions = merge(options.dial, point.dial),
                        radius = (pInt(pick(dialOptions.radius, 80)) * center[2]) / 200,
                        baseLength = (pInt(pick(dialOptions.baseLength, 70)) * radius) / 100,
                        rearLength = (pInt(pick(dialOptions.rearLength, 10)) * radius) / 100,
                        baseWidth = dialOptions.baseWidth || 3,
                        topWidth = dialOptions.topWidth || 1,
                        overshoot = options.overshoot,
                        rotation = yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true);

                    // Handle the wrap and overshoot options
                    if (isNumber(overshoot)) {
                        overshoot = overshoot / 180 * Math.PI;
                        rotation = Math.max(yAxis.startAngleRad - overshoot, Math.min(yAxis.endAngleRad + overshoot, rotation));

                    } else if (options.wrap === false) {
                        rotation = Math.max(yAxis.startAngleRad, Math.min(yAxis.endAngleRad, rotation));
                    }

                    rotation = rotation * 180 / Math.PI;

                    point.shapeType = 'path';
                    point.shapeArgs = {
                        d: dialOptions.path || [
                            'M', -rearLength, -baseWidth / 2,
                            'L',
                            baseLength, -baseWidth / 2,
                            radius, -topWidth / 2,
                            radius, topWidth / 2,
                            baseLength, baseWidth / 2, -rearLength, baseWidth / 2,
                            'z'
                        ],
                        translateX: center[0],
                        translateY: center[1],
                        rotation: rotation
                    };

                    // Positions for data label
                    point.plotX = center[0];
                    point.plotY = center[1];
                });
            },

            /**
             * Draw the points where each point is one needle
             */
            drawPoints: function() {

                var series = this,
                    center = series.yAxis.center,
                    pivot = series.pivot,
                    options = series.options,
                    pivotOptions = options.pivot,
                    renderer = series.chart.renderer;

                each(series.points, function(point) {

                    var graphic = point.graphic,
                        shapeArgs = point.shapeArgs,
                        d = shapeArgs.d,
                        dialOptions = merge(options.dial, point.dial); // #1233

                    if (graphic) {
                        graphic.animate(shapeArgs);
                        shapeArgs.d = d; // animate alters it
                    } else {
                        point.graphic = renderer[point.shapeType](shapeArgs)
                            .attr({
                                rotation: shapeArgs.rotation, // required by VML when animation is false
                                zIndex: 1
                            })
                            .addClass('highcharts-dial')
                            .add(series.group);


                        // Presentational attributes
                        point.graphic.attr({
                            stroke: dialOptions.borderColor || 'none',
                            'stroke-width': dialOptions.borderWidth || 0,
                            fill: dialOptions.backgroundColor || '#000000'
                        });

                    }
                });

                // Add or move the pivot
                if (pivot) {
                    pivot.animate({ // #1235
                        translateX: center[0],
                        translateY: center[1]
                    });
                } else {
                    series.pivot = renderer.circle(0, 0, pick(pivotOptions.radius, 5))
                        .attr({
                            zIndex: 2
                        })
                        .addClass('highcharts-pivot')
                        .translate(center[0], center[1])
                        .add(series.group);


                    // Presentational attributes
                    series.pivot.attr({
                        'stroke-width': pivotOptions.borderWidth || 0,
                        stroke: pivotOptions.borderColor || '#cccccc',
                        fill: pivotOptions.backgroundColor || '#000000'
                    });

                }
            },

            /**
             * Animate the arrow up from startAngle
             */
            animate: function(init) {
                var series = this;

                if (!init) {
                    each(series.points, function(point) {
                        var graphic = point.graphic;

                        if (graphic) {
                            // start value
                            graphic.attr({
                                rotation: series.yAxis.startAngleRad * 180 / Math.PI
                            });

                            // animate
                            graphic.animate({
                                rotation: point.shapeArgs.rotation
                            }, series.options.animation);
                        }
                    });

                    // delete this function to allow it only once
                    series.animate = null;
                }
            },

            render: function() {
                this.group = this.plotGroup(
                    'group',
                    'series',
                    this.visible ? 'visible' : 'hidden',
                    this.options.zIndex,
                    this.chart.seriesGroup
                );
                Series.prototype.render.call(this);
                this.group.clip(this.chart.clipRect);
            },

            /**
             * Extend the basic setData method by running processData and generatePoints immediately,
             * in order to access the points from the legend.
             */
            setData: function(data, redraw) {
                Series.prototype.setData.call(this, data, false);
                this.processData();
                this.generatePoints();
                if (pick(redraw, true)) {
                    this.chart.redraw();
                }
            },

            /**
             * If the tracking module is loaded, add the point tracker
             */
            drawTracker: TrackerMixin && TrackerMixin.drawTrackerPoint

            // Point members
        }, {
            /**
             * Don't do any hover colors or anything
             */
            setState: function(state) {
                this.state = state;
            }
        });

        /**
         * A `gauge` series. If the [type](#series.gauge.type) option is not
         * specified, it is inherited from [chart.type](#chart.type).
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * gauge](#plotOptions.gauge).
         * 
         * @type {Object}
         * @extends series,plotOptions.gauge
         * @excluding dataParser,dataURL,stack
         * @product highcharts
         * @apioption series.gauge
         */

        /**
         * An array of data points for the series. For the `gauge` series type,
         * points can be given in the following ways:
         * 
         * 1.  An array of numerical values. In this case, the numerical values
         * will be interpreted as `y` options. Example:
         * 
         *  ```js
         *  data: [0, 5, 3, 5]
         *  ```
         * 
         * 2.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.gauge.turboThreshold),
         * this option is not available.
         * 
         *  ```js
         *     data: [{
         *     y: 6,
         *     name: "Point2",
         *     color: "#00FF00"
         * }, {
         *     y: 8,
         *     name: "Point1",
         *     color: "#FF00FF"
         * }]</pre>
         * 
         * The typical gauge only contains a single data value.
         * 
         * @type {Array<Object|Number>}
         * @extends series.line.data
         * @excluding drilldown,marker,x
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
         * @product highcharts
         * @apioption series.gauge.data
         */

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var each = H.each,
            noop = H.noop,
            pick = H.pick,
            seriesType = H.seriesType,
            seriesTypes = H.seriesTypes;

        /**
         * The boxplot series type.
         *
         * @constructor seriesTypes.boxplot
         * @augments seriesTypes.column
         */

        /**
         * A box plot is a convenient way of depicting groups of data through their
         * five-number summaries: the smallest observation (sample minimum), lower
         * quartile (Q1), median (Q2), upper quartile (Q3), and largest observation
         * (sample maximum).
         * 
         * @sample highcharts/demo/box-plot/ Box plot
         * @extends {plotOptions.column}
         * @product highcharts
         * @optionparent plotOptions.boxplot
         */
        seriesType('boxplot', 'column', {

            threshold: null,

            tooltip: {


                pointFormat: '<span style="color:{point.color}">\u25CF</span> <b> {series.name}</b><br/>' + // eslint-disable-line no-dupe-keys
                    'Maximum: {point.high}<br/>' +
                    'Upper quartile: {point.q3}<br/>' +
                    'Median: {point.median}<br/>' +
                    'Lower quartile: {point.q1}<br/>' +
                    'Minimum: {point.low}<br/>'

            },

            /**
             * The length of the whiskers, the horizontal lines marking low and
             * high values. It can be a numerical pixel value, or a percentage
             * value of the box width. Set `0` to disable whiskers.
             * 
             * @type {Number|String}
             * @sample {highcharts} highcharts/plotoptions/box-plot-styling/
             *         True by default
             * @default 50%
             * @since 3.0
             * @product highcharts
             */
            whiskerLength: '50%',


            /**
             * The fill color of the box.
             * 
             * @type {Color}
             * @see In styled mode, the fill color can be set with the
             * `.highcharts-boxplot-box` class.
             * @sample {highcharts} highcharts/plotoptions/box-plot-styling/
             *         Box plot styling
             * @default #ffffff
             * @since 3.0
             * @product highcharts
             */
            fillColor: '#ffffff',

            /**
             * The width of the line surrounding the box. If any of [stemWidth](#plotOptions.
             * boxplot.stemWidth), [medianWidth](#plotOptions.boxplot.medianWidth)
             * or [whiskerWidth](#plotOptions.boxplot.whiskerWidth) are `null`,
             *  the lineWidth also applies to these lines.
             * 
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/box-plot-styling/
             *         Box plot styling
             * @sample {highcharts} highcharts/plotoptions/error-bar-styling/
             *         Error bar styling
             * @default 1
             * @since 3.0
             * @product highcharts
             */
            lineWidth: 1,

            /**
             * The color of the median line. If `null`, the general series color
             * applies.
             * 
             * @type {Color}
             * @see In styled mode, the median stroke width can be set with the
             * `.highcharts-boxplot-median` class.
             * 
             * @sample {highcharts} highcharts/plotoptions/box-plot-styling/
             *         Box plot styling
             * @sample {highcharts} highcharts/css/boxplot/
             *         Box plot in styled mode
             * @sample {highcharts} highcharts/plotoptions/error-bar-styling/
             *         Error bar styling
             * @default null
             * @since 3.0
             * @product highcharts
             * @apioption plotOptions.boxplot.medianColor
             */

            /**
             * The pixel width of the median line. If `null`, the [lineWidth](#plotOptions.
             * boxplot.lineWidth) is used.
             * 
             * @type {Number}
             * @see In styled mode, the median stroke width can be set with the
             * `.highcharts-boxplot-median` class.
             * @sample {highcharts} highcharts/plotoptions/box-plot-styling/
             *         Box plot styling
             * @sample {highcharts} highcharts/css/boxplot/
             *         Box plot in styled mode
             * @default 2
             * @since 3.0
             * @product highcharts
             */
            medianWidth: 2,

            states: {
                hover: {
                    brightness: -0.3
                }
            },
            /**
             * The color of the stem, the vertical line extending from the box to
             * the whiskers. If `null`, the series color is used.
             * 
             * @type {Color}
             * @see In styled mode, the stem stroke can be set with the `.highcharts-boxplot-stem` class.
             * @sample {highcharts} highcharts/plotoptions/box-plot-styling/
             *         Box plot styling
             * @sample {highcharts} highcharts/css/boxplot/
             *         Box plot in styled mode
             * @sample {highcharts} highcharts/plotoptions/error-bar-styling/
             *         Error bar styling
             * @default null
             * @since 3.0
             * @product highcharts
             * @apioption plotOptions.boxplot.stemColor
             */

            /**
             * The dash style of the stem, the vertical line extending from the
             * box to the whiskers.
             * 
             * @validvalue ["Solid", "ShortDash", "ShortDot", "ShortDashDot",
             *         "ShortDashDotDot", "Dot", "Dash" ,"LongDash", "DashDot",
             *         "LongDashDot", "LongDashDotDot"]
             * @type {String}
             * @sample {highcharts} highcharts/plotoptions/box-plot-styling/
             *         Box plot styling
             * @sample {highcharts} highcharts/css/boxplot/
             *         Box plot in styled mode
             * @sample {highcharts} highcharts/plotoptions/error-bar-styling/
             *         Error bar styling
             * @default Solid
             * @since 3.0
             * @product highcharts
             * @apioption plotOptions.boxplot.stemDashStyle
             */

            /**
             * The width of the stem, the vertical line extending from the box to
             * the whiskers. If `null`, the width is inherited from the [lineWidth](#plotOptions.
             * boxplot.lineWidth) option.
             * 
             * @type {Number}
             * @see In styled mode, the stem stroke width can be set with the `.
             * highcharts-boxplot-stem` class.
             * @sample {highcharts} highcharts/plotoptions/box-plot-styling/
             *         Box plot styling
             * @sample {highcharts} highcharts/css/boxplot/
             *         Box plot in styled mode
             * @sample {highcharts} highcharts/plotoptions/error-bar-styling/
             *         Error bar styling
             * @default null
             * @since 3.0
             * @product highcharts
             * @apioption plotOptions.boxplot.stemWidth
             */

            /**
             * The color of the whiskers, the horizontal lines marking low and high
             * values. When `null`, the general series color is used.
             * 
             * @type {Color}
             * @see In styled mode, the whisker stroke can be set with the `.highcharts-boxplot-whisker` class .
             * @sample {highcharts} highcharts/plotoptions/box-plot-styling/
             *         Box plot styling
             * @sample {highcharts} highcharts/css/boxplot/
             *         Box plot in styled mode
             * @default null
             * @since 3.0
             * @product highcharts
             * @apioption plotOptions.boxplot.whiskerColor
             */

            /**
             * The line width of the whiskers, the horizontal lines marking low
             * and high values. When `null`, the general [lineWidth](#plotOptions.
             * boxplot.lineWidth) applies.
             * 
             * @type {Number}
             * @see In styled mode, the whisker stroke width can be set with the
             * `.highcharts-boxplot-whisker` class.
             * 
             * @sample {highcharts} highcharts/plotoptions/box-plot-styling/
             *         Box plot styling
             * @sample {highcharts} highcharts/css/boxplot/
             *         Box plot in styled mode
             * @since 3.0
             * @product highcharts
             */
            whiskerWidth: 2


        }, /** @lends seriesTypes.boxplot */ {
            pointArrayMap: ['low', 'q1', 'median', 'q3', 'high'], // array point configs are mapped to this
            toYData: function(point) { // return a plain array for speedy calculation
                return [point.low, point.q1, point.median, point.q3, point.high];
            },
            pointValKey: 'high', // defines the top of the tracker


            /**
             * Get presentational attributes
             */
            pointAttribs: function(point) {
                var options = this.options,
                    color = (point && point.color) || this.color;

                return {
                    'fill': point.fillColor || options.fillColor || color,
                    'stroke': options.lineColor || color,
                    'stroke-width': options.lineWidth || 0
                };
            },


            /**
             * Disable data labels for box plot
             */
            drawDataLabels: noop,

            /**
             * Translate data points from raw values x and y to plotX and plotY
             */
            translate: function() {
                var series = this,
                    yAxis = series.yAxis,
                    pointArrayMap = series.pointArrayMap;

                seriesTypes.column.prototype.translate.apply(series);

                // do the translation on each point dimension
                each(series.points, function(point) {
                    each(pointArrayMap, function(key) {
                        if (point[key] !== null) {
                            point[key + 'Plot'] = yAxis.translate(point[key], 0, 1, 0, 1);
                        }
                    });
                });
            },

            /**
             * Draw the data points
             */
            drawPoints: function() {
                var series = this,
                    points = series.points,
                    options = series.options,
                    chart = series.chart,
                    renderer = chart.renderer,
                    q1Plot,
                    q3Plot,
                    highPlot,
                    lowPlot,
                    medianPlot,
                    medianPath,
                    crispCorr,
                    crispX = 0,
                    boxPath,
                    width,
                    left,
                    right,
                    halfWidth,
                    doQuartiles = series.doQuartiles !== false, // error bar inherits this series type but doesn't do quartiles
                    pointWiskerLength,
                    whiskerLength = series.options.whiskerLength;


                each(points, function(point) {

                    var graphic = point.graphic,
                        verb = graphic ? 'animate' : 'attr',
                        shapeArgs = point.shapeArgs; // the box


                    var boxAttr,
                        stemAttr = {},
                        whiskersAttr = {},
                        medianAttr = {},
                        color = point.color || series.color;


                    if (point.plotY !== undefined) {

                        // crisp vector coordinates
                        width = shapeArgs.width;
                        left = Math.floor(shapeArgs.x);
                        right = left + width;
                        halfWidth = Math.round(width / 2);
                        q1Plot = Math.floor(doQuartiles ? point.q1Plot : point.lowPlot);
                        q3Plot = Math.floor(doQuartiles ? point.q3Plot : point.lowPlot);
                        highPlot = Math.floor(point.highPlot);
                        lowPlot = Math.floor(point.lowPlot);

                        if (!graphic) {
                            point.graphic = graphic = renderer.g('point')
                                .add(series.group);

                            point.stem = renderer.path()
                                .addClass('highcharts-boxplot-stem')
                                .add(graphic);

                            if (whiskerLength) {
                                point.whiskers = renderer.path()
                                    .addClass('highcharts-boxplot-whisker')
                                    .add(graphic);
                            }
                            if (doQuartiles) {
                                point.box = renderer.path(boxPath)
                                    .addClass('highcharts-boxplot-box')
                                    .add(graphic);
                            }
                            point.medianShape = renderer.path(medianPath)
                                .addClass('highcharts-boxplot-median')
                                .add(graphic);
                        }






                        // Stem attributes
                        stemAttr.stroke = point.stemColor || options.stemColor || color;
                        stemAttr['stroke-width'] = pick(point.stemWidth, options.stemWidth, options.lineWidth);
                        stemAttr.dashstyle = point.stemDashStyle || options.stemDashStyle;
                        point.stem.attr(stemAttr);

                        // Whiskers attributes
                        if (whiskerLength) {
                            whiskersAttr.stroke = point.whiskerColor || options.whiskerColor || color;
                            whiskersAttr['stroke-width'] = pick(point.whiskerWidth, options.whiskerWidth, options.lineWidth);
                            point.whiskers.attr(whiskersAttr);
                        }

                        if (doQuartiles) {
                            boxAttr = series.pointAttribs(point);
                            point.box.attr(boxAttr);
                        }


                        // Median attributes
                        medianAttr.stroke = point.medianColor || options.medianColor || color;
                        medianAttr['stroke-width'] = pick(point.medianWidth, options.medianWidth, options.lineWidth);
                        point.medianShape.attr(medianAttr);





                        // The stem
                        crispCorr = (point.stem.strokeWidth() % 2) / 2;
                        crispX = left + halfWidth + crispCorr;
                        point.stem[verb]({
                            d: [
                                // stem up
                                'M',
                                crispX, q3Plot,
                                'L',
                                crispX, highPlot,

                                // stem down
                                'M',
                                crispX, q1Plot,
                                'L',
                                crispX, lowPlot
                            ]
                        });

                        // The box
                        if (doQuartiles) {
                            crispCorr = (point.box.strokeWidth() % 2) / 2;
                            q1Plot = Math.floor(q1Plot) + crispCorr;
                            q3Plot = Math.floor(q3Plot) + crispCorr;
                            left += crispCorr;
                            right += crispCorr;
                            point.box[verb]({
                                d: [
                                    'M',
                                    left, q3Plot,
                                    'L',
                                    left, q1Plot,
                                    'L',
                                    right, q1Plot,
                                    'L',
                                    right, q3Plot,
                                    'L',
                                    left, q3Plot,
                                    'z'
                                ]
                            });
                        }

                        // The whiskers
                        if (whiskerLength) {
                            crispCorr = (point.whiskers.strokeWidth() % 2) / 2;
                            highPlot = highPlot + crispCorr;
                            lowPlot = lowPlot + crispCorr;
                            pointWiskerLength = (/%$/).test(whiskerLength) ? halfWidth * parseFloat(whiskerLength) / 100 : whiskerLength / 2;
                            point.whiskers[verb]({
                                d: [
                                    // High whisker
                                    'M',
                                    crispX - pointWiskerLength,
                                    highPlot,
                                    'L',
                                    crispX + pointWiskerLength,
                                    highPlot,

                                    // Low whisker
                                    'M',
                                    crispX - pointWiskerLength,
                                    lowPlot,
                                    'L',
                                    crispX + pointWiskerLength,
                                    lowPlot
                                ]
                            });
                        }

                        // The median
                        medianPlot = Math.round(point.medianPlot);
                        crispCorr = (point.medianShape.strokeWidth() % 2) / 2;
                        medianPlot = medianPlot + crispCorr;

                        point.medianShape[verb]({
                            d: [
                                'M',
                                left,
                                medianPlot,
                                'L',
                                right,
                                medianPlot
                            ]
                        });
                    }
                });

            },
            setStackedPoints: noop // #3890


        });

        /**
         * A `boxplot` series. If the [type](#series.boxplot.type) option is
         * not specified, it is inherited from [chart.type](#chart.type).
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * boxplot](#plotOptions.boxplot).
         * 
         * @type {Object}
         * @extends series,plotOptions.boxplot
         * @excluding dataParser,dataURL,stack
         * @product highcharts
         * @apioption series.boxplot
         */

        /**
         * An array of data points for the series. For the `boxplot` series
         * type, points can be given in the following ways:
         * 
         * 1.  An array of arrays with 6 or 5 values. In this case, the values
         * correspond to `x,low,q1,median,q3,high`. If the first value is a
         * string, it is applied as the name of the point, and the `x` value
         * is inferred. The `x` value can also be omitted, in which case the
         * inner arrays should be of length 5\. Then the `x` value is automatically
         * calculated, either starting at 0 and incremented by 1, or from `pointStart`
         * and `pointInterval` given in the series options.
         * 
         *  ```js
         *     data: [
         *         [0, 3, 0, 10, 3, 5],
         *         [1, 7, 8, 7, 2, 9],
         *         [2, 6, 9, 5, 1, 3]
         *     ]
         *  ```
         * 
         * 2.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.boxplot.turboThreshold),
         * this option is not available.
         * 
         *  ```js
         *     data: [{
         *         x: 1,
         *         low: 4,
         *         q1: 9,
         *         median: 9,
         *         q3: 1,
         *         high: 10,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         low: 5,
         *         q1: 7,
         *         median: 3,
         *         q3: 6,
         *         high: 2,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         * 
         * @type {Array<Object|Array>}
         * @extends series.line.data
         * @excluding marker
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
         * @product highcharts
         * @apioption series.boxplot.data
         */

        /**
         * The `high` value for each data point, signifying the highest value
         * in the sample set. The top whisker is drawn here.
         * 
         * @type {Number}
         * @product highcharts
         * @apioption series.boxplot.data.high
         */

        /**
         * The `low` value for each data point, signifying the lowest value
         * in the sample set. The bottom whisker is drawn here.
         * 
         * @type {Number}
         * @product highcharts
         * @apioption series.boxplot.data.low
         */

        /**
         * The median for each data point. This is drawn as a line through the
         * middle area of the box.
         * 
         * @type {Number}
         * @product highcharts
         * @apioption series.boxplot.data.median
         */

        /**
         * The lower quartile for each data point. This is the bottom of the
         * box.
         * 
         * @type {Number}
         * @product highcharts
         * @apioption series.boxplot.data.q1
         */

        /**
         * The higher quartile for each data point. This is the top of the box.
         * 
         * @type {Number}
         * @product highcharts
         * @apioption series.boxplot.data.q3
         */


    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var each = H.each,
            noop = H.noop,
            seriesType = H.seriesType,
            seriesTypes = H.seriesTypes;

        /**  
         * Error bars are a graphical representation of the variability of data and are
         * used on graphs to indicate the error, or uncertainty in a reported
         * measurement.
         *
         * @sample highcharts/demo/error-bar/ Error bars
         * @extends {plotOptions.boxplot}
         * @product highcharts highstock
         * @optionparent plotOptions.errorbar
         */
        seriesType('errorbar', 'boxplot', {


            /**
             * The main color of the bars. This can be overridden by [stemColor](#plotOptions.
             * errorbar.stemColor) and [whiskerColor](#plotOptions.errorbar.whiskerColor)
             * individually.
             * 
             * @type {Color}
             * @sample {highcharts} highcharts/plotoptions/error-bar-styling/ Error bar styling
             * @default #000000
             * @since 3.0
             * @product highcharts
             */
            color: '#000000',


            grouping: false,

            /**
             * The parent series of the error bar. The default value links it to
             * the previous series. Otherwise, use the id of the parent series.
             * 
             * @type {String}
             * @default :previous
             * @since 3.0
             * @product highcharts
             */
            linkedTo: ':previous',

            tooltip: {
                pointFormat: '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.low}</b> - <b>{point.high}</b><br/>'
            },

            /**
             * The line width of the whiskers, the horizontal lines marking low
             * and high values. When `null`, the general [lineWidth](#plotOptions.
             * errorbar.lineWidth) applies.
             * 
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/error-bar-styling/ Error bar styling
             * @default null
             * @since 3.0
             * @product highcharts
             */
            whiskerWidth: null

            // Prototype members
        }, {
            type: 'errorbar',
            pointArrayMap: ['low', 'high'], // array point configs are mapped to this
            toYData: function(point) { // return a plain array for speedy calculation
                return [point.low, point.high];
            },
            pointValKey: 'high', // defines the top of the tracker
            doQuartiles: false,
            drawDataLabels: seriesTypes.arearange ? function() {
                var valKey = this.pointValKey;
                seriesTypes.arearange.prototype.drawDataLabels.call(this);
                // Arearange drawDataLabels does not reset point.y to high, but to low after drawing. #4133 
                each(this.data, function(point) {
                    point.y = point[valKey];
                });
            } : noop,

            /**
             * Get the width and X offset, either on top of the linked series column
             * or standalone
             */
            getColumnMetrics: function() {
                return (this.linkedParent && this.linkedParent.columnMetrics) ||
                    seriesTypes.column.prototype.getColumnMetrics.call(this);
            }
        });

        /**
         * A `errorbar` series. If the [type](#series.errorbar.type) option
         * is not specified, it is inherited from [chart.type](#chart.type).
         * 
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * errorbar](#plotOptions.errorbar).
         * 
         * @type {Object}
         * @extends series,plotOptions.errorbar
         * @excluding dataParser,dataURL,stack
         * @product highcharts
         * @apioption series.errorbar
         */

        /**
         * An array of data points for the series. For the `errorbar` series
         * type, points can be given in the following ways:
         * 
         * 1.  An array of arrays with 3 or 2 values. In this case, the values
         * correspond to `x,low,high`. If the first value is a string, it is
         * applied as the name of the point, and the `x` value is inferred.
         * The `x` value can also be omitted, in which case the inner arrays
         * should be of length 2\. Then the `x` value is automatically calculated,
         * either starting at 0 and incremented by 1, or from `pointStart`
         * and `pointInterval` given in the series options.
         * 
         *  ```js
         *     data: [
         *         [0, 10, 2],
         *         [1, 1, 8],
         *         [2, 4, 5]
         *     ]
         *  ```
         * 
         * 2.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.errorbar.turboThreshold),
         * this option is not available.
         * 
         *  ```js
         *     data: [{
         *         x: 1,
         *         low: 0,
         *         high: 0,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         low: 5,
         *         high: 5,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         * 
         * @type {Array<Object|Array>}
         * @extends series.arearange.data
         * @excluding dataLabels,drilldown,marker
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
         * @product highcharts
         * @apioption series.errorbar.data
         */

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var correctFloat = H.correctFloat,
            isNumber = H.isNumber,
            pick = H.pick,
            Point = H.Point,
            Series = H.Series,
            seriesType = H.seriesType,
            seriesTypes = H.seriesTypes;

        /**
         * A waterfall chart displays sequentially introduced positive or negative
         * values in cumulative columns.
         *
         * @sample highcharts/demo/waterfall/ Waterfall chart
         * @extends {plotOptions.column}
         * @product highcharts
         * @optionparent plotOptions.waterfall
         */
        seriesType('waterfall', 'column', {

            dataLabels: {
                inside: true
            },


            /**
             * The width of the line connecting waterfall columns.
             * 
             * @type {Number}
             * @default 1
             * @product highcharts
             */
            lineWidth: 1,

            /**
             * The color of the line that connects columns in a waterfall series.
             * 
             * 
             * In styled mode, the stroke can be set with the `.highcharts-graph` class.
             * 
             * @type {Color}
             * @default #333333
             * @since 3.0
             * @product highcharts
             */
            lineColor: '#333333',

            /**
             * A name for the dash style to use for the line connecting the columns
             * of the waterfall series. Possible values:
             * 
             * *   Solid
             * *   ShortDash
             * *   ShortDot
             * *   ShortDashDot
             * *   ShortDashDotDot
             * *   Dot
             * *   Dash
             * *   LongDash
             * *   DashDot
             * *   LongDashDot
             * *   LongDashDotDot
             * 
             * In styled mode, the stroke dash-array can be set with the `.
             * highcharts-graph` class.
             * 
             * @type {String}
             * @default Dot
             * @since 3.0
             * @product highcharts
             */
            dashStyle: 'dot',

            /**
             * The color of the border of each waterfall column.
             * 
             * In styled mode, the border stroke can be set with the `.highcharts-point` class.
             * 
             * @type {Color}
             * @default #333333
             * @since 3.0
             * @product highcharts
             */
            borderColor: '#333333',

            states: {
                hover: {
                    lineWidthPlus: 0 // #3126
                }
            }


            /**
             * The color used specifically for positive point columns. When not
             * specified, the general series color is used.
             * 
             * In styled mode, the waterfall colors can be set with the
             * `.highcharts-point-negative`, `.highcharts-sum` and
             * `.highcharts-intermediate-sum` classes.
             * 
             * @type {Color}
             * @sample {highcharts} highcharts/demo/waterfall/ Waterfall
             * @product highcharts
             * @apioption plotOptions.waterfall.upColor
             */

            // Prototype members
        }, {
            pointValKey: 'y',

            /**
             * Translate data points from raw values
             */
            translate: function() {
                var series = this,
                    options = series.options,
                    yAxis = series.yAxis,
                    len,
                    i,
                    points,
                    point,
                    shapeArgs,
                    stack,
                    y,
                    yValue,
                    previousY,
                    previousIntermediate,
                    range,
                    minPointLength = pick(options.minPointLength, 5),
                    halfMinPointLength = minPointLength / 2,
                    threshold = options.threshold,
                    stacking = options.stacking,
                    stackIndicator,
                    tooltipY;

                // run column series translate
                seriesTypes.column.prototype.translate.apply(series);

                previousY = previousIntermediate = threshold;
                points = series.points;

                for (i = 0, len = points.length; i < len; i++) {
                    // cache current point object
                    point = points[i];
                    yValue = series.processedYData[i];
                    shapeArgs = point.shapeArgs;

                    // get current stack
                    stack = stacking && yAxis.stacks[(series.negStacks && yValue < threshold ? '-' : '') + series.stackKey];
                    stackIndicator = series.getStackIndicator(
                        stackIndicator,
                        point.x,
                        series.index
                    );
                    range = stack ?
                        stack[point.x].points[stackIndicator.key] : [0, yValue];

                    // override point value for sums
                    // #3710 Update point does not propagate to sum
                    if (point.isSum) {
                        point.y = correctFloat(yValue);
                    } else if (point.isIntermediateSum) {
                        point.y = correctFloat(yValue - previousIntermediate); // #3840
                    }
                    // up points
                    y = Math.max(previousY, previousY + point.y) + range[0];
                    shapeArgs.y = yAxis.translate(y, 0, 1, 0, 1);

                    // sum points
                    if (point.isSum) {
                        shapeArgs.y = yAxis.translate(range[1], 0, 1, 0, 1);
                        shapeArgs.height = Math.min(yAxis.translate(range[0], 0, 1, 0, 1), yAxis.len) -
                            shapeArgs.y; // #4256

                    } else if (point.isIntermediateSum) {
                        shapeArgs.y = yAxis.translate(range[1], 0, 1, 0, 1);
                        shapeArgs.height = Math.min(yAxis.translate(previousIntermediate, 0, 1, 0, 1), yAxis.len) -
                            shapeArgs.y;
                        previousIntermediate = range[1];

                        // If it's not the sum point, update previous stack end position and get
                        // shape height (#3886)
                    } else {
                        shapeArgs.height = yValue > 0 ?
                            yAxis.translate(previousY, 0, 1, 0, 1) - shapeArgs.y :
                            yAxis.translate(previousY, 0, 1, 0, 1) - yAxis.translate(previousY - yValue, 0, 1, 0, 1);

                        previousY += stack && stack[point.x] ? stack[point.x].total : yValue;
                    }

                    // #3952 Negative sum or intermediate sum not rendered correctly
                    if (shapeArgs.height < 0) {
                        shapeArgs.y += shapeArgs.height;
                        shapeArgs.height *= -1;
                    }

                    point.plotY = shapeArgs.y = Math.round(shapeArgs.y) - (series.borderWidth % 2) / 2;
                    shapeArgs.height = Math.max(Math.round(shapeArgs.height), 0.001); // #3151
                    point.yBottom = shapeArgs.y + shapeArgs.height;

                    if (shapeArgs.height <= minPointLength && !point.isNull) {
                        shapeArgs.height = minPointLength;
                        shapeArgs.y -= halfMinPointLength;
                        point.plotY = shapeArgs.y;
                        if (point.y < 0) {
                            point.minPointLengthOffset = -halfMinPointLength;
                        } else {
                            point.minPointLengthOffset = halfMinPointLength;
                        }
                    } else {
                        point.minPointLengthOffset = 0;
                    }

                    // Correct tooltip placement (#3014)
                    tooltipY = point.plotY + (point.negative ? shapeArgs.height : 0);

                    if (series.chart.inverted) {
                        point.tooltipPos[0] = yAxis.len - tooltipY;
                    } else {
                        point.tooltipPos[1] = tooltipY;
                    }
                }
            },

            /**
             * Call default processData then override yData to reflect waterfall's extremes on yAxis
             */
            processData: function(force) {
                var series = this,
                    options = series.options,
                    yData = series.yData,
                    points = series.options.data, // #3710 Update point does not propagate to sum
                    point,
                    dataLength = yData.length,
                    threshold = options.threshold || 0,
                    subSum,
                    sum,
                    dataMin,
                    dataMax,
                    y,
                    i;

                sum = subSum = dataMin = dataMax = threshold;

                for (i = 0; i < dataLength; i++) {
                    y = yData[i];
                    point = points && points[i] ? points[i] : {};

                    if (y === 'sum' || point.isSum) {
                        yData[i] = correctFloat(sum);
                    } else if (y === 'intermediateSum' || point.isIntermediateSum) {
                        yData[i] = correctFloat(subSum);
                    } else {
                        sum += y;
                        subSum += y;
                    }
                    dataMin = Math.min(sum, dataMin);
                    dataMax = Math.max(sum, dataMax);
                }

                Series.prototype.processData.call(this, force);

                // Record extremes only if stacking was not set:
                if (!series.options.stacking) {
                    series.dataMin = dataMin;
                    series.dataMax = dataMax;
                }
            },

            /**
             * Return y value or string if point is sum
             */
            toYData: function(pt) {
                if (pt.isSum) {
                    return (pt.x === 0 ? null : 'sum'); // #3245 Error when first element is Sum or Intermediate Sum
                }
                if (pt.isIntermediateSum) {
                    return (pt.x === 0 ? null : 'intermediateSum'); // #3245
                }
                return pt.y;
            },


            /**
             * Postprocess mapping between options and SVG attributes
             */
            pointAttribs: function(point, state) {

                var upColor = this.options.upColor,
                    attr;

                // Set or reset up color (#3710, update to negative)
                if (upColor && !point.options.color) {
                    point.color = point.y > 0 ? upColor : null;
                }

                attr = seriesTypes.column.prototype.pointAttribs.call(this, point, state);

                // The dashStyle option in waterfall applies to the graph, not
                // the points
                delete attr.dashstyle;

                return attr;
            },


            /**
             * Return an empty path initially, because we need to know the stroke-width in order 
             * to set the final path.
             */
            getGraphPath: function() {
                return ['M', 0, 0];
            },

            /**
             * Draw columns' connector lines
             */
            getCrispPath: function() {

                var data = this.data,
                    length = data.length,
                    lineWidth = this.graph.strokeWidth() + this.borderWidth,
                    normalizer = Math.round(lineWidth) % 2 / 2,
                    reversedYAxis = this.yAxis.reversed,
                    path = [],
                    prevArgs,
                    pointArgs,
                    i,
                    d;

                for (i = 1; i < length; i++) {
                    pointArgs = data[i].shapeArgs;
                    prevArgs = data[i - 1].shapeArgs;

                    d = [
                        'M',
                        prevArgs.x + prevArgs.width,
                        prevArgs.y + data[i - 1].minPointLengthOffset + normalizer,
                        'L',
                        pointArgs.x,
                        prevArgs.y + data[i - 1].minPointLengthOffset + normalizer
                    ];

                    if (
                        (data[i - 1].y < 0 && !reversedYAxis) ||
                        (data[i - 1].y > 0 && reversedYAxis)
                    ) {
                        d[2] += prevArgs.height;
                        d[5] += prevArgs.height;
                    }

                    path = path.concat(d);
                }

                return path;
            },

            /**
             * The graph is initally drawn with an empty definition, then updated with
             * crisp rendering.
             */
            drawGraph: function() {
                Series.prototype.drawGraph.call(this);
                this.graph.attr({
                    d: this.getCrispPath()
                });
            },

            /**
             * Waterfall has stacking along the x-values too.
             */
            setStackedPoints: function() {
                var series = this,
                    options = series.options,
                    stackedYLength,
                    i;

                Series.prototype.setStackedPoints.apply(series, arguments);

                stackedYLength = series.stackedYData ? series.stackedYData.length : 0;

                // Start from the second point:
                for (i = 1; i < stackedYLength; i++) {
                    if (!options.data[i].isSum &&
                        !options.data[i].isIntermediateSum
                    ) {
                        // Sum previous stacked data as waterfall can grow up/down:
                        series.stackedYData[i] += series.stackedYData[i - 1];
                    }
                }
            },

            /**
             * Extremes for a non-stacked series are recorded in processData.
             * In case of stacking, use Series.stackedYData to calculate extremes.
             */
            getExtremes: function() {
                if (this.options.stacking) {
                    return Series.prototype.getExtremes.apply(this, arguments);
                }
            }


            // Point members
        }, {
            getClassName: function() {
                var className = Point.prototype.getClassName.call(this);

                if (this.isSum) {
                    className += ' highcharts-sum';
                } else if (this.isIntermediateSum) {
                    className += ' highcharts-intermediate-sum';
                }
                return className;
            },
            /**
             * Pass the null test in ColumnSeries.translate.
             */
            isValid: function() {
                return isNumber(this.y, true) || this.isSum || this.isIntermediateSum;
            }

        });

        /**
         * A `waterfall` series. If the [type](#series.waterfall.type) option
         * is not specified, it is inherited from [chart.type](#chart.type).
         * 
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * waterfall](#plotOptions.waterfall).
         * 
         * @type {Object}
         * @extends series,plotOptions.waterfall
         * @excluding dataParser,dataURL
         * @product highcharts
         * @apioption series.waterfall
         */

        /**
         * An array of data points for the series. For the `waterfall` series
         * type, points can be given in the following ways:
         * 
         * 1.  An array of numerical values. In this case, the numerical values
         * will be interpreted as `y` options. The `x` values will be automatically
         * calculated, either starting at 0 and incremented by 1, or from `pointStart`
         * and `pointInterval` given in the series options. If the axis has
         * categories, these will be used. Example:
         * 
         *  ```js
         *  data: [0, 5, 3, 5]
         *  ```
         * 
         * 2.  An array of arrays with 2 values. In this case, the values correspond
         * to `x,y`. If the first value is a string, it is applied as the name
         * of the point, and the `x` value is inferred.
         * 
         *  ```js
         *     data: [
         *         [0, 7],
         *         [1, 8],
         *         [2, 3]
         *     ]
         *  ```
         * 
         * 3.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.waterfall.turboThreshold),
         * this option is not available.
         * 
         *  ```js
         *     data: [{
         *         x: 1,
         *         y: 8,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         y: 8,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         * 
         * @type {Array<Object|Array|Number>}
         * @extends series.line.data
         * @excluding marker
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
         * @product highcharts
         * @apioption series.waterfall.data
         */


        /**
         * When this property is true, the points acts as a summary column for
         * the values added or substracted since the last intermediate sum,
         * or since the start of the series. The `y` value is ignored.
         * 
         * @type {Boolean}
         * @sample {highcharts} highcharts/demo/waterfall/ Waterfall
         * @default false
         * @product highcharts
         * @apioption series.waterfall.data.isIntermediateSum
         */

        /**
         * When this property is true, the point display the total sum across
         * the entire series. The `y` value is ignored.
         * 
         * @type {Boolean}
         * @sample {highcharts} highcharts/demo/waterfall/ Waterfall
         * @default false
         * @product highcharts
         * @apioption series.waterfall.data.isSum
         */

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var LegendSymbolMixin = H.LegendSymbolMixin,
            noop = H.noop,
            Series = H.Series,
            seriesType = H.seriesType,
            seriesTypes = H.seriesTypes;

        /**
         * A polygon series can be used to draw any freeform shape in the cartesian
         * coordinate system. A fill is applied with the `color` option, and
         * stroke is applied through `lineWidth` and `lineColor` options. Requires
         * the `highcharts-more.js` file.
         * 
         * @type {Object}
         * @extends plotOptions.scatter
         * @excluding softThreshold,threshold
         * @sample {highcharts} highcharts/demo/polygon/ Polygon
         * @sample {highstock} highcharts/demo/polygon/ Polygon
         * @since 4.1.0
         * @product highcharts highstock
         * @optionparent plotOptions.polygon
         */
        seriesType('polygon', 'scatter', {
            marker: {
                enabled: false,
                states: {
                    hover: {
                        enabled: false
                    }
                }
            },
            stickyTracking: false,
            tooltip: {
                followPointer: true,
                pointFormat: ''
            },
            trackByArea: true

            // Prototype members
        }, {
            type: 'polygon',
            getGraphPath: function() {

                var graphPath = Series.prototype.getGraphPath.call(this),
                    i = graphPath.length + 1;

                // Close all segments
                while (i--) {
                    if ((i === graphPath.length || graphPath[i] === 'M') && i > 0) {
                        graphPath.splice(i, 0, 'z');
                    }
                }
                this.areaPath = graphPath;
                return graphPath;
            },
            drawGraph: function() {

                this.options.fillColor = this.color; // Hack into the fill logic in area.drawGraph

                seriesTypes.area.prototype.drawGraph.call(this);
            },
            drawLegendSymbol: LegendSymbolMixin.drawRectangle,
            drawTracker: Series.prototype.drawTracker,
            setStackedPoints: noop // No stacking points on polygons (#5310)
        });



        /**
         * A `polygon` series. If the [type](#series.polygon.type) option is
         * not specified, it is inherited from [chart.type](#chart.type).
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * polygon](#plotOptions.polygon).
         * 
         * @type {Object}
         * @extends series,plotOptions.polygon
         * @excluding dataParser,dataURL,stack
         * @product highcharts highstock
         * @apioption series.polygon
         */

        /**
         * An array of data points for the series. For the `polygon` series
         * type, points can be given in the following ways:
         * 
         * 1.  An array of numerical values. In this case, the numerical values
         * will be interpreted as `y` options. The `x` values will be automatically
         * calculated, either starting at 0 and incremented by 1, or from `pointStart`
         * and `pointInterval` given in the series options. If the axis has
         * categories, these will be used. Example:
         * 
         *  ```js
         *  data: [0, 5, 3, 5]
         *  ```
         * 
         * 2.  An array of arrays with 2 values. In this case, the values correspond
         * to `x,y`. If the first value is a string, it is applied as the name
         * of the point, and the `x` value is inferred.
         * 
         *  ```js
         *     data: [
         *         [0, 10],
         *         [1, 3],
         *         [2, 1]
         *     ]
         *  ```
         * 
         * 3.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.polygon.turboThreshold),
         * this option is not available.
         * 
         *  ```js
         *     data: [{
         *         x: 1,
         *         y: 1,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         y: 8,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         * 
         * @type {Array<Object|Array>}
         * @extends series.line.data
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
         * @product highcharts highstock
         * @apioption series.polygon.data
         */

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */
        var arrayMax = H.arrayMax,
            arrayMin = H.arrayMin,
            Axis = H.Axis,
            color = H.color,
            each = H.each,
            isNumber = H.isNumber,
            noop = H.noop,
            pick = H.pick,
            pInt = H.pInt,
            Point = H.Point,
            Series = H.Series,
            seriesType = H.seriesType,
            seriesTypes = H.seriesTypes;


        /**
         * A bubble series is a three dimensional series type where each point renders
         * an X, Y and Z value. Each points is drawn as a bubble where the position
         * along the X and Y axes mark the X and Y values, and the size of the bubble
         * relates to the Z value. Requires `highcharts-more.js`.
         *
         * @sample {highcharts} highcharts/demo/bubble/ Bubble chart
         * @extends plotOptions.scatter
         * @product highcharts highstock
         * @optionparent plotOptions.bubble
         */
        seriesType('bubble', 'scatter', {

            dataLabels: {
                formatter: function() { // #2945
                    return this.point.z;
                },
                inside: true,
                verticalAlign: 'middle'
            },

            /**
             * Whether to display negative sized bubbles. The threshold is given
             * by the [zThreshold](#plotOptions.bubble.zThreshold) option, and negative
             * bubbles can be visualized by setting [negativeColor](#plotOptions.
             * bubble.negativeColor).
             * 
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/bubble-negative/
             *         Negative bubbles
             * @default true
             * @since 3.0
             * @product highcharts
             * @apioption plotOptions.bubble.displayNegative
             */

            /**
             * Options for the point markers of line-like series. Properties like
             * `fillColor`, `lineColor` and `lineWidth` define the visual appearance
             * of the markers. Other series types, like column series, don't have
             * markers, but have visual options on the series level instead.
             * 
             * In styled mode, the markers can be styled with the `.highcharts-point`, `.highcharts-point-hover` and `.highcharts-point-select`
             * class names.
             * 
             * @type {Object}
             * @extends plotOptions.series.marker
             * @excluding enabled,height,radius,width
             * @product highcharts
             */
            marker: {

                lineColor: null, // inherit from series.color
                lineWidth: 1,
                /**
                 * The fill opacity of the bubble markers.
                 * @type {Number}
                 * @default 0.5
                 * @product highcharts
                 */

                /**
                 * In bubble charts, the radius is overridden and determined based on 
                 * the point's data value.
                 */
                radius: null,
                states: {
                    hover: {
                        radiusPlus: 0
                    }
                },

                /**
                 * A predefined shape or symbol for the marker. Possible values are
                 * "circle", "square", "diamond", "triangle" and "triangle-down".
                 * 
                 * Additionally, the URL to a graphic can be given on the form
                 * `url(graphic.png)`. Note that for the image to be applied to exported
                 * charts, its URL needs to be accessible by the export server.
                 * 
                 * Custom callbacks for symbol path generation can also be added to
                 * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
                 * used by its method name, as shown in the demo.
                 * 
                 * @validvalue ["circle", "square", "diamond", "triangle", "triangle-down"]
                 * @type {String}
                 * @sample {highcharts} highcharts/plotoptions/bubble-symbol/
                 *         Bubble chart with various symbols
                 * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/
                 *         General chart with predefined, graphic and custom markers
                 * @default circle
                 * @since 5.0.11
                 * @product highcharts
                 */
                symbol: 'circle'
            },

            /**
             * Minimum bubble size. Bubbles will automatically size between the
             * `minSize` and `maxSize` to reflect the `z` value of each bubble.
             * Can be either pixels (when no unit is given), or a percentage of
             * the smallest one of the plot width and height.
             * 
             * @type {String}
             * @sample {highcharts} highcharts/plotoptions/bubble-size/ Bubble size
             * @default 8
             * @since 3.0
             * @product highcharts
             */
            minSize: 8,

            /**
             * Maximum bubble size. Bubbles will automatically size between the
             * `minSize` and `maxSize` to reflect the `z` value of each bubble.
             * Can be either pixels (when no unit is given), or a percentage of
             * the smallest one of the plot width and height.
             * 
             * @type {String}
             * @sample {highcharts} highcharts/plotoptions/bubble-size/ Bubble size
             * @default 20%
             * @since 3.0
             * @product highcharts
             */
            maxSize: '20%',

            /**
             * When a point's Z value is below the [zThreshold](#plotOptions.bubble.
             * zThreshold) setting, this color is used.
             * 
             * @type {Color}
             * @sample {highcharts} highcharts/plotoptions/bubble-negative/
             *         Negative bubbles
             * @default null
             * @since 3.0
             * @product highcharts
             * @apioption plotOptions.bubble.negativeColor
             */

            /**
             * Whether the bubble's value should be represented by the area or the
             * width of the bubble. The default, `area`, corresponds best to the
             * human perception of the size of each bubble.
             * 
             * @validvalue ["area", "width"]
             * @type {String}
             * @sample {highcharts} highcharts/plotoptions/bubble-sizeby/
             *         Comparison of area and size
             * @default area
             * @since 3.0.7
             * @product highcharts
             * @apioption plotOptions.bubble.sizeBy
             */

            /**
             * When this is true, the absolute value of z determines the size of
             * the bubble. This means that with the default `zThreshold` of 0, a
             * bubble of value -1 will have the same size as a bubble of value 1,
             * while a bubble of value 0 will have a smaller size according to
             * `minSize`.
             * 
             * @type {Boolean}
             * @sample {highcharts} highcharts/plotoptions/bubble-sizebyabsolutevalue/
             *         Size by absolute value, various thresholds
             * @default false
             * @since 4.1.9
             * @product highcharts
             * @apioption plotOptions.bubble.sizeByAbsoluteValue
             */

            /**
             * When this is true, the series will not cause the Y axis to cross
             * the zero plane (or [threshold](#plotOptions.series.threshold) option)
             * unless the data actually crosses the plane.
             * 
             * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
             * 3 will make the Y axis show negative values according to the `minPadding`
             * option. If `softThreshold` is `true`, the Y axis starts at 0.
             * 
             * @type {Boolean}
             * @default false
             * @since 4.1.9
             * @product highcharts
             */
            softThreshold: false,

            states: {
                hover: {
                    halo: {
                        size: 5
                    }
                }
            },

            tooltip: {
                pointFormat: '({point.x}, {point.y}), Size: {point.z}'
            },

            turboThreshold: 0,

            /**
             * When [displayNegative](#plotOptions.bubble.displayNegative) is `false`,
             * bubbles with lower Z values are skipped. When `displayNegative`
             * is `true` and a [negativeColor](#plotOptions.bubble.negativeColor)
             * is given, points with lower Z is colored.
             * 
             * @type {Number}
             * @sample {highcharts} highcharts/plotoptions/bubble-negative/
             *         Negative bubbles
             * @default 0
             * @since 3.0
             * @product highcharts
             */
            zThreshold: 0,

            zoneAxis: 'z'

            /**
             * The minimum for the Z value range. Defaults to the highest Z value
             * in the data.
             * 
             * @type {Number}
             * @see [zMax](#plotOptions.bubble.zMin)
             * @sample {highcharts} highcharts/plotoptions/bubble-zmin-zmax/
             *         Z has a possible range of 0-100
             * @default null
             * @since 4.0.3
             * @product highcharts
             * @apioption plotOptions.bubble.zMax
             */

            /**
             * The minimum for the Z value range. Defaults to the lowest Z value
             * in the data.
             * 
             * @type {Number}
             * @see [zMax](#plotOptions.bubble.zMax)
             * @sample {highcharts} highcharts/plotoptions/bubble-zmin-zmax/
             *         Z has a possible range of 0-100
             * @default null
             * @since 4.0.3
             * @product highcharts
             * @apioption plotOptions.bubble.zMin
             */

            // Prototype members
        }, {
            pointArrayMap: ['y', 'z'],
            parallelArrays: ['x', 'y', 'z'],
            trackerGroups: ['group', 'dataLabelsGroup'],
            specialGroup: 'group', // To allow clipping (#6296)
            bubblePadding: true,
            zoneAxis: 'z',
            directTouch: true,


            pointAttribs: function(point, state) {
                var markerOptions = this.options.marker,
                    fillOpacity = pick(markerOptions.fillOpacity, 0.5),
                    attr = Series.prototype.pointAttribs.call(this, point, state);

                if (fillOpacity !== 1) {
                    attr.fill = color(attr.fill).setOpacity(fillOpacity).get('rgba');
                }

                return attr;
            },


            /**
             * Get the radius for each point based on the minSize, maxSize and each point's Z value. This
             * must be done prior to Series.translate because the axis needs to add padding in
             * accordance with the point sizes.
             */
            getRadii: function(zMin, zMax, minSize, maxSize) {
                var len,
                    i,
                    pos,
                    zData = this.zData,
                    radii = [],
                    options = this.options,
                    sizeByArea = options.sizeBy !== 'width',
                    zThreshold = options.zThreshold,
                    zRange = zMax - zMin,
                    value,
                    radius;

                // Set the shape type and arguments to be picked up in drawPoints
                for (i = 0, len = zData.length; i < len; i++) {

                    value = zData[i];

                    // When sizing by threshold, the absolute value of z determines the size
                    // of the bubble.
                    if (options.sizeByAbsoluteValue && value !== null) {
                        value = Math.abs(value - zThreshold);
                        zMax = Math.max(zMax - zThreshold, Math.abs(zMin - zThreshold));
                        zMin = 0;
                    }

                    if (value === null) {
                        radius = null;
                        // Issue #4419 - if value is less than zMin, push a radius that's always smaller than the minimum size
                    } else if (value < zMin) {
                        radius = minSize / 2 - 1;
                    } else {
                        // Relative size, a number between 0 and 1
                        pos = zRange > 0 ? (value - zMin) / zRange : 0.5;

                        if (sizeByArea && pos >= 0) {
                            pos = Math.sqrt(pos);
                        }
                        radius = Math.ceil(minSize + pos * (maxSize - minSize)) / 2;
                    }
                    radii.push(radius);
                }
                this.radii = radii;
            },

            /**
             * Perform animation on the bubbles
             */
            animate: function(init) {
                var animation = this.options.animation;

                if (!init) { // run the animation
                    each(this.points, function(point) {
                        var graphic = point.graphic,
                            animationTarget;

                        if (graphic && graphic.width) { // URL symbols don't have width
                            animationTarget = {
                                x: graphic.x,
                                y: graphic.y,
                                width: graphic.width,
                                height: graphic.height
                            };

                            // Start values
                            graphic.attr({
                                x: point.plotX,
                                y: point.plotY,
                                width: 1,
                                height: 1
                            });

                            // Run animation
                            graphic.animate(animationTarget, animation);
                        }
                    });

                    // delete this function to allow it only once
                    this.animate = null;
                }
            },

            /**
             * Extend the base translate method to handle bubble size
             */
            translate: function() {

                var i,
                    data = this.data,
                    point,
                    radius,
                    radii = this.radii;

                // Run the parent method
                seriesTypes.scatter.prototype.translate.call(this);

                // Set the shape type and arguments to be picked up in drawPoints
                i = data.length;

                while (i--) {
                    point = data[i];
                    radius = radii ? radii[i] : 0; // #1737

                    if (isNumber(radius) && radius >= this.minPxSize / 2) {
                        // Shape arguments
                        point.marker = H.extend(point.marker, {
                            radius: radius,
                            width: 2 * radius,
                            height: 2 * radius
                        });

                        // Alignment box for the data label
                        point.dlBox = {
                            x: point.plotX - radius,
                            y: point.plotY - radius,
                            width: 2 * radius,
                            height: 2 * radius
                        };
                    } else { // below zThreshold
                        point.shapeArgs = point.plotY = point.dlBox = undefined; // #1691
                    }
                }
            },

            alignDataLabel: seriesTypes.column.prototype.alignDataLabel,
            buildKDTree: noop,
            applyZones: noop

            // Point class
        }, {
            haloPath: function(size) {
                return Point.prototype.haloPath.call(
                    this,
                    size === 0 ? 0 : (this.marker ? this.marker.radius || 0 : 0) + size // #6067
                );
            },
            ttBelow: false
        });

        /**
         * Add logic to pad each axis with the amount of pixels
         * necessary to avoid the bubbles to overflow.
         */
        Axis.prototype.beforePadding = function() {
            var axis = this,
                axisLength = this.len,
                chart = this.chart,
                pxMin = 0,
                pxMax = axisLength,
                isXAxis = this.isXAxis,
                dataKey = isXAxis ? 'xData' : 'yData',
                min = this.min,
                extremes = {},
                smallestSize = Math.min(chart.plotWidth, chart.plotHeight),
                zMin = Number.MAX_VALUE,
                zMax = -Number.MAX_VALUE,
                range = this.max - min,
                transA = axisLength / range,
                activeSeries = [];

            // Handle padding on the second pass, or on redraw
            each(this.series, function(series) {

                var seriesOptions = series.options,
                    zData;

                if (series.bubblePadding && (series.visible || !chart.options.chart.ignoreHiddenSeries)) {

                    // Correction for #1673
                    axis.allowZoomOutside = true;

                    // Cache it
                    activeSeries.push(series);

                    if (isXAxis) { // because X axis is evaluated first

                        // For each series, translate the size extremes to pixel values
                        each(['minSize', 'maxSize'], function(prop) {
                            var length = seriesOptions[prop],
                                isPercent = /%$/.test(length);

                            length = pInt(length);
                            extremes[prop] = isPercent ?
                                smallestSize * length / 100 :
                                length;

                        });
                        series.minPxSize = extremes.minSize;
                        // Prioritize min size if conflict to make sure bubbles are
                        // always visible. #5873
                        series.maxPxSize = Math.max(extremes.maxSize, extremes.minSize);

                        // Find the min and max Z
                        zData = series.zData;
                        if (zData.length) { // #1735
                            zMin = pick(seriesOptions.zMin, Math.min(
                                zMin,
                                Math.max(
                                    arrayMin(zData),
                                    seriesOptions.displayNegative === false ? seriesOptions.zThreshold : -Number.MAX_VALUE
                                )
                            ));
                            zMax = pick(seriesOptions.zMax, Math.max(zMax, arrayMax(zData)));
                        }
                    }
                }
            });

            each(activeSeries, function(series) {

                var data = series[dataKey],
                    i = data.length,
                    radius;

                if (isXAxis) {
                    series.getRadii(zMin, zMax, series.minPxSize, series.maxPxSize);
                }

                if (range > 0) {
                    while (i--) {
                        if (isNumber(data[i]) && axis.dataMin <= data[i] && data[i] <= axis.dataMax) {
                            radius = series.radii[i];
                            pxMin = Math.min(((data[i] - min) * transA) - radius, pxMin);
                            pxMax = Math.max(((data[i] - min) * transA) + radius, pxMax);
                        }
                    }
                }
            });

            if (activeSeries.length && range > 0 && !this.isLog) {
                pxMax -= axisLength;
                transA *= (axisLength + pxMin - pxMax) / axisLength;
                each([
                    ['min', 'userMin', pxMin],
                    ['max', 'userMax', pxMax]
                ], function(keys) {
                    if (pick(axis.options[keys[0]], axis[keys[1]]) === undefined) {
                        axis[keys[0]] += keys[2] / transA;
                    }
                });
            }
        };


        /**
         * A `bubble` series. If the [type](#series.bubble.type) option is
         * not specified, it is inherited from [chart.type](#chart.type).
         * 
         * For options that apply to multiple series, it is recommended to add
         * them to the [plotOptions.series](#plotOptions.series) options structure.
         * To apply to all series of this specific type, apply it to [plotOptions.
         * bubble](#plotOptions.bubble).
         * 
         * @type {Object}
         * @extends series,plotOptions.bubble
         * @excluding dataParser,dataURL,stack
         * @product highcharts
         * @apioption series.bubble
         */

        /**
         * An array of data points for the series. For the `bubble` series type,
         * points can be given in the following ways:
         * 
         * 1.  An array of arrays with 3 or 2 values. In this case, the values
         * correspond to `x,y,z`. If the first value is a string, it is applied
         * as the name of the point, and the `x` value is inferred. The `x`
         * value can also be omitted, in which case the inner arrays should
         * be of length 2\. Then the `x` value is automatically calculated,
         * either starting at 0 and incremented by 1, or from `pointStart` and
         * `pointInterval` given in the series options.
         * 
         *  ```js
         *     data: [
         *         [0, 1, 2],
         *         [1, 5, 5],
         *         [2, 0, 2]
         *     ]
         *  ```
         * 
         * 2.  An array of objects with named values. The objects are point
         * configuration objects as seen below. If the total number of data
         * points exceeds the series' [turboThreshold](#series.bubble.turboThreshold),
         * this option is not available.
         * 
         *  ```js
         *     data: [{
         *         x: 1,
         *         y: 1,
         *         z: 1,
         *         name: "Point2",
         *         color: "#00FF00"
         *     }, {
         *         x: 1,
         *         y: 5,
         *         z: 4,
         *         name: "Point1",
         *         color: "#FF00FF"
         *     }]
         *  ```
         * 
         * @type {Array<Object|Array>}
         * @extends series.line.data
         * @excluding marker
         * @sample {highcharts} highcharts/chart/reflow-true/ Numerical values
         * @sample {highcharts} highcharts/series/data-array-of-arrays/ Arrays of numeric x and y
         * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ Arrays of datetime x and y
         * @sample {highcharts} highcharts/series/data-array-of-name-value/ Arrays of point.name and y
         * @sample {highcharts} highcharts/series/data-array-of-objects/ Config objects
         * @product highcharts
         * @apioption series.bubble.data
         */

        /**
         * The size value for each bubble. The bubbles' diameters are computed
         * based on the `z`, and controlled by series options like `minSize`,
         *  `maxSize`, `sizeBy`, `zMin` and `zMax`.
         * 
         * @type {Number}
         * @product highcharts
         * @apioption series.bubble.data.z
         */

    }(Highcharts));
    (function(H) {
        /**
         * (c) 2010-2017 Torstein Honsi
         *
         * License: www.highcharts.com/license
         */

        /**
         * Extensions for polar charts. Additionally, much of the geometry required for polar charts is
         * gathered in RadialAxes.js.
         *
         */

        var each = H.each,
            pick = H.pick,
            Pointer = H.Pointer,
            Series = H.Series,
            seriesTypes = H.seriesTypes,
            wrap = H.wrap,

            seriesProto = Series.prototype,
            pointerProto = Pointer.prototype,
            colProto;

        /**
         * Search a k-d tree by the point angle, used for shared tooltips in polar charts
         */
        seriesProto.searchPointByAngle = function(e) {
            var series = this,
                chart = series.chart,
                xAxis = series.xAxis,
                center = xAxis.pane.center,
                plotX = e.chartX - center[0] - chart.plotLeft,
                plotY = e.chartY - center[1] - chart.plotTop;

            return this.searchKDTree({
                clientX: 180 + (Math.atan2(plotX, plotY) * (-180 / Math.PI))
            });

        };

        /**
         * #6212 Calculate connectors for spline series in polar chart. 
         * @param {Boolean} calculateNeighbours - Check if connectors should be calculated for neighbour points as well
         * allows short recurence
         */
        seriesProto.getConnectors = function(segment, index, calculateNeighbours, connectEnds) {

            var i,
                prevPointInd,
                nextPointInd,
                previousPoint,
                nextPoint,
                previousX,
                previousY,
                nextX,
                nextY,
                plotX,
                plotY,
                ret,
                smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc;
                denom = smoothing + 1,
                leftContX,
                leftContY,
                rightContX,
                rightContY,
                dLControlPoint, // distance left control point
                dRControlPoint,
                leftContAngle,
                rightContAngle,
                jointAngle,
                addedNumber = connectEnds ? 1 : 0;

            /** calculate final index of points depending on the initial index value.
             * Because of calculating neighbours, index may be outisde segment array.
             */
            if (index >= 0 && index <= segment.length - 1) {
                i = index;
            } else if (index < 0) {
                i = segment.length - 1 + index;
            } else {
                i = 0;
            }

            prevPointInd = (i - 1 < 0) ? segment.length - (1 + addedNumber) : i - 1;
            nextPointInd = (i + 1 > segment.length - 1) ? addedNumber : i + 1;
            previousPoint = segment[prevPointInd];
            nextPoint = segment[nextPointInd];
            previousX = previousPoint.plotX;
            previousY = previousPoint.plotY;
            nextX = nextPoint.plotX;
            nextY = nextPoint.plotY;
            plotX = segment[i].plotX; // actual point
            plotY = segment[i].plotY;
            leftContX = (smoothing * plotX + previousX) / denom;
            leftContY = (smoothing * plotY + previousY) / denom;
            rightContX = (smoothing * plotX + nextX) / denom;
            rightContY = (smoothing * plotY + nextY) / denom;
            dLControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2));
            dRControlPoint = Math.sqrt(Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2));
            leftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX);
            rightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX);
            jointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2);
            // Ensure the right direction, jointAngle should be in the same quadrant as leftContAngle
            if (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) {
                jointAngle -= Math.PI;
            }
            // Find the corrected control points for a spline straight through the point
            leftContX = plotX + Math.cos(jointAngle) * dLControlPoint;
            leftContY = plotY + Math.sin(jointAngle) * dLControlPoint;
            rightContX = plotX + Math.cos(Math.PI + jointAngle) * dRControlPoint;
            rightContY = plotY + Math.sin(Math.PI + jointAngle) * dRControlPoint;

            // push current point's connectors into returned object

            ret = {
                rightContX: rightContX,
                rightContY: rightContY,
                leftContX: leftContX,
                leftContY: leftContY,
                plotX: plotX,
                plotY: plotY
            };

            // calculate connectors for previous and next point and push them inside returned object 
            if (calculateNeighbours) {
                ret.prevPointCont = this.getConnectors(segment, prevPointInd, false, connectEnds);
            }
            return ret;
        };

        /**
         * Wrap the buildKDTree function so that it searches by angle (clientX) in case of shared tooltip,
         * and by two dimensional distance in case of non-shared.
         */
        wrap(seriesProto, 'buildKDTree', function(proceed) {
            if (this.chart.polar) {
                if (this.kdByAngle) {
                    this.searchPoint = this.searchPointByAngle;
                } else {
                    this.options.findNearestPointBy = 'xy';
                }
            }
            proceed.apply(this);
        });

        /**
         * Translate a point's plotX and plotY from the internal angle and radius measures to
         * true plotX, plotY coordinates
         */
        seriesProto.toXY = function(point) {
            var xy,
                chart = this.chart,
                plotX = point.plotX,
                plotY = point.plotY,
                clientX;

            // Save rectangular plotX, plotY for later computation
            point.rectPlotX = plotX;
            point.rectPlotY = plotY;

            // Find the polar plotX and plotY
            xy = this.xAxis.postTranslate(point.plotX, this.yAxis.len - plotY);
            point.plotX = point.polarPlotX = xy.x - chart.plotLeft;
            point.plotY = point.polarPlotY = xy.y - chart.plotTop;

            // If shared tooltip, record the angle in degrees in order to align X points. Otherwise,
            // use a standard k-d tree to get the nearest point in two dimensions.
            if (this.kdByAngle) {
                clientX = ((plotX / Math.PI * 180) + this.xAxis.pane.options.startAngle) % 360;
                if (clientX < 0) { // #2665
                    clientX += 360;
                }
                point.clientX = clientX;
            } else {
                point.clientX = point.plotX;
            }
        };

        if (seriesTypes.spline) {
            /**
             * Overridden method for calculating a spline from one point to the next
             */
            wrap(seriesTypes.spline.prototype, 'getPointSpline', function(proceed, segment, point, i) {
                var ret,
                    connectors;

                if (this.chart.polar) {
                    // moveTo or lineTo
                    if (!i) {
                        ret = ['M', point.plotX, point.plotY];
                    } else { // curve from last point to this
                        connectors = this.getConnectors(segment, i, true, this.connectEnds);
                        ret = [
                            'C',
                            connectors.prevPointCont.rightContX,
                            connectors.prevPointCont.rightContY,
                            connectors.leftContX,
                            connectors.leftContY,
                            connectors.plotX,
                            connectors.plotY
                        ];
                    }
                } else {
                    ret = proceed.call(this, segment, point, i);
                }
                return ret;
            });

            // #6430 Areasplinerange series use unwrapped getPointSpline method, so we need to set this method again.
            if (seriesTypes.areasplinerange) {
                seriesTypes.areasplinerange.prototype.getPointSpline = seriesTypes.spline.prototype.getPointSpline;
            }
        }

        /**
         * Extend translate. The plotX and plotY values are computed as if the polar chart were a
         * cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from
         * center.
         */
        wrap(seriesProto, 'translate', function(proceed) {
            var chart = this.chart,
                points,
                i;

            // Run uber method
            proceed.call(this);

            // Postprocess plot coordinates
            if (chart.polar) {
                this.kdByAngle = chart.tooltip && chart.tooltip.shared;

                if (!this.preventPostTranslate) {
                    points = this.points;
                    i = points.length;

                    while (i--) {
                        // Translate plotX, plotY from angle and radius to true plot coordinates
                        this.toXY(points[i]);
                    }
                }
            }
        });

        /**
         * Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in
         * line-like series.
         */
        wrap(seriesProto, 'getGraphPath', function(proceed, points) {
            var series = this,
                i,
                firstValid,
                popLastPoint;

            // Connect the path
            if (this.chart.polar) {
                points = points || this.points;

                // Append first valid point in order to connect the ends
                for (i = 0; i < points.length; i++) {
                    if (!points[i].isNull) {
                        firstValid = i;
                        break;
                    }
                }


                /**
                 * Polar charts only. Whether to connect the ends of a line series plot
                 * across the extremes.
                 * 
                 * @type {Boolean}
                 * @sample {highcharts} highcharts/plotoptions/line-connectends-false/
                 *         Do not connect
                 * @since 2.3.0
                 * @product highcharts
                 * @apioption plotOptions.series.connectEnds
                 */
                if (this.options.connectEnds !== false && firstValid !== undefined) {
                    this.connectEnds = true; // re-used in splines
                    points.splice(points.length, 0, points[firstValid]);
                    popLastPoint = true;
                }

                // For area charts, pseudo points are added to the graph, now we need to translate these
                each(points, function(point) {
                    if (point.polarPlotY === undefined) {
                        series.toXY(point);
                    }
                });
            }

            // Run uber method
            var ret = proceed.apply(this, [].slice.call(arguments, 1));

            /** #6212 points.splice method is adding points to an array. In case of areaspline getGraphPath method is used two times
             * and in both times points are added to an array. That is why points.pop is used, to get unmodified points.
             */
            if (popLastPoint) {
                points.pop();
            }
            return ret;
        });


        function polarAnimate(proceed, init) {
            var chart = this.chart,
                animation = this.options.animation,
                group = this.group,
                markerGroup = this.markerGroup,
                center = this.xAxis.center,
                plotLeft = chart.plotLeft,
                plotTop = chart.plotTop,
                attribs;

            // Specific animation for polar charts
            if (chart.polar) {

                // Enable animation on polar charts only in SVG. In VML, the scaling is different, plus animation
                // would be so slow it would't matter.
                if (chart.renderer.isSVG) {

                    if (animation === true) {
                        animation = {};
                    }

                    // Initialize the animation
                    if (init) {

                        // Scale down the group and place it in the center
                        attribs = {
                            translateX: center[0] + plotLeft,
                            translateY: center[1] + plotTop,
                            scaleX: 0.001, // #1499
                            scaleY: 0.001
                        };

                        group.attr(attribs);
                        if (markerGroup) {
                            markerGroup.attr(attribs);
                        }

                        // Run the animation
                    } else {
                        attribs = {
                            translateX: plotLeft,
                            translateY: plotTop,
                            scaleX: 1,
                            scaleY: 1
                        };
                        group.animate(attribs, animation);
                        if (markerGroup) {
                            markerGroup.animate(attribs, animation);
                        }

                        // Delete this function to allow it only once
                        this.animate = null;
                    }
                }

                // For non-polar charts, revert to the basic animation
            } else {
                proceed.call(this, init);
            }
        }

        // Define the animate method for regular series
        wrap(seriesProto, 'animate', polarAnimate);


        if (seriesTypes.column) {

            colProto = seriesTypes.column.prototype;

            colProto.polarArc = function(low, high, start, end) {
                var center = this.xAxis.center,
                    len = this.yAxis.len;

                return this.chart.renderer.symbols.arc(
                    center[0],
                    center[1],
                    len - high,
                    null, {
                        start: start,
                        end: end,
                        innerR: len - pick(low, len)
                    }
                );
            };

            /**
             * Define the animate method for columnseries
             */
            wrap(colProto, 'animate', polarAnimate);


            /**
             * Extend the column prototype's translate method
             */
            wrap(colProto, 'translate', function(proceed) {

                var xAxis = this.xAxis,
                    startAngleRad = xAxis.startAngleRad,
                    start,
                    points,
                    point,
                    i;

                this.preventPostTranslate = true;

                // Run uber method
                proceed.call(this);

                // Postprocess plot coordinates
                if (xAxis.isRadial) {
                    points = this.points;
                    i = points.length;
                    while (i--) {
                        point = points[i];
                        start = point.barX + startAngleRad;
                        point.shapeType = 'path';
                        point.shapeArgs = {
                            d: this.polarArc(point.yBottom, point.plotY, start, start + point.pointWidth)
                        };
                        // Provide correct plotX, plotY for tooltip
                        this.toXY(point);
                        point.tooltipPos = [point.plotX, point.plotY];
                        point.ttBelow = point.plotY > xAxis.center[1];
                    }
                }
            });


            /**
             * Align column data labels outside the columns. #1199.
             */
            wrap(colProto, 'alignDataLabel', function(proceed, point, dataLabel, options, alignTo, isNew) {

                if (this.chart.polar) {
                    var angle = point.rectPlotX / Math.PI * 180,
                        align,
                        verticalAlign;

                    // Align nicely outside the perimeter of the columns
                    if (options.align === null) {
                        if (angle > 20 && angle < 160) {
                            align = 'left'; // right hemisphere
                        } else if (angle > 200 && angle < 340) {
                            align = 'right'; // left hemisphere
                        } else {
                            align = 'center'; // top or bottom
                        }
                        options.align = align;
                    }
                    if (options.verticalAlign === null) {
                        if (angle < 45 || angle > 315) {
                            verticalAlign = 'bottom'; // top part
                        } else if (angle > 135 && angle < 225) {
                            verticalAlign = 'top'; // bottom part
                        } else {
                            verticalAlign = 'middle'; // left or right
                        }
                        options.verticalAlign = verticalAlign;
                    }

                    seriesProto.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
                } else {
                    proceed.call(this, point, dataLabel, options, alignTo, isNew);
                }

            });
        }

        /**
         * Extend getCoordinates to prepare for polar axis values
         */
        wrap(pointerProto, 'getCoordinates', function(proceed, e) {
            var chart = this.chart,
                ret = {
                    xAxis: [],
                    yAxis: []
                };

            if (chart.polar) {

                each(chart.axes, function(axis) {
                    var isXAxis = axis.isXAxis,
                        center = axis.center,
                        x = e.chartX - center[0] - chart.plotLeft,
                        y = e.chartY - center[1] - chart.plotTop;

                    ret[isXAxis ? 'xAxis' : 'yAxis'].push({
                        axis: axis,
                        value: axis.translate(
                            isXAxis ?
                            Math.PI - Math.atan2(x, y) : // angle
                            Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center
                            true
                        )
                    });
                });

            } else {
                ret = proceed.call(this, e);
            }

            return ret;
        });

        wrap(H.Chart.prototype, 'getAxes', function(proceed) {

            if (!this.pane) {
                this.pane = [];
            }
            each(H.splat(this.options.pane), function(paneOptions) {
                new H.Pane( // eslint-disable-line no-new
                    paneOptions,
                    this
                );
            }, this);

            proceed.call(this);
        });

        wrap(H.Chart.prototype, 'drawChartBox', function(proceed) {
            proceed.call(this);

            each(this.pane, function(pane) {
                pane.render();
            });
        });

        /**
         * Extend chart.get to also search in panes. Used internally in responsiveness
         * and chart.update.
         */
        wrap(H.Chart.prototype, 'get', function(proceed, id) {
            return H.find(this.pane, function(pane) {
                return pane.options.id === id;
            }) || proceed.call(this, id);
        });

    }(Highcharts));
}));
//     Underscore.js 1.8.3
//     http://underscorejs.org
//     (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
//     Underscore may be freely distributed under the MIT license.

(function() {

  // Baseline setup
  // --------------

  // Establish the root object, `window` in the browser, or `exports` on the server.
  var root = this;

  // Save the previous value of the `_` variable.
  var previousUnderscore = root._;

  // Save bytes in the minified (but not gzipped) version:
  var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype;

  // Create quick reference variables for speed access to core prototypes.
  var
    push             = ArrayProto.push,
    slice            = ArrayProto.slice,
    toString         = ObjProto.toString,
    hasOwnProperty   = ObjProto.hasOwnProperty;

  // All **ECMAScript 5** native function implementations that we hope to use
  // are declared here.
  var
    nativeIsArray      = Array.isArray,
    nativeKeys         = Object.keys,
    nativeBind         = FuncProto.bind,
    nativeCreate       = Object.create;

  // Naked function reference for surrogate-prototype-swapping.
  var Ctor = function(){};

  // Create a safe reference to the Underscore object for use below.
  var _ = function(obj) {
    if (obj instanceof _) return obj;
    if (!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  };

  // Export the Underscore object for **Node.js**, with
  // backwards-compatibility for the old `require()` API. If we're in
  // the browser, add `_` as a global object.
  if (typeof exports !== 'undefined') {
    if (typeof module !== 'undefined' && module.exports) {
      exports = module.exports = _;
    }
    exports._ = _;
  } else {
    root._ = _;
  }

  // Current version.
  _.VERSION = '1.8.3';

  // Internal function that returns an efficient (for current engines) version
  // of the passed-in callback, to be repeatedly applied in other Underscore
  // functions.
  var optimizeCb = function(func, context, argCount) {
    if (context === void 0) return func;
    switch (argCount == null ? 3 : argCount) {
      case 1: return function(value) {
        return func.call(context, value);
      };
      case 2: return function(value, other) {
        return func.call(context, value, other);
      };
      case 3: return function(value, index, collection) {
        return func.call(context, value, index, collection);
      };
      case 4: return function(accumulator, value, index, collection) {
        return func.call(context, accumulator, value, index, collection);
      };
    }
    return function() {
      return func.apply(context, arguments);
    };
  };

  // A mostly-internal function to generate callbacks that can be applied
  // to each element in a collection, returning the desired result — either
  // identity, an arbitrary callback, a property matcher, or a property accessor.
  var cb = function(value, context, argCount) {
    if (value == null) return _.identity;
    if (_.isFunction(value)) return optimizeCb(value, context, argCount);
    if (_.isObject(value)) return _.matcher(value);
    return _.property(value);
  };
  _.iteratee = function(value, context) {
    return cb(value, context, Infinity);
  };

  // An internal function for creating assigner functions.
  var createAssigner = function(keysFunc, undefinedOnly) {
    return function(obj) {
      var length = arguments.length;
      if (length < 2 || obj == null) return obj;
      for (var index = 1; index < length; index++) {
        var source = arguments[index],
            keys = keysFunc(source),
            l = keys.length;
        for (var i = 0; i < l; i++) {
          var key = keys[i];
          if (!undefinedOnly || obj[key] === void 0) obj[key] = source[key];
        }
      }
      return obj;
    };
  };

  // An internal function for creating a new object that inherits from another.
  var baseCreate = function(prototype) {
    if (!_.isObject(prototype)) return {};
    if (nativeCreate) return nativeCreate(prototype);
    Ctor.prototype = prototype;
    var result = new Ctor;
    Ctor.prototype = null;
    return result;
  };

  var property = function(key) {
    return function(obj) {
      return obj == null ? void 0 : obj[key];
    };
  };

  // Helper for collection methods to determine whether a collection
  // should be iterated as an array or as an object
  // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
  // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
  var getLength = property('length');
  var isArrayLike = function(collection) {
    var length = getLength(collection);
    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
  };

  // Collection Functions
  // --------------------

  // The cornerstone, an `each` implementation, aka `forEach`.
  // Handles raw objects in addition to array-likes. Treats all
  // sparse array-likes as if they were dense.
  _.each = _.forEach = function(obj, iteratee, context) {
    iteratee = optimizeCb(iteratee, context);
    var i, length;
    if (isArrayLike(obj)) {
      for (i = 0, length = obj.length; i < length; i++) {
        iteratee(obj[i], i, obj);
      }
    } else {
      var keys = _.keys(obj);
      for (i = 0, length = keys.length; i < length; i++) {
        iteratee(obj[keys[i]], keys[i], obj);
      }
    }
    return obj;
  };

  // Return the results of applying the iteratee to each element.
  _.map = _.collect = function(obj, iteratee, context) {
    iteratee = cb(iteratee, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length,
        results = Array(length);
    for (var index = 0; index < length; index++) {
      var currentKey = keys ? keys[index] : index;
      results[index] = iteratee(obj[currentKey], currentKey, obj);
    }
    return results;
  };

  // Create a reducing function iterating left or right.
  function createReduce(dir) {
    // Optimized iterator function as using arguments.length
    // in the main function will deoptimize the, see #1991.
    function iterator(obj, iteratee, memo, keys, index, length) {
      for (; index >= 0 && index < length; index += dir) {
        var currentKey = keys ? keys[index] : index;
        memo = iteratee(memo, obj[currentKey], currentKey, obj);
      }
      return memo;
    }

    return function(obj, iteratee, memo, context) {
      iteratee = optimizeCb(iteratee, context, 4);
      var keys = !isArrayLike(obj) && _.keys(obj),
          length = (keys || obj).length,
          index = dir > 0 ? 0 : length - 1;
      // Determine the initial value if none is provided.
      if (arguments.length < 3) {
        memo = obj[keys ? keys[index] : index];
        index += dir;
      }
      return iterator(obj, iteratee, memo, keys, index, length);
    };
  }

  // **Reduce** builds up a single result from a list of values, aka `inject`,
  // or `foldl`.
  _.reduce = _.foldl = _.inject = createReduce(1);

  // The right-associative version of reduce, also known as `foldr`.
  _.reduceRight = _.foldr = createReduce(-1);

  // Return the first value which passes a truth test. Aliased as `detect`.
  _.find = _.detect = function(obj, predicate, context) {
    var key;
    if (isArrayLike(obj)) {
      key = _.findIndex(obj, predicate, context);
    } else {
      key = _.findKey(obj, predicate, context);
    }
    if (key !== void 0 && key !== -1) return obj[key];
  };

  // Return all the elements that pass a truth test.
  // Aliased as `select`.
  _.filter = _.select = function(obj, predicate, context) {
    var results = [];
    predicate = cb(predicate, context);
    _.each(obj, function(value, index, list) {
      if (predicate(value, index, list)) results.push(value);
    });
    return results;
  };

  // Return all the elements for which a truth test fails.
  _.reject = function(obj, predicate, context) {
    return _.filter(obj, _.negate(cb(predicate)), context);
  };

  // Determine whether all of the elements match a truth test.
  // Aliased as `all`.
  _.every = _.all = function(obj, predicate, context) {
    predicate = cb(predicate, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length;
    for (var index = 0; index < length; index++) {
      var currentKey = keys ? keys[index] : index;
      if (!predicate(obj[currentKey], currentKey, obj)) return false;
    }
    return true;
  };

  // Determine if at least one element in the object matches a truth test.
  // Aliased as `any`.
  _.some = _.any = function(obj, predicate, context) {
    predicate = cb(predicate, context);
    var keys = !isArrayLike(obj) && _.keys(obj),
        length = (keys || obj).length;
    for (var index = 0; index < length; index++) {
      var currentKey = keys ? keys[index] : index;
      if (predicate(obj[currentKey], currentKey, obj)) return true;
    }
    return false;
  };

  // Determine if the array or object contains a given item (using `===`).
  // Aliased as `includes` and `include`.
  _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
    if (!isArrayLike(obj)) obj = _.values(obj);
    if (typeof fromIndex != 'number' || guard) fromIndex = 0;
    return _.indexOf(obj, item, fromIndex) >= 0;
  };

  // Invoke a method (with arguments) on every item in a collection.
  _.invoke = function(obj, method) {
    var args = slice.call(arguments, 2);
    var isFunc = _.isFunction(method);
    return _.map(obj, function(value) {
      var func = isFunc ? method : value[method];
      return func == null ? func : func.apply(value, args);
    });
  };

  // Convenience version of a common use case of `map`: fetching a property.
  _.pluck = function(obj, key) {
    return _.map(obj, _.property(key));
  };

  // Convenience version of a common use case of `filter`: selecting only objects
  // containing specific `key:value` pairs.
  _.where = function(obj, attrs) {
    return _.filter(obj, _.matcher(attrs));
  };

  // Convenience version of a common use case of `find`: getting the first object
  // containing specific `key:value` pairs.
  _.findWhere = function(obj, attrs) {
    return _.find(obj, _.matcher(attrs));
  };

  // Return the maximum element (or element-based computation).
  _.max = function(obj, iteratee, context) {
    var result = -Infinity, lastComputed = -Infinity,
        value, computed;
    if (iteratee == null && obj != null) {
      obj = isArrayLike(obj) ? obj : _.values(obj);
      for (var i = 0, length = obj.length; i < length; i++) {
        value = obj[i];
        if (value > result) {
          result = value;
        }
      }
    } else {
      iteratee = cb(iteratee, context);
      _.each(obj, function(value, index, list) {
        computed = iteratee(value, index, list);
        if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
          result = value;
          lastComputed = computed;
        }
      });
    }
    return result;
  };

  // Return the minimum element (or element-based computation).
  _.min = function(obj, iteratee, context) {
    var result = Infinity, lastComputed = Infinity,
        value, computed;
    if (iteratee == null && obj != null) {
      obj = isArrayLike(obj) ? obj : _.values(obj);
      for (var i = 0, length = obj.length; i < length; i++) {
        value = obj[i];
        if (value < result) {
          result = value;
        }
      }
    } else {
      iteratee = cb(iteratee, context);
      _.each(obj, function(value, index, list) {
        computed = iteratee(value, index, list);
        if (computed < lastComputed || computed === Infinity && result === Infinity) {
          result = value;
          lastComputed = computed;
        }
      });
    }
    return result;
  };

  // Shuffle a collection, using the modern version of the
  // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
  _.shuffle = function(obj) {
    var set = isArrayLike(obj) ? obj : _.values(obj);
    var length = set.length;
    var shuffled = Array(length);
    for (var index = 0, rand; index < length; index++) {
      rand = _.random(0, index);
      if (rand !== index) shuffled[index] = shuffled[rand];
      shuffled[rand] = set[index];
    }
    return shuffled;
  };

  // Sample **n** random values from a collection.
  // If **n** is not specified, returns a single random element.
  // The internal `guard` argument allows it to work with `map`.
  _.sample = function(obj, n, guard) {
    if (n == null || guard) {
      if (!isArrayLike(obj)) obj = _.values(obj);
      return obj[_.random(obj.length - 1)];
    }
    return _.shuffle(obj).slice(0, Math.max(0, n));
  };

  // Sort the object's values by a criterion produced by an iteratee.
  _.sortBy = function(obj, iteratee, context) {
    iteratee = cb(iteratee, context);
    return _.pluck(_.map(obj, function(value, index, list) {
      return {
        value: value,
        index: index,
        criteria: iteratee(value, index, list)
      };
    }).sort(function(left, right) {
      var a = left.criteria;
      var b = right.criteria;
      if (a !== b) {
        if (a > b || a === void 0) return 1;
        if (a < b || b === void 0) return -1;
      }
      return left.index - right.index;
    }), 'value');
  };

  // An internal function used for aggregate "group by" operations.
  var group = function(behavior) {
    return function(obj, iteratee, context) {
      var result = {};
      iteratee = cb(iteratee, context);
      _.each(obj, function(value, index) {
        var key = iteratee(value, index, obj);
        behavior(result, value, key);
      });
      return result;
    };
  };

  // Groups the object's values by a criterion. Pass either a string attribute
  // to group by, or a function that returns the criterion.
  _.groupBy = group(function(result, value, key) {
    if (_.has(result, key)) result[key].push(value); else result[key] = [value];
  });

  // Indexes the object's values by a criterion, similar to `groupBy`, but for
  // when you know that your index values will be unique.
  _.indexBy = group(function(result, value, key) {
    result[key] = value;
  });

  // Counts instances of an object that group by a certain criterion. Pass
  // either a string attribute to count by, or a function that returns the
  // criterion.
  _.countBy = group(function(result, value, key) {
    if (_.has(result, key)) result[key]++; else result[key] = 1;
  });

  // Safely create a real, live array from anything iterable.
  _.toArray = function(obj) {
    if (!obj) return [];
    if (_.isArray(obj)) return slice.call(obj);
    if (isArrayLike(obj)) return _.map(obj, _.identity);
    return _.values(obj);
  };

  // Return the number of elements in an object.
  _.size = function(obj) {
    if (obj == null) return 0;
    return isArrayLike(obj) ? obj.length : _.keys(obj).length;
  };

  // Split a collection into two arrays: one whose elements all satisfy the given
  // predicate, and one whose elements all do not satisfy the predicate.
  _.partition = function(obj, predicate, context) {
    predicate = cb(predicate, context);
    var pass = [], fail = [];
    _.each(obj, function(value, key, obj) {
      (predicate(value, key, obj) ? pass : fail).push(value);
    });
    return [pass, fail];
  };

  // Array Functions
  // ---------------

  // Get the first element of an array. Passing **n** will return the first N
  // values in the array. Aliased as `head` and `take`. The **guard** check
  // allows it to work with `_.map`.
  _.first = _.head = _.take = function(array, n, guard) {
    if (array == null) return void 0;
    if (n == null || guard) return array[0];
    return _.initial(array, array.length - n);
  };

  // Returns everything but the last entry of the array. Especially useful on
  // the arguments object. Passing **n** will return all the values in
  // the array, excluding the last N.
  _.initial = function(array, n, guard) {
    return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
  };

  // Get the last element of an array. Passing **n** will return the last N
  // values in the array.
  _.last = function(array, n, guard) {
    if (array == null) return void 0;
    if (n == null || guard) return array[array.length - 1];
    return _.rest(array, Math.max(0, array.length - n));
  };

  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
  // Especially useful on the arguments object. Passing an **n** will return
  // the rest N values in the array.
  _.rest = _.tail = _.drop = function(array, n, guard) {
    return slice.call(array, n == null || guard ? 1 : n);
  };

  // Trim out all falsy values from an array.
  _.compact = function(array) {
    return _.filter(array, _.identity);
  };

  // Internal implementation of a recursive `flatten` function.
  var flatten = function(input, shallow, strict, startIndex) {
    var output = [], idx = 0;
    for (var i = startIndex || 0, length = getLength(input); i < length; i++) {
      var value = input[i];
      if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
        //flatten current level of array or arguments object
        if (!shallow) value = flatten(value, shallow, strict);
        var j = 0, len = value.length;
        output.length += len;
        while (j < len) {
          output[idx++] = value[j++];
        }
      } else if (!strict) {
        output[idx++] = value;
      }
    }
    return output;
  };

  // Flatten out an array, either recursively (by default), or just one level.
  _.flatten = function(array, shallow) {
    return flatten(array, shallow, false);
  };

  // Return a version of the array that does not contain the specified value(s).
  _.without = function(array) {
    return _.difference(array, slice.call(arguments, 1));
  };

  // Produce a duplicate-free version of the array. If the array has already
  // been sorted, you have the option of using a faster algorithm.
  // Aliased as `unique`.
  _.uniq = _.unique = function(array, isSorted, iteratee, context) {
    if (!_.isBoolean(isSorted)) {
      context = iteratee;
      iteratee = isSorted;
      isSorted = false;
    }
    if (iteratee != null) iteratee = cb(iteratee, context);
    var result = [];
    var seen = [];
    for (var i = 0, length = getLength(array); i < length; i++) {
      var value = array[i],
          computed = iteratee ? iteratee(value, i, array) : value;
      if (isSorted) {
        if (!i || seen !== computed) result.push(value);
        seen = computed;
      } else if (iteratee) {
        if (!_.contains(seen, computed)) {
          seen.push(computed);
          result.push(value);
        }
      } else if (!_.contains(result, value)) {
        result.push(value);
      }
    }
    return result;
  };

  // Produce an array that contains the union: each distinct element from all of
  // the passed-in arrays.
  _.union = function() {
    return _.uniq(flatten(arguments, true, true));
  };

  // Produce an array that contains every item shared between all the
  // passed-in arrays.
  _.intersection = function(array) {
    var result = [];
    var argsLength = arguments.length;
    for (var i = 0, length = getLength(array); i < length; i++) {
      var item = array[i];
      if (_.contains(result, item)) continue;
      for (var j = 1; j < argsLength; j++) {
        if (!_.contains(arguments[j], item)) break;
      }
      if (j === argsLength) result.push(item);
    }
    return result;
  };

  // Take the difference between one array and a number of other arrays.
  // Only the elements present in just the first array will remain.
  _.difference = function(array) {
    var rest = flatten(arguments, true, true, 1);
    return _.filter(array, function(value){
      return !_.contains(rest, value);
    });
  };

  // Zip together multiple lists into a single array -- elements that share
  // an index go together.
  _.zip = function() {
    return _.unzip(arguments);
  };

  // Complement of _.zip. Unzip accepts an array of arrays and groups
  // each array's elements on shared indices
  _.unzip = function(array) {
    var length = array && _.max(array, getLength).length || 0;
    var result = Array(length);

    for (var index = 0; index < length; index++) {
      result[index] = _.pluck(array, index);
    }
    return result;
  };

  // Converts lists into objects. Pass either a single array of `[key, value]`
  // pairs, or two parallel arrays of the same length -- one of keys, and one of
  // the corresponding values.
  _.object = function(list, values) {
    var result = {};
    for (var i = 0, length = getLength(list); i < length; i++) {
      if (values) {
        result[list[i]] = values[i];
      } else {
        result[list[i][0]] = list[i][1];
      }
    }
    return result;
  };

  // Generator function to create the findIndex and findLastIndex functions
  function createPredicateIndexFinder(dir) {
    return function(array, predicate, context) {
      predicate = cb(predicate, context);
      var length = getLength(array);
      var index = dir > 0 ? 0 : length - 1;
      for (; index >= 0 && index < length; index += dir) {
        if (predicate(array[index], index, array)) return index;
      }
      return -1;
    };
  }

  // Returns the first index on an array-like that passes a predicate test
  _.findIndex = createPredicateIndexFinder(1);
  _.findLastIndex = createPredicateIndexFinder(-1);

  // Use a comparator function to figure out the smallest index at which
  // an object should be inserted so as to maintain order. Uses binary search.
  _.sortedIndex = function(array, obj, iteratee, context) {
    iteratee = cb(iteratee, context, 1);
    var value = iteratee(obj);
    var low = 0, high = getLength(array);
    while (low < high) {
      var mid = Math.floor((low + high) / 2);
      if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
    }
    return low;
  };

  // Generator function to create the indexOf and lastIndexOf functions
  function createIndexFinder(dir, predicateFind, sortedIndex) {
    return function(array, item, idx) {
      var i = 0, length = getLength(array);
      if (typeof idx == 'number') {
        if (dir > 0) {
            i = idx >= 0 ? idx : Math.max(idx + length, i);
        } else {
            length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
        }
      } else if (sortedIndex && idx && length) {
        idx = sortedIndex(array, item);
        return array[idx] === item ? idx : -1;
      }
      if (item !== item) {
        idx = predicateFind(slice.call(array, i, length), _.isNaN);
        return idx >= 0 ? idx + i : -1;
      }
      for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
        if (array[idx] === item) return idx;
      }
      return -1;
    };
  }

  // Return the position of the first occurrence of an item in an array,
  // or -1 if the item is not included in the array.
  // If the array is large and already in sort order, pass `true`
  // for **isSorted** to use binary search.
  _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
  _.lastIndexOf = createIndexFinder(-1, _.findLastIndex);

  // Generate an integer Array containing an arithmetic progression. A port of
  // the native Python `range()` function. See
  // [the Python documentation](http://docs.python.org/library/functions.html#range).
  _.range = function(start, stop, step) {
    if (stop == null) {
      stop = start || 0;
      start = 0;
    }
    step = step || 1;

    var length = Math.max(Math.ceil((stop - start) / step), 0);
    var range = Array(length);

    for (var idx = 0; idx < length; idx++, start += step) {
      range[idx] = start;
    }

    return range;
  };

  // Function (ahem) Functions
  // ------------------

  // Determines whether to execute a function as a constructor
  // or a normal function with the provided arguments
  var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
    if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
    var self = baseCreate(sourceFunc.prototype);
    var result = sourceFunc.apply(self, args);
    if (_.isObject(result)) return result;
    return self;
  };

  // Create a function bound to a given object (assigning `this`, and arguments,
  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
  // available.
  _.bind = function(func, context) {
    if (nativeBind && func.bind === nativeBind) return nativeBind.apply(func, slice.call(arguments, 1));
    if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
    var args = slice.call(arguments, 2);
    var bound = function() {
      return executeBound(func, bound, context, this, args.concat(slice.call(arguments)));
    };
    return bound;
  };

  // Partially apply a function by creating a version that has had some of its
  // arguments pre-filled, without changing its dynamic `this` context. _ acts
  // as a placeholder, allowing any combination of arguments to be pre-filled.
  _.partial = function(func) {
    var boundArgs = slice.call(arguments, 1);
    var bound = function() {
      var position = 0, length = boundArgs.length;
      var args = Array(length);
      for (var i = 0; i < length; i++) {
        args[i] = boundArgs[i] === _ ? arguments[position++] : boundArgs[i];
      }
      while (position < arguments.length) args.push(arguments[position++]);
      return executeBound(func, bound, this, this, args);
    };
    return bound;
  };

  // Bind a number of an object's methods to that object. Remaining arguments
  // are the method names to be bound. Useful for ensuring that all callbacks
  // defined on an object belong to it.
  _.bindAll = function(obj) {
    var i, length = arguments.length, key;
    if (length <= 1) throw new Error('bindAll must be passed function names');
    for (i = 1; i < length; i++) {
      key = arguments[i];
      obj[key] = _.bind(obj[key], obj);
    }
    return obj;
  };

  // Memoize an expensive function by storing its results.
  _.memoize = function(func, hasher) {
    var memoize = function(key) {
      var cache = memoize.cache;
      var address = '' + (hasher ? hasher.apply(this, arguments) : key);
      if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
      return cache[address];
    };
    memoize.cache = {};
    return memoize;
  };

  // Delays a function for the given number of milliseconds, and then calls
  // it with the arguments supplied.
  _.delay = function(func, wait) {
    var args = slice.call(arguments, 2);
    return setTimeout(function(){
      return func.apply(null, args);
    }, wait);
  };

  // Defers a function, scheduling it to run after the current call stack has
  // cleared.
  _.defer = _.partial(_.delay, _, 1);

  // Returns a function, that, when invoked, will only be triggered at most once
  // during a given window of time. Normally, the throttled function will run
  // as much as it can, without ever going more than once per `wait` duration;
  // but if you'd like to disable the execution on the leading edge, pass
  // `{leading: false}`. To disable execution on the trailing edge, ditto.
  _.throttle = function(func, wait, options) {
    var context, args, result;
    var timeout = null;
    var previous = 0;
    if (!options) options = {};
    var later = function() {
      previous = options.leading === false ? 0 : _.now();
      timeout = null;
      result = func.apply(context, args);
      if (!timeout) context = args = null;
    };
    return function() {
      var now = _.now();
      if (!previous && options.leading === false) previous = now;
      var remaining = wait - (now - previous);
      context = this;
      args = arguments;
      if (remaining <= 0 || remaining > wait) {
        if (timeout) {
          clearTimeout(timeout);
          timeout = null;
        }
        previous = now;
        result = func.apply(context, args);
        if (!timeout) context = args = null;
      } else if (!timeout && options.trailing !== false) {
        timeout = setTimeout(later, remaining);
      }
      return result;
    };
  };

  // Returns a function, that, as long as it continues to be invoked, will not
  // be triggered. The function will be called after it stops being called for
  // N milliseconds. If `immediate` is passed, trigger the function on the
  // leading edge, instead of the trailing.
  _.debounce = function(func, wait, immediate) {
    var timeout, args, context, timestamp, result;

    var later = function() {
      var last = _.now() - timestamp;

      if (last < wait && last >= 0) {
        timeout = setTimeout(later, wait - last);
      } else {
        timeout = null;
        if (!immediate) {
          result = func.apply(context, args);
          if (!timeout) context = args = null;
        }
      }
    };

    return function() {
      context = this;
      args = arguments;
      timestamp = _.now();
      var callNow = immediate && !timeout;
      if (!timeout) timeout = setTimeout(later, wait);
      if (callNow) {
        result = func.apply(context, args);
        context = args = null;
      }

      return result;
    };
  };

  // Returns the first function passed as an argument to the second,
  // allowing you to adjust arguments, run code before and after, and
  // conditionally execute the original function.
  _.wrap = function(func, wrapper) {
    return _.partial(wrapper, func);
  };

  // Returns a negated version of the passed-in predicate.
  _.negate = function(predicate) {
    return function() {
      return !predicate.apply(this, arguments);
    };
  };

  // Returns a function that is the composition of a list of functions, each
  // consuming the return value of the function that follows.
  _.compose = function() {
    var args = arguments;
    var start = args.length - 1;
    return function() {
      var i = start;
      var result = args[start].apply(this, arguments);
      while (i--) result = args[i].call(this, result);
      return result;
    };
  };

  // Returns a function that will only be executed on and after the Nth call.
  _.after = function(times, func) {
    return function() {
      if (--times < 1) {
        return func.apply(this, arguments);
      }
    };
  };

  // Returns a function that will only be executed up to (but not including) the Nth call.
  _.before = function(times, func) {
    var memo;
    return function() {
      if (--times > 0) {
        memo = func.apply(this, arguments);
      }
      if (times <= 1) func = null;
      return memo;
    };
  };

  // Returns a function that will be executed at most one time, no matter how
  // often you call it. Useful for lazy initialization.
  _.once = _.partial(_.before, 2);

  // Object Functions
  // ----------------

  // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
  var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
  var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
                      'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];

  function collectNonEnumProps(obj, keys) {
    var nonEnumIdx = nonEnumerableProps.length;
    var constructor = obj.constructor;
    var proto = (_.isFunction(constructor) && constructor.prototype) || ObjProto;

    // Constructor is a special case.
    var prop = 'constructor';
    if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);

    while (nonEnumIdx--) {
      prop = nonEnumerableProps[nonEnumIdx];
      if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
        keys.push(prop);
      }
    }
  }

  // Retrieve the names of an object's own properties.
  // Delegates to **ECMAScript 5**'s native `Object.keys`
  _.keys = function(obj) {
    if (!_.isObject(obj)) return [];
    if (nativeKeys) return nativeKeys(obj);
    var keys = [];
    for (var key in obj) if (_.has(obj, key)) keys.push(key);
    // Ahem, IE < 9.
    if (hasEnumBug) collectNonEnumProps(obj, keys);
    return keys;
  };

  // Retrieve all the property names of an object.
  _.allKeys = function(obj) {
    if (!_.isObject(obj)) return [];
    var keys = [];
    for (var key in obj) keys.push(key);
    // Ahem, IE < 9.
    if (hasEnumBug) collectNonEnumProps(obj, keys);
    return keys;
  };

  // Retrieve the values of an object's properties.
  _.values = function(obj) {
    var keys = _.keys(obj);
    var length = keys.length;
    var values = Array(length);
    for (var i = 0; i < length; i++) {
      values[i] = obj[keys[i]];
    }
    return values;
  };

  // Returns the results of applying the iteratee to each element of the object
  // In contrast to _.map it returns an object
  _.mapObject = function(obj, iteratee, context) {
    iteratee = cb(iteratee, context);
    var keys =  _.keys(obj),
          length = keys.length,
          results = {},
          currentKey;
      for (var index = 0; index < length; index++) {
        currentKey = keys[index];
        results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
      }
      return results;
  };

  // Convert an object into a list of `[key, value]` pairs.
  _.pairs = function(obj) {
    var keys = _.keys(obj);
    var length = keys.length;
    var pairs = Array(length);
    for (var i = 0; i < length; i++) {
      pairs[i] = [keys[i], obj[keys[i]]];
    }
    return pairs;
  };

  // Invert the keys and values of an object. The values must be serializable.
  _.invert = function(obj) {
    var result = {};
    var keys = _.keys(obj);
    for (var i = 0, length = keys.length; i < length; i++) {
      result[obj[keys[i]]] = keys[i];
    }
    return result;
  };

  // Return a sorted list of the function names available on the object.
  // Aliased as `methods`
  _.functions = _.methods = function(obj) {
    var names = [];
    for (var key in obj) {
      if (_.isFunction(obj[key])) names.push(key);
    }
    return names.sort();
  };

  // Extend a given object with all the properties in passed-in object(s).
  _.extend = createAssigner(_.allKeys);

  // Assigns a given object with all the own properties in the passed-in object(s)
  // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
  _.extendOwn = _.assign = createAssigner(_.keys);

  // Returns the first key on an object that passes a predicate test
  _.findKey = function(obj, predicate, context) {
    predicate = cb(predicate, context);
    var keys = _.keys(obj), key;
    for (var i = 0, length = keys.length; i < length; i++) {
      key = keys[i];
      if (predicate(obj[key], key, obj)) return key;
    }
  };

  // Return a copy of the object only containing the whitelisted properties.
  _.pick = function(object, oiteratee, context) {
    var result = {}, obj = object, iteratee, keys;
    if (obj == null) return result;
    if (_.isFunction(oiteratee)) {
      keys = _.allKeys(obj);
      iteratee = optimizeCb(oiteratee, context);
    } else {
      keys = flatten(arguments, false, false, 1);
      iteratee = function(value, key, obj) { return key in obj; };
      obj = Object(obj);
    }
    for (var i = 0, length = keys.length; i < length; i++) {
      var key = keys[i];
      var value = obj[key];
      if (iteratee(value, key, obj)) result[key] = value;
    }
    return result;
  };

   // Return a copy of the object without the blacklisted properties.
  _.omit = function(obj, iteratee, context) {
    if (_.isFunction(iteratee)) {
      iteratee = _.negate(iteratee);
    } else {
      var keys = _.map(flatten(arguments, false, false, 1), String);
      iteratee = function(value, key) {
        return !_.contains(keys, key);
      };
    }
    return _.pick(obj, iteratee, context);
  };

  // Fill in a given object with default properties.
  _.defaults = createAssigner(_.allKeys, true);

  // Creates an object that inherits from the given prototype object.
  // If additional properties are provided then they will be added to the
  // created object.
  _.create = function(prototype, props) {
    var result = baseCreate(prototype);
    if (props) _.extendOwn(result, props);
    return result;
  };

  // Create a (shallow-cloned) duplicate of an object.
  _.clone = function(obj) {
    if (!_.isObject(obj)) return obj;
    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
  };

  // Invokes interceptor with the obj, and then returns obj.
  // The primary purpose of this method is to "tap into" a method chain, in
  // order to perform operations on intermediate results within the chain.
  _.tap = function(obj, interceptor) {
    interceptor(obj);
    return obj;
  };

  // Returns whether an object has a given set of `key:value` pairs.
  _.isMatch = function(object, attrs) {
    var keys = _.keys(attrs), length = keys.length;
    if (object == null) return !length;
    var obj = Object(object);
    for (var i = 0; i < length; i++) {
      var key = keys[i];
      if (attrs[key] !== obj[key] || !(key in obj)) return false;
    }
    return true;
  };


  // Internal recursive comparison function for `isEqual`.
  var eq = function(a, b, aStack, bStack) {
    // Identical objects are equal. `0 === -0`, but they aren't identical.
    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
    if (a === b) return a !== 0 || 1 / a === 1 / b;
    // A strict comparison is necessary because `null == undefined`.
    if (a == null || b == null) return a === b;
    // Unwrap any wrapped objects.
    if (a instanceof _) a = a._wrapped;
    if (b instanceof _) b = b._wrapped;
    // Compare `[[Class]]` names.
    var className = toString.call(a);
    if (className !== toString.call(b)) return false;
    switch (className) {
      // Strings, numbers, regular expressions, dates, and booleans are compared by value.
      case '[object RegExp]':
      // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
      case '[object String]':
        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
        // equivalent to `new String("5")`.
        return '' + a === '' + b;
      case '[object Number]':
        // `NaN`s are equivalent, but non-reflexive.
        // Object(NaN) is equivalent to NaN
        if (+a !== +a) return +b !== +b;
        // An `egal` comparison is performed for other numeric values.
        return +a === 0 ? 1 / +a === 1 / b : +a === +b;
      case '[object Date]':
      case '[object Boolean]':
        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
        // millisecond representations. Note that invalid dates with millisecond representations
        // of `NaN` are not equivalent.
        return +a === +b;
    }

    var areArrays = className === '[object Array]';
    if (!areArrays) {
      if (typeof a != 'object' || typeof b != 'object') return false;

      // Objects with different constructors are not equivalent, but `Object`s or `Array`s
      // from different frames are.
      var aCtor = a.constructor, bCtor = b.constructor;
      if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
                               _.isFunction(bCtor) && bCtor instanceof bCtor)
                          && ('constructor' in a && 'constructor' in b)) {
        return false;
      }
    }
    // Assume equality for cyclic structures. The algorithm for detecting cyclic
    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.

    // Initializing stack of traversed objects.
    // It's done here since we only need them for objects and arrays comparison.
    aStack = aStack || [];
    bStack = bStack || [];
    var length = aStack.length;
    while (length--) {
      // Linear search. Performance is inversely proportional to the number of
      // unique nested structures.
      if (aStack[length] === a) return bStack[length] === b;
    }

    // Add the first object to the stack of traversed objects.
    aStack.push(a);
    bStack.push(b);

    // Recursively compare objects and arrays.
    if (areArrays) {
      // Compare array lengths to determine if a deep comparison is necessary.
      length = a.length;
      if (length !== b.length) return false;
      // Deep compare the contents, ignoring non-numeric properties.
      while (length--) {
        if (!eq(a[length], b[length], aStack, bStack)) return false;
      }
    } else {
      // Deep compare objects.
      var keys = _.keys(a), key;
      length = keys.length;
      // Ensure that both objects contain the same number of properties before comparing deep equality.
      if (_.keys(b).length !== length) return false;
      while (length--) {
        // Deep compare each member
        key = keys[length];
        if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
      }
    }
    // Remove the first object from the stack of traversed objects.
    aStack.pop();
    bStack.pop();
    return true;
  };

  // Perform a deep comparison to check if two objects are equal.
  _.isEqual = function(a, b) {
    return eq(a, b);
  };

  // Is a given array, string, or object empty?
  // An "empty" object has no enumerable own-properties.
  _.isEmpty = function(obj) {
    if (obj == null) return true;
    if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
    return _.keys(obj).length === 0;
  };

  // Is a given value a DOM element?
  _.isElement = function(obj) {
    return !!(obj && obj.nodeType === 1);
  };

  // Is a given value an array?
  // Delegates to ECMA5's native Array.isArray
  _.isArray = nativeIsArray || function(obj) {
    return toString.call(obj) === '[object Array]';
  };

  // Is a given variable an object?
  _.isObject = function(obj) {
    var type = typeof obj;
    return type === 'function' || type === 'object' && !!obj;
  };

  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError.
  _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error'], function(name) {
    _['is' + name] = function(obj) {
      return toString.call(obj) === '[object ' + name + ']';
    };
  });

  // Define a fallback version of the method in browsers (ahem, IE < 9), where
  // there isn't any inspectable "Arguments" type.
  if (!_.isArguments(arguments)) {
    _.isArguments = function(obj) {
      return _.has(obj, 'callee');
    };
  }

  // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
  // IE 11 (#1621), and in Safari 8 (#1929).
  if (typeof /./ != 'function' && typeof Int8Array != 'object') {
    _.isFunction = function(obj) {
      return typeof obj == 'function' || false;
    };
  }

  // Is a given object a finite number?
  _.isFinite = function(obj) {
    return isFinite(obj) && !isNaN(parseFloat(obj));
  };

  // Is the given value `NaN`? (NaN is the only number which does not equal itself).
  _.isNaN = function(obj) {
    return _.isNumber(obj) && obj !== +obj;
  };

  // Is a given value a boolean?
  _.isBoolean = function(obj) {
    return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
  };

  // Is a given value equal to null?
  _.isNull = function(obj) {
    return obj === null;
  };

  // Is a given variable undefined?
  _.isUndefined = function(obj) {
    return obj === void 0;
  };

  // Shortcut function for checking if an object has a given property directly
  // on itself (in other words, not on a prototype).
  _.has = function(obj, key) {
    return obj != null && hasOwnProperty.call(obj, key);
  };

  // Utility Functions
  // -----------------

  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
  // previous owner. Returns a reference to the Underscore object.
  _.noConflict = function() {
    root._ = previousUnderscore;
    return this;
  };

  // Keep the identity function around for default iteratees.
  _.identity = function(value) {
    return value;
  };

  // Predicate-generating functions. Often useful outside of Underscore.
  _.constant = function(value) {
    return function() {
      return value;
    };
  };

  _.noop = function(){};

  _.property = property;

  // Generates a function for a given object that returns a given property.
  _.propertyOf = function(obj) {
    return obj == null ? function(){} : function(key) {
      return obj[key];
    };
  };

  // Returns a predicate for checking whether an object has a given set of
  // `key:value` pairs.
  _.matcher = _.matches = function(attrs) {
    attrs = _.extendOwn({}, attrs);
    return function(obj) {
      return _.isMatch(obj, attrs);
    };
  };

  // Run a function **n** times.
  _.times = function(n, iteratee, context) {
    var accum = Array(Math.max(0, n));
    iteratee = optimizeCb(iteratee, context, 1);
    for (var i = 0; i < n; i++) accum[i] = iteratee(i);
    return accum;
  };

  // Return a random integer between min and max (inclusive).
  _.random = function(min, max) {
    if (max == null) {
      max = min;
      min = 0;
    }
    return min + Math.floor(Math.random() * (max - min + 1));
  };

  // A (possibly faster) way to get the current timestamp as an integer.
  _.now = Date.now || function() {
    return new Date().getTime();
  };

   // List of HTML entities for escaping.
  var escapeMap = {
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#x27;',
    '`': '&#x60;'
  };
  var unescapeMap = _.invert(escapeMap);

  // Functions for escaping and unescaping strings to/from HTML interpolation.
  var createEscaper = function(map) {
    var escaper = function(match) {
      return map[match];
    };
    // Regexes for identifying a key that needs to be escaped
    var source = '(?:' + _.keys(map).join('|') + ')';
    var testRegexp = RegExp(source);
    var replaceRegexp = RegExp(source, 'g');
    return function(string) {
      string = string == null ? '' : '' + string;
      return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
    };
  };
  _.escape = createEscaper(escapeMap);
  _.unescape = createEscaper(unescapeMap);

  // If the value of the named `property` is a function then invoke it with the
  // `object` as context; otherwise, return it.
  _.result = function(object, property, fallback) {
    var value = object == null ? void 0 : object[property];
    if (value === void 0) {
      value = fallback;
    }
    return _.isFunction(value) ? value.call(object) : value;
  };

  // Generate a unique integer id (unique within the entire client session).
  // Useful for temporary DOM ids.
  var idCounter = 0;
  _.uniqueId = function(prefix) {
    var id = ++idCounter + '';
    return prefix ? prefix + id : id;
  };

  // By default, Underscore uses ERB-style template delimiters, change the
  // following template settings to use alternative delimiters.
  _.templateSettings = {
    evaluate    : /<%([\s\S]+?)%>/g,
    interpolate : /<%=([\s\S]+?)%>/g,
    escape      : /<%-([\s\S]+?)%>/g
  };

  // When customizing `templateSettings`, if you don't want to define an
  // interpolation, evaluation or escaping regex, we need one that is
  // guaranteed not to match.
  var noMatch = /(.)^/;

  // Certain characters need to be escaped so that they can be put into a
  // string literal.
  var escapes = {
    "'":      "'",
    '\\':     '\\',
    '\r':     'r',
    '\n':     'n',
    '\u2028': 'u2028',
    '\u2029': 'u2029'
  };

  var escaper = /\\|'|\r|\n|\u2028|\u2029/g;

  var escapeChar = function(match) {
    return '\\' + escapes[match];
  };

  // JavaScript micro-templating, similar to John Resig's implementation.
  // Underscore templating handles arbitrary delimiters, preserves whitespace,
  // and correctly escapes quotes within interpolated code.
  // NB: `oldSettings` only exists for backwards compatibility.
  _.template = function(text, settings, oldSettings) {
    if (!settings && oldSettings) settings = oldSettings;
    settings = _.defaults({}, settings, _.templateSettings);

    // Combine delimiters into one regular expression via alternation.
    var matcher = RegExp([
      (settings.escape || noMatch).source,
      (settings.interpolate || noMatch).source,
      (settings.evaluate || noMatch).source
    ].join('|') + '|$', 'g');

    // Compile the template source, escaping string literals appropriately.
    var index = 0;
    var source = "__p+='";
    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
      source += text.slice(index, offset).replace(escaper, escapeChar);
      index = offset + match.length;

      if (escape) {
        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
      } else if (interpolate) {
        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
      } else if (evaluate) {
        source += "';\n" + evaluate + "\n__p+='";
      }

      // Adobe VMs need the match returned to produce the correct offest.
      return match;
    });
    source += "';\n";

    // If a variable is not specified, place data values in local scope.
    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';

    source = "var __t,__p='',__j=Array.prototype.join," +
      "print=function(){__p+=__j.call(arguments,'');};\n" +
      source + 'return __p;\n';

    try {
      var render = new Function(settings.variable || 'obj', '_', source);
    } catch (e) {
      e.source = source;
      throw e;
    }

    var template = function(data) {
      return render.call(this, data, _);
    };

    // Provide the compiled source as a convenience for precompilation.
    var argument = settings.variable || 'obj';
    template.source = 'function(' + argument + '){\n' + source + '}';

    return template;
  };

  // Add a "chain" function. Start chaining a wrapped Underscore object.
  _.chain = function(obj) {
    var instance = _(obj);
    instance._chain = true;
    return instance;
  };

  // OOP
  // ---------------
  // If Underscore is called as a function, it returns a wrapped object that
  // can be used OO-style. This wrapper holds altered versions of all the
  // underscore functions. Wrapped objects may be chained.

  // Helper function to continue chaining intermediate results.
  var result = function(instance, obj) {
    return instance._chain ? _(obj).chain() : obj;
  };

  // Add your own custom functions to the Underscore object.
  _.mixin = function(obj) {
    _.each(_.functions(obj), function(name) {
      var func = _[name] = obj[name];
      _.prototype[name] = function() {
        var args = [this._wrapped];
        push.apply(args, arguments);
        return result(this, func.apply(_, args));
      };
    });
  };

  // Add all of the Underscore functions to the wrapper object.
  _.mixin(_);

  // Add all mutator Array functions to the wrapper.
  _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
    var method = ArrayProto[name];
    _.prototype[name] = function() {
      var obj = this._wrapped;
      method.apply(obj, arguments);
      if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
      return result(this, obj);
    };
  });

  // Add all accessor Array functions to the wrapper.
  _.each(['concat', 'join', 'slice'], function(name) {
    var method = ArrayProto[name];
    _.prototype[name] = function() {
      return result(this, method.apply(this._wrapped, arguments));
    };
  });

  // Extracts the result from a wrapped and chained object.
  _.prototype.value = function() {
    return this._wrapped;
  };

  // Provide unwrapping proxy for some methods used in engine operations
  // such as arithmetic and JSON stringification.
  _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;

  _.prototype.toString = function() {
    return '' + this._wrapped;
  };

  // AMD registration happens at the end for compatibility with AMD loaders
  // that may not enforce next-turn semantics on modules. Even though general
  // practice for AMD registration is to be anonymous, underscore registers
  // as a named module because, like jQuery, it is a base library that is
  // popular enough to be bundled in a third party lib, but not be part of
  // an AMD load request. Those cases could generate an error when an
  // anonymous define() is called outside of a loader request.
  if (typeof define === 'function' && define.amd) {
    define('underscore', [], function() {
      return _;
    });
  }
}.call(this));
(function() {
  this.Gmaps = {
    build: function(type, options) {
      var model;
      if (options == null) {
        options = {};
      }
      model = _.isFunction(options.handler) ? options.handler : Gmaps.Objects.Handler;
      return new model(type, options);
    },
    Builders: {},
    Objects: {},
    Google: {
      Objects: {},
      Builders: {}
    }
  };

}).call(this);
(function() {
  var moduleKeywords,
    indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; };

  moduleKeywords = ['extended', 'included'];

  this.Gmaps.Base = (function() {
    function Base() {}

    Base.extend = function(obj) {
      var key, ref, value;
      for (key in obj) {
        value = obj[key];
        if (indexOf.call(moduleKeywords, key) < 0) {
          this[key] = value;
        }
      }
      if ((ref = obj.extended) != null) {
        ref.apply(this);
      }
      return this;
    };

    Base.include = function(obj) {
      var key, ref, value;
      for (key in obj) {
        value = obj[key];
        if (indexOf.call(moduleKeywords, key) < 0) {
          this.prototype[key] = value;
        }
      }
      if ((ref = obj.included) != null) {
        ref.apply(this);
      }
      return this;
    };

    return Base;

  })();

}).call(this);
(function() {
  this.Gmaps.Objects.BaseBuilder = (function() {
    function BaseBuilder() {}

    BaseBuilder.prototype.build = function() {
      return new (this.model_class())(this.serviceObject);
    };

    BaseBuilder.prototype.before_init = function() {};

    BaseBuilder.prototype.after_init = function() {};

    BaseBuilder.prototype.addListener = function(action, fn) {
      return this.primitives().addListener(this.getServiceObject(), action, fn);
    };

    BaseBuilder.prototype.getServiceObject = function() {
      return this.serviceObject;
    };

    BaseBuilder.prototype.primitives = function() {
      return this.constructor.PRIMITIVES;
    };

    BaseBuilder.prototype.model_class = function() {
      return this.constructor.OBJECT;
    };

    return BaseBuilder;

  })();

}).call(this);
(function() {
  this.Gmaps.Objects.Builders = function(builderClass, objectClass, primitivesProvider) {
    return {
      build: function(args, provider_options, internal_options) {
        var builder;
        objectClass.PRIMITIVES = primitivesProvider;
        builderClass.OBJECT = objectClass;
        builderClass.PRIMITIVES = primitivesProvider;
        builder = new builderClass(args, provider_options, internal_options);
        return builder.build();
      }
    };
  };

}).call(this);
(function() {
  this.Gmaps.Objects.Handler = (function() {
    function Handler(type, options) {
      this.type = type;
      if (options == null) {
        options = {};
      }
      this.setPrimitives(options);
      this.setOptions(options);
      this._cacheAllBuilders();
      this.resetBounds();
    }

    Handler.prototype.buildMap = function(options, onMapLoad) {
      if (onMapLoad == null) {
        onMapLoad = function() {};
      }
      return this.map = this._builder('Map').build(options, (function(_this) {
        return function() {
          _this._createClusterer();
          return onMapLoad();
        };
      })(this));
    };

    Handler.prototype.addMarkers = function(markers_data, provider_options) {
      return _.map(markers_data, (function(_this) {
        return function(marker_data) {
          return _this.addMarker(marker_data, provider_options);
        };
      })(this));
    };

    Handler.prototype.addMarker = function(marker_data, provider_options) {
      var marker;
      marker = this._builder('Marker').build(marker_data, provider_options, this.marker_options);
      marker.setMap(this.getMap());
      this.clusterer.addMarker(marker);
      return marker;
    };

    Handler.prototype.addCircles = function(circles_data, provider_options) {
      return _.map(circles_data, (function(_this) {
        return function(circle_data) {
          return _this.addCircle(circle_data, provider_options);
        };
      })(this));
    };

    Handler.prototype.addCircle = function(circle_data, provider_options) {
      return this._addResource('circle', circle_data, provider_options);
    };

    Handler.prototype.addPolylines = function(polylines_data, provider_options) {
      return _.map(polylines_data, (function(_this) {
        return function(polyline_data) {
          return _this.addPolyline(polyline_data, provider_options);
        };
      })(this));
    };

    Handler.prototype.addPolyline = function(polyline_data, provider_options) {
      return this._addResource('polyline', polyline_data, provider_options);
    };

    Handler.prototype.addPolygons = function(polygons_data, provider_options) {
      return _.map(polygons_data, (function(_this) {
        return function(polygon_data) {
          return _this.addPolygon(polygon_data, provider_options);
        };
      })(this));
    };

    Handler.prototype.addPolygon = function(polygon_data, provider_options) {
      return this._addResource('polygon', polygon_data, provider_options);
    };

    Handler.prototype.addKmls = function(kmls_data, provider_options) {
      return _.map(kmls_data, (function(_this) {
        return function(kml_data) {
          return _this.addKml(kml_data, provider_options);
        };
      })(this));
    };

    Handler.prototype.addKml = function(kml_data, provider_options) {
      return this._addResource('kml', kml_data, provider_options);
    };

    Handler.prototype.removeMarkers = function(gem_markers) {
      return _.map(gem_markers, (function(_this) {
        return function(gem_marker) {
          return _this.removeMarker(gem_marker);
        };
      })(this));
    };

    Handler.prototype.removeMarker = function(gem_marker) {
      gem_marker.clear();
      return this.clusterer.removeMarker(gem_marker);
    };

    Handler.prototype.fitMapToBounds = function() {
      return this.map.fitToBounds(this.bounds.getServiceObject());
    };

    Handler.prototype.getMap = function() {
      return this.map.getServiceObject();
    };

    Handler.prototype.setOptions = function(options) {
      this.marker_options = _.extend(this._default_marker_options(), options.markers);
      this.builders = _.extend(this._default_builders(), options.builders);
      return this.models = _.extend(this._default_models(), options.models);
    };

    Handler.prototype.resetBounds = function() {
      return this.bounds = this._builder('Bound').build();
    };

    Handler.prototype.setPrimitives = function(options) {
      return this.primitives = options.primitives === void 0 ? this._rootModule().Primitives() : _.isFunction(options.primitives) ? options.primitives() : options.primitives;
    };

    Handler.prototype.currentInfowindow = function() {
      return this.builders.Marker.CURRENT_INFOWINDOW;
    };

    Handler.prototype._addResource = function(resource_name, resource_data, provider_options) {
      var resource;
      resource = this._builder(resource_name).build(resource_data, provider_options);
      resource.setMap(this.getMap());
      return resource;
    };

    Handler.prototype._cacheAllBuilders = function() {
      var that;
      that = this;
      return _.each(['Bound', 'Circle', 'Clusterer', 'Kml', 'Map', 'Marker', 'Polygon', 'Polyline'], function(kind) {
        return that._builder(kind);
      });
    };

    Handler.prototype._clusterize = function() {
      return _.isObject(this.marker_options.clusterer);
    };

    Handler.prototype._createClusterer = function() {
      return this.clusterer = this._builder('Clusterer').build({
        map: this.getMap()
      }, this.marker_options.clusterer);
    };

    Handler.prototype._default_marker_options = function() {
      return _.clone({
        singleInfowindow: true,
        maxRandomDistance: 0,
        clusterer: {
          maxZoom: 5,
          gridSize: 50
        }
      });
    };

    Handler.prototype._builder = function(name) {
      var name1;
      name = this._capitalize(name);
      if (this[name1 = "__builder" + name] == null) {
        this[name1] = Gmaps.Objects.Builders(this.builders[name], this.models[name], this.primitives);
      }
      return this["__builder" + name];
    };

    Handler.prototype._default_models = function() {
      var models;
      models = _.clone(this._rootModule().Objects);
      if (this._clusterize()) {
        return models;
      } else {
        models.Clusterer = Gmaps.Objects.NullClusterer;
        return models;
      }
    };

    Handler.prototype._capitalize = function(string) {
      return string.charAt(0).toUpperCase() + string.slice(1);
    };

    Handler.prototype._default_builders = function() {
      return _.clone(this._rootModule().Builders);
    };

    Handler.prototype._rootModule = function() {
      if (this.__rootModule == null) {
        this.__rootModule = Gmaps[this.type];
      }
      return this.__rootModule;
    };

    return Handler;

  })();

}).call(this);
(function() {
  this.Gmaps.Objects.NullClusterer = (function() {
    function NullClusterer() {}

    NullClusterer.prototype.addMarkers = function() {};

    NullClusterer.prototype.addMarker = function() {};

    NullClusterer.prototype.clear = function() {};

    NullClusterer.prototype.removeMarker = function() {};

    return NullClusterer;

  })();

}).call(this);
(function() {
  this.Gmaps.Google.Objects.Common = {
    getServiceObject: function() {
      return this.serviceObject;
    },
    setMap: function(map) {
      return this.getServiceObject().setMap(map);
    },
    clear: function() {
      return this.getServiceObject().setMap(null);
    },
    show: function() {
      return this.getServiceObject().setVisible(true);
    },
    hide: function() {
      return this.getServiceObject().setVisible(false);
    },
    isVisible: function() {
      return this.getServiceObject().getVisible();
    },
    primitives: function() {
      return this.constructor.PRIMITIVES;
    }
  };

}).call(this);
(function() {
  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Builders.Bound = (function(superClass) {
    extend(Bound, superClass);

    function Bound(options) {
      this.before_init();
      this.serviceObject = new (this.primitives().latLngBounds);
      this.after_init();
    }

    return Bound;

  })(Gmaps.Objects.BaseBuilder);

}).call(this);
(function() {
  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Builders.Circle = (function(superClass) {
    extend(Circle, superClass);

    function Circle(args, provider_options) {
      this.args = args;
      this.provider_options = provider_options != null ? provider_options : {};
      this.before_init();
      this.serviceObject = this.create_circle();
      this.after_init();
    }

    Circle.prototype.create_circle = function() {
      return new (this.primitives().circle)(this.circle_options());
    };

    Circle.prototype.circle_options = function() {
      var base_options;
      base_options = {
        center: new (this.primitives().latLng)(this.args.lat, this.args.lng),
        radius: this.args.radius
      };
      return _.defaults(base_options, this.provider_options);
    };

    return Circle;

  })(Gmaps.Objects.BaseBuilder);

}).call(this);
(function() {
  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Builders.Clusterer = (function(superClass) {
    extend(Clusterer, superClass);

    function Clusterer(args, options) {
      this.args = args;
      this.options = options;
      this.before_init();
      this.serviceObject = new (this.primitives().clusterer)(this.args.map, [], this.options);
      this.after_init();
    }

    return Clusterer;

  })(Gmaps.Objects.BaseBuilder);

}).call(this);
(function() {
  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Builders.Kml = (function(superClass) {
    extend(Kml, superClass);

    function Kml(args, provider_options) {
      this.args = args;
      this.provider_options = provider_options != null ? provider_options : {};
      this.before_init();
      this.serviceObject = this.create_kml();
      this.after_init();
    }

    Kml.prototype.create_kml = function() {
      return new (this.primitives().kml)(this.args.url, this.kml_options());
    };

    Kml.prototype.kml_options = function() {
      var base_options;
      base_options = {};
      return _.defaults(base_options, this.provider_options);
    };

    return Kml;

  })(Gmaps.Objects.BaseBuilder);

}).call(this);
(function() {
  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Builders.Map = (function(superClass) {
    extend(Map, superClass);

    function Map(options, onMapLoad) {
      var provider_options;
      this.before_init();
      provider_options = _.extend(this.default_options(), options.provider);
      this.internal_options = options.internal;
      this.serviceObject = new (this.primitives().map)(document.getElementById(this.internal_options.id), provider_options);
      this.on_map_load(onMapLoad);
      this.after_init();
    }

    Map.prototype.build = function() {
      return new (this.model_class())(this.serviceObject, this.primitives());
    };

    Map.prototype.on_map_load = function(onMapLoad) {
      return this.primitives().addListenerOnce(this.serviceObject, 'idle', onMapLoad);
    };

    Map.prototype.default_options = function() {
      return {
        mapTypeId: this.primitives().mapTypes('ROADMAP'),
        center: new (this.primitives().latLng)(0, 0),
        zoom: 8
      };
    };

    return Map;

  })(Gmaps.Objects.BaseBuilder);

}).call(this);
(function() {
  var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; },
    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Builders.Marker = (function(superClass) {
    extend(Marker, superClass);

    Marker.CURRENT_INFOWINDOW = void 0;

    Marker.CACHE_STORE = {};

    function Marker(args, provider_options, internal_options) {
      this.args = args;
      this.provider_options = provider_options != null ? provider_options : {};
      this.internal_options = internal_options != null ? internal_options : {};
      this.infowindow_binding = bind(this.infowindow_binding, this);
      this.before_init();
      this.create_marker();
      this.create_infowindow_on_click();
      this.after_init();
    }

    Marker.prototype.build = function() {
      return this.marker = new (this.model_class())(this.serviceObject);
    };

    Marker.prototype.create_marker = function() {
      return this.serviceObject = new (this.primitives().marker)(this.marker_options());
    };

    Marker.prototype.create_infowindow = function() {
      if (!_.isString(this.args.infowindow)) {
        return null;
      }
      return new (this.primitives().infowindow)({
        content: this.args.infowindow
      });
    };

    Marker.prototype.marker_options = function() {
      var base_options, coords;
      coords = this._randomized_coordinates();
      base_options = {
        title: this.args.marker_title,
        position: new (this.primitives().latLng)(coords[0], coords[1]),
        icon: this._get_picture('picture'),
        shadow: this._get_picture('shadow')
      };
      return _.extend(this.provider_options, base_options);
    };

    Marker.prototype.create_infowindow_on_click = function() {
      return this.addListener('click', this.infowindow_binding);
    };

    Marker.prototype.infowindow_binding = function() {
      var base;
      if (this._should_close_infowindow()) {
        this.constructor.CURRENT_INFOWINDOW.close();
      }
      this.marker.panTo();
      if (this.infowindow == null) {
        this.infowindow = this.create_infowindow();
      }
      if (this.infowindow == null) {
        return;
      }
      this.infowindow.open(this.getServiceObject().getMap(), this.getServiceObject());
      if ((base = this.marker).infowindow == null) {
        base.infowindow = this.infowindow;
      }
      return this.constructor.CURRENT_INFOWINDOW = this.infowindow;
    };

    Marker.prototype._get_picture = function(picture_name) {
      if (!_.isObject(this.args[picture_name]) || !_.isString(this.args[picture_name].url)) {
        return null;
      }
      return this._create_or_retrieve_image(this._picture_args(picture_name));
    };

    Marker.prototype._create_or_retrieve_image = function(picture_args) {
      if (this.constructor.CACHE_STORE[picture_args.url] === void 0) {
        this.constructor.CACHE_STORE[picture_args.url] = new (this.primitives().markerImage)(picture_args.url, picture_args.size, picture_args.origin, picture_args.anchor, picture_args.scaledSize);
      }
      return this.constructor.CACHE_STORE[picture_args.url];
    };

    Marker.prototype._picture_args = function(picture_name) {
      return {
        url: this.args[picture_name].url,
        anchor: this._createImageAnchorPosition(this.args[picture_name].anchor),
        size: new (this.primitives().size)(this.args[picture_name].width, this.args[picture_name].height),
        scaledSize: null,
        origin: null
      };
    };

    Marker.prototype._createImageAnchorPosition = function(anchorLocation) {
      if (!_.isArray(anchorLocation)) {
        return null;
      }
      return new (this.primitives().point)(anchorLocation[0], anchorLocation[1]);
    };

    Marker.prototype._should_close_infowindow = function() {
      return this.internal_options.singleInfowindow && (this.constructor.CURRENT_INFOWINDOW != null);
    };

    Marker.prototype._randomized_coordinates = function() {
      var Lat, Lng, dx, dy, random;
      if (!_.isNumber(this.internal_options.maxRandomDistance)) {
        return [this.args.lat, this.args.lng];
      }
      random = function() {
        return Math.random() * 2 - 1;
      };
      dx = this.internal_options.maxRandomDistance * random();
      dy = this.internal_options.maxRandomDistance * random();
      Lat = parseFloat(this.args.lat) + (180 / Math.PI) * (dy / 6378137);
      Lng = parseFloat(this.args.lng) + (90 / Math.PI) * (dx / 6378137) / Math.cos(this.args.lat);
      return [Lat, Lng];
    };

    return Marker;

  })(Gmaps.Objects.BaseBuilder);

}).call(this);
(function() {
  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Builders.Polygon = (function(superClass) {
    extend(Polygon, superClass);

    function Polygon(args, provider_options) {
      this.args = args;
      this.provider_options = provider_options != null ? provider_options : {};
      this.before_init();
      this.serviceObject = this.create_polygon();
      this.after_init();
    }

    Polygon.prototype.create_polygon = function() {
      return new (this.primitives().polygon)(this.polygon_options());
    };

    Polygon.prototype.polygon_options = function() {
      var base_options;
      base_options = {
        path: this._build_path()
      };
      return _.defaults(base_options, this.provider_options);
    };

    Polygon.prototype._build_path = function() {
      return _.map(this.args, (function(_this) {
        return function(arg) {
          return new (_this.primitives().latLng)(arg.lat, arg.lng);
        };
      })(this));
    };

    return Polygon;

  })(Gmaps.Objects.BaseBuilder);

}).call(this);
(function() {
  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Builders.Polyline = (function(superClass) {
    extend(Polyline, superClass);

    function Polyline(args, provider_options) {
      this.args = args;
      this.provider_options = provider_options != null ? provider_options : {};
      this.before_init();
      this.serviceObject = this.create_polyline();
      this.after_init();
    }

    Polyline.prototype.create_polyline = function() {
      return new (this.primitives().polyline)(this.polyline_options());
    };

    Polyline.prototype.polyline_options = function() {
      var base_options;
      base_options = {
        path: this._build_path()
      };
      return _.defaults(base_options, this.provider_options);
    };

    Polyline.prototype._build_path = function() {
      return _.map(this.args, (function(_this) {
        return function(arg) {
          return new (_this.primitives().latLng)(arg.lat, arg.lng);
        };
      })(this));
    };

    return Polyline;

  })(Gmaps.Objects.BaseBuilder);

}).call(this);
(function() {
  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Objects.Bound = (function(superClass) {
    extend(Bound, superClass);

    Bound.include(Gmaps.Google.Objects.Common);

    function Bound(serviceObject) {
      this.serviceObject = serviceObject;
    }

    Bound.prototype.extendWith = function(array_or_object) {
      var collection;
      collection = _.isArray(array_or_object) ? array_or_object : [array_or_object];
      return _.each(collection, (function(_this) {
        return function(object) {
          return object.updateBounds(_this);
        };
      })(this));
    };

    Bound.prototype.extend = function(value) {
      return this.getServiceObject().extend(this.primitives().latLngFromPosition(value));
    };

    return Bound;

  })(Gmaps.Base);

}).call(this);
(function() {
  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Objects.Circle = (function(superClass) {
    extend(Circle, superClass);

    Circle.include(Gmaps.Google.Objects.Common);

    function Circle(serviceObject) {
      this.serviceObject = serviceObject;
    }

    Circle.prototype.updateBounds = function(bounds) {
      bounds.extend(this.getServiceObject().getBounds().getNorthEast());
      return bounds.extend(this.getServiceObject().getBounds().getSouthWest());
    };

    return Circle;

  })(Gmaps.Base);

}).call(this);
(function() {
  this.Gmaps.Google.Objects.Clusterer = (function() {
    function Clusterer(serviceObject) {
      this.serviceObject = serviceObject;
    }

    Clusterer.prototype.addMarkers = function(markers) {
      return _.each(markers, (function(_this) {
        return function(marker) {
          return _this.addMarker(marker);
        };
      })(this));
    };

    Clusterer.prototype.addMarker = function(marker) {
      return this.getServiceObject().addMarker(marker.getServiceObject());
    };

    Clusterer.prototype.clear = function() {
      return this.getServiceObject().clearMarkers();
    };

    Clusterer.prototype.removeMarker = function(marker) {
      return this.getServiceObject().removeMarker(marker.getServiceObject());
    };

    Clusterer.prototype.getServiceObject = function() {
      return this.serviceObject;
    };

    return Clusterer;

  })();

}).call(this);
(function() {
  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Objects.Kml = (function(superClass) {
    extend(Kml, superClass);

    function Kml(serviceObject) {
      this.serviceObject = serviceObject;
    }

    Kml.prototype.updateBounds = function(bounds) {};

    Kml.prototype.setMap = function(map) {
      return this.getServiceObject().setMap(map);
    };

    Kml.prototype.getServiceObject = function() {
      return this.serviceObject;
    };

    Kml.prototype.primitives = function() {
      return this.constructor.PRIMITIVES;
    };

    return Kml;

  })(Gmaps.Base);

}).call(this);
(function() {
  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Objects.Map = (function(superClass) {
    extend(Map, superClass);

    function Map(serviceObject) {
      this.serviceObject = serviceObject;
    }

    Map.prototype.getServiceObject = function() {
      return this.serviceObject;
    };

    Map.prototype.centerOn = function(position) {
      return this.getServiceObject().setCenter(this.primitives().latLngFromPosition(position));
    };

    Map.prototype.fitToBounds = function(boundsObject) {
      if (!boundsObject.isEmpty()) {
        return this.getServiceObject().fitBounds(boundsObject);
      }
    };

    Map.prototype.primitives = function() {
      return this.constructor.PRIMITIVES;
    };

    return Map;

  })(Gmaps.Base);

}).call(this);
(function() {
  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Objects.Marker = (function(superClass) {
    extend(Marker, superClass);

    Marker.include(Gmaps.Google.Objects.Common);

    function Marker(serviceObject) {
      this.serviceObject = serviceObject;
    }

    Marker.prototype.updateBounds = function(bounds) {
      return bounds.extend(this.getServiceObject().position);
    };

    Marker.prototype.panTo = function() {
      return this.getServiceObject().getMap().panTo(this.getServiceObject().getPosition());
    };

    return Marker;

  })(Gmaps.Base);

}).call(this);
(function() {
  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Objects.Polygon = (function(superClass) {
    extend(Polygon, superClass);

    Polygon.include(Gmaps.Google.Objects.Common);

    function Polygon(serviceObject) {
      this.serviceObject = serviceObject;
    }

    Polygon.prototype.updateBounds = function(bounds) {
      var i, len, ll, ref, results;
      ref = this.serviceObject.getPath().getArray();
      results = [];
      for (i = 0, len = ref.length; i < len; i++) {
        ll = ref[i];
        results.push(bounds.extend(ll));
      }
      return results;
    };

    return Polygon;

  })(Gmaps.Base);

}).call(this);
(function() {
  var extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  this.Gmaps.Google.Objects.Polyline = (function(superClass) {
    extend(Polyline, superClass);

    Polyline.include(Gmaps.Google.Objects.Common);

    function Polyline(serviceObject) {
      this.serviceObject = serviceObject;
    }

    Polyline.prototype.updateBounds = function(bounds) {
      var i, len, ll, ref, results;
      ref = this.serviceObject.getPath().getArray();
      results = [];
      for (i = 0, len = ref.length; i < len; i++) {
        ll = ref[i];
        results.push(bounds.extend(ll));
      }
      return results;
    };

    return Polyline;

  })(Gmaps.Base);

}).call(this);
(function() {
  this.Gmaps.Google.Primitives = function() {
    var factory;
    factory = {
      point: google.maps.Point,
      size: google.maps.Size,
      circle: google.maps.Circle,
      latLng: google.maps.LatLng,
      latLngBounds: google.maps.LatLngBounds,
      map: google.maps.Map,
      mapTypez: google.maps.MapTypeId,
      markerImage: google.maps.MarkerImage,
      marker: google.maps.Marker,
      infowindow: google.maps.InfoWindow,
      listener: google.maps.event.addListener,
      clusterer: MarkerClusterer,
      listenerOnce: google.maps.event.addListenerOnce,
      polyline: google.maps.Polyline,
      polygon: google.maps.Polygon,
      kml: google.maps.KmlLayer,
      addListener: function(object, event_name, fn) {
        return factory.listener(object, event_name, fn);
      },
      addListenerOnce: function(object, event_name, fn) {
        return factory.listenerOnce(object, event_name, fn);
      },
      mapTypes: function(type) {
        return factory.mapTypez[type];
      },
      latLngFromPosition: function(position) {
        if (_.isArray(position)) {
          return new factory.latLng(position[0], position[1]);
        } else {
          if (_.isNumber(position.lat) && _.isNumber(position.lng)) {
            return new factory.latLng(position.lat, position.lng);
          } else {
            if (_.isFunction(position.getServiceObject)) {
              return position.getServiceObject().getPosition();
            } else {
              return position;
            }
          }
        }
      }
    };
    return factory;
  };

}).call(this);
(function() {


}).call(this);
( function( factory ) {
	"use strict";

	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [ "jquery" ], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} )( function( $ ) {
"use strict";

$.ui = $.ui || {};

return $.ui.version = "1.13.0";

} );


/*!
 * jQuery UI Keycode 1.13.0
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Keycode
//>>group: Core
//>>description: Provide keycodes as keynames
//>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/

( function( factory ) {
	"use strict";

	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [ "jquery", "./version" ], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} )( function( $ ) {
"use strict";

return $.ui.keyCode = {
	BACKSPACE: 8,
	COMMA: 188,
	DELETE: 46,
	DOWN: 40,
	END: 35,
	ENTER: 13,
	ESCAPE: 27,
	HOME: 36,
	LEFT: 37,
	PAGE_DOWN: 34,
	PAGE_UP: 33,
	PERIOD: 190,
	RIGHT: 39,
	SPACE: 32,
	TAB: 9,
	UP: 38
};

} );


/*!
 * jQuery UI Position 1.13.0
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 *
 * http://api.jqueryui.com/position/
 */

//>>label: Position
//>>group: Core
//>>description: Positions elements relative to other elements.
//>>docs: http://api.jqueryui.com/position/
//>>demos: http://jqueryui.com/position/

( function( factory ) {
	"use strict";

	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [ "jquery", "./version" ], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} )( function( $ ) {
"use strict";

( function() {
var cachedScrollbarWidth,
	max = Math.max,
	abs = Math.abs,
	rhorizontal = /left|center|right/,
	rvertical = /top|center|bottom/,
	roffset = /[\+\-]\d+(\.[\d]+)?%?/,
	rposition = /^\w+/,
	rpercent = /%$/,
	_position = $.fn.position;

function getOffsets( offsets, width, height ) {
	return [
		parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
		parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
	];
}

function parseCss( element, property ) {
	return parseInt( $.css( element, property ), 10 ) || 0;
}

function isWindow( obj ) {
	return obj != null && obj === obj.window;
}

function getDimensions( elem ) {
	var raw = elem[ 0 ];
	if ( raw.nodeType === 9 ) {
		return {
			width: elem.width(),
			height: elem.height(),
			offset: { top: 0, left: 0 }
		};
	}
	if ( isWindow( raw ) ) {
		return {
			width: elem.width(),
			height: elem.height(),
			offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
		};
	}
	if ( raw.preventDefault ) {
		return {
			width: 0,
			height: 0,
			offset: { top: raw.pageY, left: raw.pageX }
		};
	}
	return {
		width: elem.outerWidth(),
		height: elem.outerHeight(),
		offset: elem.offset()
	};
}

$.position = {
	scrollbarWidth: function() {
		if ( cachedScrollbarWidth !== undefined ) {
			return cachedScrollbarWidth;
		}
		var w1, w2,
			div = $( "<div style=" +
				"'display:block;position:absolute;width:200px;height:200px;overflow:hidden;'>" +
				"<div style='height:300px;width:auto;'></div></div>" ),
			innerDiv = div.children()[ 0 ];

		$( "body" ).append( div );
		w1 = innerDiv.offsetWidth;
		div.css( "overflow", "scroll" );

		w2 = innerDiv.offsetWidth;

		if ( w1 === w2 ) {
			w2 = div[ 0 ].clientWidth;
		}

		div.remove();

		return ( cachedScrollbarWidth = w1 - w2 );
	},
	getScrollInfo: function( within ) {
		var overflowX = within.isWindow || within.isDocument ? "" :
				within.element.css( "overflow-x" ),
			overflowY = within.isWindow || within.isDocument ? "" :
				within.element.css( "overflow-y" ),
			hasOverflowX = overflowX === "scroll" ||
				( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ),
			hasOverflowY = overflowY === "scroll" ||
				( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight );
		return {
			width: hasOverflowY ? $.position.scrollbarWidth() : 0,
			height: hasOverflowX ? $.position.scrollbarWidth() : 0
		};
	},
	getWithinInfo: function( element ) {
		var withinElement = $( element || window ),
			isElemWindow = isWindow( withinElement[ 0 ] ),
			isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9,
			hasOffset = !isElemWindow && !isDocument;
		return {
			element: withinElement,
			isWindow: isElemWindow,
			isDocument: isDocument,
			offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 },
			scrollLeft: withinElement.scrollLeft(),
			scrollTop: withinElement.scrollTop(),
			width: withinElement.outerWidth(),
			height: withinElement.outerHeight()
		};
	}
};

$.fn.position = function( options ) {
	if ( !options || !options.of ) {
		return _position.apply( this, arguments );
	}

	// Make a copy, we don't want to modify arguments
	options = $.extend( {}, options );

	var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,

		// Make sure string options are treated as CSS selectors
		target = typeof options.of === "string" ?
			$( document ).find( options.of ) :
			$( options.of ),

		within = $.position.getWithinInfo( options.within ),
		scrollInfo = $.position.getScrollInfo( within ),
		collision = ( options.collision || "flip" ).split( " " ),
		offsets = {};

	dimensions = getDimensions( target );
	if ( target[ 0 ].preventDefault ) {

		// Force left top to allow flipping
		options.at = "left top";
	}
	targetWidth = dimensions.width;
	targetHeight = dimensions.height;
	targetOffset = dimensions.offset;

	// Clone to reuse original targetOffset later
	basePosition = $.extend( {}, targetOffset );

	// Force my and at to have valid horizontal and vertical positions
	// if a value is missing or invalid, it will be converted to center
	$.each( [ "my", "at" ], function() {
		var pos = ( options[ this ] || "" ).split( " " ),
			horizontalOffset,
			verticalOffset;

		if ( pos.length === 1 ) {
			pos = rhorizontal.test( pos[ 0 ] ) ?
				pos.concat( [ "center" ] ) :
				rvertical.test( pos[ 0 ] ) ?
					[ "center" ].concat( pos ) :
					[ "center", "center" ];
		}
		pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
		pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";

		// Calculate offsets
		horizontalOffset = roffset.exec( pos[ 0 ] );
		verticalOffset = roffset.exec( pos[ 1 ] );
		offsets[ this ] = [
			horizontalOffset ? horizontalOffset[ 0 ] : 0,
			verticalOffset ? verticalOffset[ 0 ] : 0
		];

		// Reduce to just the positions without the offsets
		options[ this ] = [
			rposition.exec( pos[ 0 ] )[ 0 ],
			rposition.exec( pos[ 1 ] )[ 0 ]
		];
	} );

	// Normalize collision option
	if ( collision.length === 1 ) {
		collision[ 1 ] = collision[ 0 ];
	}

	if ( options.at[ 0 ] === "right" ) {
		basePosition.left += targetWidth;
	} else if ( options.at[ 0 ] === "center" ) {
		basePosition.left += targetWidth / 2;
	}

	if ( options.at[ 1 ] === "bottom" ) {
		basePosition.top += targetHeight;
	} else if ( options.at[ 1 ] === "center" ) {
		basePosition.top += targetHeight / 2;
	}

	atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
	basePosition.left += atOffset[ 0 ];
	basePosition.top += atOffset[ 1 ];

	return this.each( function() {
		var collisionPosition, using,
			elem = $( this ),
			elemWidth = elem.outerWidth(),
			elemHeight = elem.outerHeight(),
			marginLeft = parseCss( this, "marginLeft" ),
			marginTop = parseCss( this, "marginTop" ),
			collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) +
				scrollInfo.width,
			collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) +
				scrollInfo.height,
			position = $.extend( {}, basePosition ),
			myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );

		if ( options.my[ 0 ] === "right" ) {
			position.left -= elemWidth;
		} else if ( options.my[ 0 ] === "center" ) {
			position.left -= elemWidth / 2;
		}

		if ( options.my[ 1 ] === "bottom" ) {
			position.top -= elemHeight;
		} else if ( options.my[ 1 ] === "center" ) {
			position.top -= elemHeight / 2;
		}

		position.left += myOffset[ 0 ];
		position.top += myOffset[ 1 ];

		collisionPosition = {
			marginLeft: marginLeft,
			marginTop: marginTop
		};

		$.each( [ "left", "top" ], function( i, dir ) {
			if ( $.ui.position[ collision[ i ] ] ) {
				$.ui.position[ collision[ i ] ][ dir ]( position, {
					targetWidth: targetWidth,
					targetHeight: targetHeight,
					elemWidth: elemWidth,
					elemHeight: elemHeight,
					collisionPosition: collisionPosition,
					collisionWidth: collisionWidth,
					collisionHeight: collisionHeight,
					offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
					my: options.my,
					at: options.at,
					within: within,
					elem: elem
				} );
			}
		} );

		if ( options.using ) {

			// Adds feedback as second argument to using callback, if present
			using = function( props ) {
				var left = targetOffset.left - position.left,
					right = left + targetWidth - elemWidth,
					top = targetOffset.top - position.top,
					bottom = top + targetHeight - elemHeight,
					feedback = {
						target: {
							element: target,
							left: targetOffset.left,
							top: targetOffset.top,
							width: targetWidth,
							height: targetHeight
						},
						element: {
							element: elem,
							left: position.left,
							top: position.top,
							width: elemWidth,
							height: elemHeight
						},
						horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
						vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
					};
				if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
					feedback.horizontal = "center";
				}
				if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
					feedback.vertical = "middle";
				}
				if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
					feedback.important = "horizontal";
				} else {
					feedback.important = "vertical";
				}
				options.using.call( this, props, feedback );
			};
		}

		elem.offset( $.extend( position, { using: using } ) );
	} );
};

$.ui.position = {
	fit: {
		left: function( position, data ) {
			var within = data.within,
				withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
				outerWidth = within.width,
				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
				overLeft = withinOffset - collisionPosLeft,
				overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
				newOverRight;

			// Element is wider than within
			if ( data.collisionWidth > outerWidth ) {

				// Element is initially over the left side of within
				if ( overLeft > 0 && overRight <= 0 ) {
					newOverRight = position.left + overLeft + data.collisionWidth - outerWidth -
						withinOffset;
					position.left += overLeft - newOverRight;

				// Element is initially over right side of within
				} else if ( overRight > 0 && overLeft <= 0 ) {
					position.left = withinOffset;

				// Element is initially over both left and right sides of within
				} else {
					if ( overLeft > overRight ) {
						position.left = withinOffset + outerWidth - data.collisionWidth;
					} else {
						position.left = withinOffset;
					}
				}

			// Too far left -> align with left edge
			} else if ( overLeft > 0 ) {
				position.left += overLeft;

			// Too far right -> align with right edge
			} else if ( overRight > 0 ) {
				position.left -= overRight;

			// Adjust based on position and margin
			} else {
				position.left = max( position.left - collisionPosLeft, position.left );
			}
		},
		top: function( position, data ) {
			var within = data.within,
				withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
				outerHeight = data.within.height,
				collisionPosTop = position.top - data.collisionPosition.marginTop,
				overTop = withinOffset - collisionPosTop,
				overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
				newOverBottom;

			// Element is taller than within
			if ( data.collisionHeight > outerHeight ) {

				// Element is initially over the top of within
				if ( overTop > 0 && overBottom <= 0 ) {
					newOverBottom = position.top + overTop + data.collisionHeight - outerHeight -
						withinOffset;
					position.top += overTop - newOverBottom;

				// Element is initially over bottom of within
				} else if ( overBottom > 0 && overTop <= 0 ) {
					position.top = withinOffset;

				// Element is initially over both top and bottom of within
				} else {
					if ( overTop > overBottom ) {
						position.top = withinOffset + outerHeight - data.collisionHeight;
					} else {
						position.top = withinOffset;
					}
				}

			// Too far up -> align with top
			} else if ( overTop > 0 ) {
				position.top += overTop;

			// Too far down -> align with bottom edge
			} else if ( overBottom > 0 ) {
				position.top -= overBottom;

			// Adjust based on position and margin
			} else {
				position.top = max( position.top - collisionPosTop, position.top );
			}
		}
	},
	flip: {
		left: function( position, data ) {
			var within = data.within,
				withinOffset = within.offset.left + within.scrollLeft,
				outerWidth = within.width,
				offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
				overLeft = collisionPosLeft - offsetLeft,
				overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
				myOffset = data.my[ 0 ] === "left" ?
					-data.elemWidth :
					data.my[ 0 ] === "right" ?
						data.elemWidth :
						0,
				atOffset = data.at[ 0 ] === "left" ?
					data.targetWidth :
					data.at[ 0 ] === "right" ?
						-data.targetWidth :
						0,
				offset = -2 * data.offset[ 0 ],
				newOverRight,
				newOverLeft;

			if ( overLeft < 0 ) {
				newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth -
					outerWidth - withinOffset;
				if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
					position.left += myOffset + atOffset + offset;
				}
			} else if ( overRight > 0 ) {
				newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset +
					atOffset + offset - offsetLeft;
				if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
					position.left += myOffset + atOffset + offset;
				}
			}
		},
		top: function( position, data ) {
			var within = data.within,
				withinOffset = within.offset.top + within.scrollTop,
				outerHeight = within.height,
				offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
				collisionPosTop = position.top - data.collisionPosition.marginTop,
				overTop = collisionPosTop - offsetTop,
				overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
				top = data.my[ 1 ] === "top",
				myOffset = top ?
					-data.elemHeight :
					data.my[ 1 ] === "bottom" ?
						data.elemHeight :
						0,
				atOffset = data.at[ 1 ] === "top" ?
					data.targetHeight :
					data.at[ 1 ] === "bottom" ?
						-data.targetHeight :
						0,
				offset = -2 * data.offset[ 1 ],
				newOverTop,
				newOverBottom;
			if ( overTop < 0 ) {
				newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight -
					outerHeight - withinOffset;
				if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) {
					position.top += myOffset + atOffset + offset;
				}
			} else if ( overBottom > 0 ) {
				newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset +
					offset - offsetTop;
				if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) {
					position.top += myOffset + atOffset + offset;
				}
			}
		}
	},
	flipfit: {
		left: function() {
			$.ui.position.flip.left.apply( this, arguments );
			$.ui.position.fit.left.apply( this, arguments );
		},
		top: function() {
			$.ui.position.flip.top.apply( this, arguments );
			$.ui.position.fit.top.apply( this, arguments );
		}
	}
};

} )();

return $.ui.position;

} );

( function( factory ) {
	"use strict";

	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [ "jquery", "./version" ], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} )( function( $ ) {
"use strict";

return $.ui.safeActiveElement = function( document ) {
	var activeElement;

	// Support: IE 9 only
	// IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
	try {
		activeElement = document.activeElement;
	} catch ( error ) {
		activeElement = document.body;
	}

	// Support: IE 9 - 11 only
	// IE may return null instead of an element
	// Interestingly, this only seems to occur when NOT in an iframe
	if ( !activeElement ) {
		activeElement = document.body;
	}

	// Support: IE 11 only
	// IE11 returns a seemingly empty object in some cases when accessing
	// document.activeElement from an <iframe>
	if ( !activeElement.nodeName ) {
		activeElement = document.body;
	}

	return activeElement;
};

} );


/*!
 * jQuery UI Unique ID 1.13.0
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: uniqueId
//>>group: Core
//>>description: Functions to generate and remove uniqueId's
//>>docs: http://api.jqueryui.com/uniqueId/

( function( factory ) {
	"use strict";

	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [ "jquery", "./version" ], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} )( function( $ ) {
"use strict";

return $.fn.extend( {
	uniqueId: ( function() {
		var uuid = 0;

		return function() {
			return this.each( function() {
				if ( !this.id ) {
					this.id = "ui-id-" + ( ++uuid );
				}
			} );
		};
	} )(),

	removeUniqueId: function() {
		return this.each( function() {
			if ( /^ui-id-\d+$/.test( this.id ) ) {
				$( this ).removeAttr( "id" );
			}
		} );
	}
} );

} );


/*!
 * jQuery UI Widget 1.13.0
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Widget
//>>group: Core
//>>description: Provides a factory for creating stateful widgets with a common API.
//>>docs: http://api.jqueryui.com/jQuery.widget/
//>>demos: http://jqueryui.com/widget/

( function( factory ) {
	"use strict";

	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [ "jquery", "./version" ], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} )( function( $ ) {
"use strict";

var widgetUuid = 0;
var widgetHasOwnProperty = Array.prototype.hasOwnProperty;
var widgetSlice = Array.prototype.slice;

$.cleanData = ( function( orig ) {
	return function( elems ) {
		var events, elem, i;
		for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) {

			// Only trigger remove when necessary to save time
			events = $._data( elem, "events" );
			if ( events && events.remove ) {
				$( elem ).triggerHandler( "remove" );
			}
		}
		orig( elems );
	};
} )( $.cleanData );

$.widget = function( name, base, prototype ) {
	var existingConstructor, constructor, basePrototype;

	// ProxiedPrototype allows the provided prototype to remain unmodified
	// so that it can be used as a mixin for multiple widgets (#8876)
	var proxiedPrototype = {};

	var namespace = name.split( "." )[ 0 ];
	name = name.split( "." )[ 1 ];
	var fullName = namespace + "-" + name;

	if ( !prototype ) {
		prototype = base;
		base = $.Widget;
	}

	if ( Array.isArray( prototype ) ) {
		prototype = $.extend.apply( null, [ {} ].concat( prototype ) );
	}

	// Create selector for plugin
	$.expr.pseudos[ fullName.toLowerCase() ] = function( elem ) {
		return !!$.data( elem, fullName );
	};

	$[ namespace ] = $[ namespace ] || {};
	existingConstructor = $[ namespace ][ name ];
	constructor = $[ namespace ][ name ] = function( options, element ) {

		// Allow instantiation without "new" keyword
		if ( !this._createWidget ) {
			return new constructor( options, element );
		}

		// Allow instantiation without initializing for simple inheritance
		// must use "new" keyword (the code above always passes args)
		if ( arguments.length ) {
			this._createWidget( options, element );
		}
	};

	// Extend with the existing constructor to carry over any static properties
	$.extend( constructor, existingConstructor, {
		version: prototype.version,

		// Copy the object used to create the prototype in case we need to
		// redefine the widget later
		_proto: $.extend( {}, prototype ),

		// Track widgets that inherit from this widget in case this widget is
		// redefined after a widget inherits from it
		_childConstructors: []
	} );

	basePrototype = new base();

	// We need to make the options hash a property directly on the new instance
	// otherwise we'll modify the options hash on the prototype that we're
	// inheriting from
	basePrototype.options = $.widget.extend( {}, basePrototype.options );
	$.each( prototype, function( prop, value ) {
		if ( typeof value !== "function" ) {
			proxiedPrototype[ prop ] = value;
			return;
		}
		proxiedPrototype[ prop ] = ( function() {
			function _super() {
				return base.prototype[ prop ].apply( this, arguments );
			}

			function _superApply( args ) {
				return base.prototype[ prop ].apply( this, args );
			}

			return function() {
				var __super = this._super;
				var __superApply = this._superApply;
				var returnValue;

				this._super = _super;
				this._superApply = _superApply;

				returnValue = value.apply( this, arguments );

				this._super = __super;
				this._superApply = __superApply;

				return returnValue;
			};
		} )();
	} );
	constructor.prototype = $.widget.extend( basePrototype, {

		// TODO: remove support for widgetEventPrefix
		// always use the name + a colon as the prefix, e.g., draggable:start
		// don't prefix for widgets that aren't DOM-based
		widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name
	}, proxiedPrototype, {
		constructor: constructor,
		namespace: namespace,
		widgetName: name,
		widgetFullName: fullName
	} );

	// If this widget is being redefined then we need to find all widgets that
	// are inheriting from it and redefine all of them so that they inherit from
	// the new version of this widget. We're essentially trying to replace one
	// level in the prototype chain.
	if ( existingConstructor ) {
		$.each( existingConstructor._childConstructors, function( i, child ) {
			var childPrototype = child.prototype;

			// Redefine the child widget using the same prototype that was
			// originally used, but inherit from the new version of the base
			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor,
				child._proto );
		} );

		// Remove the list of existing child constructors from the old constructor
		// so the old child constructors can be garbage collected
		delete existingConstructor._childConstructors;
	} else {
		base._childConstructors.push( constructor );
	}

	$.widget.bridge( name, constructor );

	return constructor;
};

$.widget.extend = function( target ) {
	var input = widgetSlice.call( arguments, 1 );
	var inputIndex = 0;
	var inputLength = input.length;
	var key;
	var value;

	for ( ; inputIndex < inputLength; inputIndex++ ) {
		for ( key in input[ inputIndex ] ) {
			value = input[ inputIndex ][ key ];
			if ( widgetHasOwnProperty.call( input[ inputIndex ], key ) && value !== undefined ) {

				// Clone objects
				if ( $.isPlainObject( value ) ) {
					target[ key ] = $.isPlainObject( target[ key ] ) ?
						$.widget.extend( {}, target[ key ], value ) :

						// Don't extend strings, arrays, etc. with objects
						$.widget.extend( {}, value );

				// Copy everything else by reference
				} else {
					target[ key ] = value;
				}
			}
		}
	}
	return target;
};

$.widget.bridge = function( name, object ) {
	var fullName = object.prototype.widgetFullName || name;
	$.fn[ name ] = function( options ) {
		var isMethodCall = typeof options === "string";
		var args = widgetSlice.call( arguments, 1 );
		var returnValue = this;

		if ( isMethodCall ) {

			// If this is an empty collection, we need to have the instance method
			// return undefined instead of the jQuery instance
			if ( !this.length && options === "instance" ) {
				returnValue = undefined;
			} else {
				this.each( function() {
					var methodValue;
					var instance = $.data( this, fullName );

					if ( options === "instance" ) {
						returnValue = instance;
						return false;
					}

					if ( !instance ) {
						return $.error( "cannot call methods on " + name +
							" prior to initialization; " +
							"attempted to call method '" + options + "'" );
					}

					if ( typeof instance[ options ] !== "function" ||
						options.charAt( 0 ) === "_" ) {
						return $.error( "no such method '" + options + "' for " + name +
							" widget instance" );
					}

					methodValue = instance[ options ].apply( instance, args );

					if ( methodValue !== instance && methodValue !== undefined ) {
						returnValue = methodValue && methodValue.jquery ?
							returnValue.pushStack( methodValue.get() ) :
							methodValue;
						return false;
					}
				} );
			}
		} else {

			// Allow multiple hashes to be passed on init
			if ( args.length ) {
				options = $.widget.extend.apply( null, [ options ].concat( args ) );
			}

			this.each( function() {
				var instance = $.data( this, fullName );
				if ( instance ) {
					instance.option( options || {} );
					if ( instance._init ) {
						instance._init();
					}
				} else {
					$.data( this, fullName, new object( options, this ) );
				}
			} );
		}

		return returnValue;
	};
};

$.Widget = function( /* options, element */ ) {};
$.Widget._childConstructors = [];

$.Widget.prototype = {
	widgetName: "widget",
	widgetEventPrefix: "",
	defaultElement: "<div>",

	options: {
		classes: {},
		disabled: false,

		// Callbacks
		create: null
	},

	_createWidget: function( options, element ) {
		element = $( element || this.defaultElement || this )[ 0 ];
		this.element = $( element );
		this.uuid = widgetUuid++;
		this.eventNamespace = "." + this.widgetName + this.uuid;

		this.bindings = $();
		this.hoverable = $();
		this.focusable = $();
		this.classesElementLookup = {};

		if ( element !== this ) {
			$.data( element, this.widgetFullName, this );
			this._on( true, this.element, {
				remove: function( event ) {
					if ( event.target === element ) {
						this.destroy();
					}
				}
			} );
			this.document = $( element.style ?

				// Element within the document
				element.ownerDocument :

				// Element is window or document
				element.document || element );
			this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow );
		}

		this.options = $.widget.extend( {},
			this.options,
			this._getCreateOptions(),
			options );

		this._create();

		if ( this.options.disabled ) {
			this._setOptionDisabled( this.options.disabled );
		}

		this._trigger( "create", null, this._getCreateEventData() );
		this._init();
	},

	_getCreateOptions: function() {
		return {};
	},

	_getCreateEventData: $.noop,

	_create: $.noop,

	_init: $.noop,

	destroy: function() {
		var that = this;

		this._destroy();
		$.each( this.classesElementLookup, function( key, value ) {
			that._removeClass( value, key );
		} );

		// We can probably remove the unbind calls in 2.0
		// all event bindings should go through this._on()
		this.element
			.off( this.eventNamespace )
			.removeData( this.widgetFullName );
		this.widget()
			.off( this.eventNamespace )
			.removeAttr( "aria-disabled" );

		// Clean up events and states
		this.bindings.off( this.eventNamespace );
	},

	_destroy: $.noop,

	widget: function() {
		return this.element;
	},

	option: function( key, value ) {
		var options = key;
		var parts;
		var curOption;
		var i;

		if ( arguments.length === 0 ) {

			// Don't return a reference to the internal hash
			return $.widget.extend( {}, this.options );
		}

		if ( typeof key === "string" ) {

			// Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
			options = {};
			parts = key.split( "." );
			key = parts.shift();
			if ( parts.length ) {
				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
				for ( i = 0; i < parts.length - 1; i++ ) {
					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
					curOption = curOption[ parts[ i ] ];
				}
				key = parts.pop();
				if ( arguments.length === 1 ) {
					return curOption[ key ] === undefined ? null : curOption[ key ];
				}
				curOption[ key ] = value;
			} else {
				if ( arguments.length === 1 ) {
					return this.options[ key ] === undefined ? null : this.options[ key ];
				}
				options[ key ] = value;
			}
		}

		this._setOptions( options );

		return this;
	},

	_setOptions: function( options ) {
		var key;

		for ( key in options ) {
			this._setOption( key, options[ key ] );
		}

		return this;
	},

	_setOption: function( key, value ) {
		if ( key === "classes" ) {
			this._setOptionClasses( value );
		}

		this.options[ key ] = value;

		if ( key === "disabled" ) {
			this._setOptionDisabled( value );
		}

		return this;
	},

	_setOptionClasses: function( value ) {
		var classKey, elements, currentElements;

		for ( classKey in value ) {
			currentElements = this.classesElementLookup[ classKey ];
			if ( value[ classKey ] === this.options.classes[ classKey ] ||
					!currentElements ||
					!currentElements.length ) {
				continue;
			}

			// We are doing this to create a new jQuery object because the _removeClass() call
			// on the next line is going to destroy the reference to the current elements being
			// tracked. We need to save a copy of this collection so that we can add the new classes
			// below.
			elements = $( currentElements.get() );
			this._removeClass( currentElements, classKey );

			// We don't use _addClass() here, because that uses this.options.classes
			// for generating the string of classes. We want to use the value passed in from
			// _setOption(), this is the new value of the classes option which was passed to
			// _setOption(). We pass this value directly to _classes().
			elements.addClass( this._classes( {
				element: elements,
				keys: classKey,
				classes: value,
				add: true
			} ) );
		}
	},

	_setOptionDisabled: function( value ) {
		this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value );

		// If the widget is becoming disabled, then nothing is interactive
		if ( value ) {
			this._removeClass( this.hoverable, null, "ui-state-hover" );
			this._removeClass( this.focusable, null, "ui-state-focus" );
		}
	},

	enable: function() {
		return this._setOptions( { disabled: false } );
	},

	disable: function() {
		return this._setOptions( { disabled: true } );
	},

	_classes: function( options ) {
		var full = [];
		var that = this;

		options = $.extend( {
			element: this.element,
			classes: this.options.classes || {}
		}, options );

		function bindRemoveEvent() {
			options.element.each( function( _, element ) {
				var isTracked = $.map( that.classesElementLookup, function( elements ) {
					return elements;
				} )
					.some( function( elements ) {
						return elements.is( element );
					} );

				if ( !isTracked ) {
					that._on( $( element ), {
						remove: "_untrackClassesElement"
					} );
				}
			} );
		}

		function processClassString( classes, checkOption ) {
			var current, i;
			for ( i = 0; i < classes.length; i++ ) {
				current = that.classesElementLookup[ classes[ i ] ] || $();
				if ( options.add ) {
					bindRemoveEvent();
					current = $( $.uniqueSort( current.get().concat( options.element.get() ) ) );
				} else {
					current = $( current.not( options.element ).get() );
				}
				that.classesElementLookup[ classes[ i ] ] = current;
				full.push( classes[ i ] );
				if ( checkOption && options.classes[ classes[ i ] ] ) {
					full.push( options.classes[ classes[ i ] ] );
				}
			}
		}

		if ( options.keys ) {
			processClassString( options.keys.match( /\S+/g ) || [], true );
		}
		if ( options.extra ) {
			processClassString( options.extra.match( /\S+/g ) || [] );
		}

		return full.join( " " );
	},

	_untrackClassesElement: function( event ) {
		var that = this;
		$.each( that.classesElementLookup, function( key, value ) {
			if ( $.inArray( event.target, value ) !== -1 ) {
				that.classesElementLookup[ key ] = $( value.not( event.target ).get() );
			}
		} );

		this._off( $( event.target ) );
	},

	_removeClass: function( element, keys, extra ) {
		return this._toggleClass( element, keys, extra, false );
	},

	_addClass: function( element, keys, extra ) {
		return this._toggleClass( element, keys, extra, true );
	},

	_toggleClass: function( element, keys, extra, add ) {
		add = ( typeof add === "boolean" ) ? add : extra;
		var shift = ( typeof element === "string" || element === null ),
			options = {
				extra: shift ? keys : extra,
				keys: shift ? element : keys,
				element: shift ? this.element : element,
				add: add
			};
		options.element.toggleClass( this._classes( options ), add );
		return this;
	},

	_on: function( suppressDisabledCheck, element, handlers ) {
		var delegateElement;
		var instance = this;

		// No suppressDisabledCheck flag, shuffle arguments
		if ( typeof suppressDisabledCheck !== "boolean" ) {
			handlers = element;
			element = suppressDisabledCheck;
			suppressDisabledCheck = false;
		}

		// No element argument, shuffle and use this.element
		if ( !handlers ) {
			handlers = element;
			element = this.element;
			delegateElement = this.widget();
		} else {
			element = delegateElement = $( element );
			this.bindings = this.bindings.add( element );
		}

		$.each( handlers, function( event, handler ) {
			function handlerProxy() {

				// Allow widgets to customize the disabled handling
				// - disabled as an array instead of boolean
				// - disabled class as method for disabling individual parts
				if ( !suppressDisabledCheck &&
						( instance.options.disabled === true ||
						$( this ).hasClass( "ui-state-disabled" ) ) ) {
					return;
				}
				return ( typeof handler === "string" ? instance[ handler ] : handler )
					.apply( instance, arguments );
			}

			// Copy the guid so direct unbinding works
			if ( typeof handler !== "string" ) {
				handlerProxy.guid = handler.guid =
					handler.guid || handlerProxy.guid || $.guid++;
			}

			var match = event.match( /^([\w:-]*)\s*(.*)$/ );
			var eventName = match[ 1 ] + instance.eventNamespace;
			var selector = match[ 2 ];

			if ( selector ) {
				delegateElement.on( eventName, selector, handlerProxy );
			} else {
				element.on( eventName, handlerProxy );
			}
		} );
	},

	_off: function( element, eventName ) {
		eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) +
			this.eventNamespace;
		element.off( eventName );

		// Clear the stack to avoid memory leaks (#10056)
		this.bindings = $( this.bindings.not( element ).get() );
		this.focusable = $( this.focusable.not( element ).get() );
		this.hoverable = $( this.hoverable.not( element ).get() );
	},

	_delay: function( handler, delay ) {
		function handlerProxy() {
			return ( typeof handler === "string" ? instance[ handler ] : handler )
				.apply( instance, arguments );
		}
		var instance = this;
		return setTimeout( handlerProxy, delay || 0 );
	},

	_hoverable: function( element ) {
		this.hoverable = this.hoverable.add( element );
		this._on( element, {
			mouseenter: function( event ) {
				this._addClass( $( event.currentTarget ), null, "ui-state-hover" );
			},
			mouseleave: function( event ) {
				this._removeClass( $( event.currentTarget ), null, "ui-state-hover" );
			}
		} );
	},

	_focusable: function( element ) {
		this.focusable = this.focusable.add( element );
		this._on( element, {
			focusin: function( event ) {
				this._addClass( $( event.currentTarget ), null, "ui-state-focus" );
			},
			focusout: function( event ) {
				this._removeClass( $( event.currentTarget ), null, "ui-state-focus" );
			}
		} );
	},

	_trigger: function( type, event, data ) {
		var prop, orig;
		var callback = this.options[ type ];

		data = data || {};
		event = $.Event( event );
		event.type = ( type === this.widgetEventPrefix ?
			type :
			this.widgetEventPrefix + type ).toLowerCase();

		// The original event may come from any element
		// so we need to reset the target on the new event
		event.target = this.element[ 0 ];

		// Copy original event properties over to the new event
		orig = event.originalEvent;
		if ( orig ) {
			for ( prop in orig ) {
				if ( !( prop in event ) ) {
					event[ prop ] = orig[ prop ];
				}
			}
		}

		this.element.trigger( event, data );
		return !( typeof callback === "function" &&
			callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false ||
			event.isDefaultPrevented() );
	}
};

$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
		if ( typeof options === "string" ) {
			options = { effect: options };
		}

		var hasOptions;
		var effectName = !options ?
			method :
			options === true || typeof options === "number" ?
				defaultEffect :
				options.effect || defaultEffect;

		options = options || {};
		if ( typeof options === "number" ) {
			options = { duration: options };
		} else if ( options === true ) {
			options = {};
		}

		hasOptions = !$.isEmptyObject( options );
		options.complete = callback;

		if ( options.delay ) {
			element.delay( options.delay );
		}

		if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
			element[ method ]( options );
		} else if ( effectName !== method && element[ effectName ] ) {
			element[ effectName ]( options.duration, options.easing, callback );
		} else {
			element.queue( function( next ) {
				$( this )[ method ]();
				if ( callback ) {
					callback.call( element[ 0 ] );
				}
				next();
			} );
		}
	};
} );

return $.widget;

} );







/*!
 * jQuery UI Menu 1.13.0
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Menu
//>>group: Widgets
//>>description: Creates nestable menus.
//>>docs: http://api.jqueryui.com/menu/
//>>demos: http://jqueryui.com/menu/
//>>css.structure: ../../themes/base/core.css
//>>css.structure: ../../themes/base/menu.css
//>>css.theme: ../../themes/base/theme.css

( function( factory ) {
	"use strict";

	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [
			"jquery",
			"../keycode",
			"../position",
			"../safe-active-element",
			"../unique-id",
			"../version",
			"../widget"
		], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} )( function( $ ) {
"use strict";

return $.widget( "ui.menu", {
	version: "1.13.0",
	defaultElement: "<ul>",
	delay: 300,
	options: {
		icons: {
			submenu: "ui-icon-caret-1-e"
		},
		items: "> *",
		menus: "ul",
		position: {
			my: "left top",
			at: "right top"
		},
		role: "menu",

		// Callbacks
		blur: null,
		focus: null,
		select: null
	},

	_create: function() {
		this.activeMenu = this.element;

		// Flag used to prevent firing of the click handler
		// as the event bubbles up through nested menus
		this.mouseHandled = false;
		this.lastMousePosition = { x: null, y: null };
		this.element
			.uniqueId()
			.attr( {
				role: this.options.role,
				tabIndex: 0
			} );

		this._addClass( "ui-menu", "ui-widget ui-widget-content" );
		this._on( {

			// Prevent focus from sticking to links inside menu after clicking
			// them (focus should always stay on UL during navigation).
			"mousedown .ui-menu-item": function( event ) {
				event.preventDefault();

				this._activateItem( event );
			},
			"click .ui-menu-item": function( event ) {
				var target = $( event.target );
				var active = $( $.ui.safeActiveElement( this.document[ 0 ] ) );
				if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) {
					this.select( event );

					// Only set the mouseHandled flag if the event will bubble, see #9469.
					if ( !event.isPropagationStopped() ) {
						this.mouseHandled = true;
					}

					// Open submenu on click
					if ( target.has( ".ui-menu" ).length ) {
						this.expand( event );
					} else if ( !this.element.is( ":focus" ) &&
							active.closest( ".ui-menu" ).length ) {

						// Redirect focus to the menu
						this.element.trigger( "focus", [ true ] );

						// If the active item is on the top level, let it stay active.
						// Otherwise, blur the active item since it is no longer visible.
						if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
							clearTimeout( this.timer );
						}
					}
				}
			},
			"mouseenter .ui-menu-item": "_activateItem",
			"mousemove .ui-menu-item": "_activateItem",
			mouseleave: "collapseAll",
			"mouseleave .ui-menu": "collapseAll",
			focus: function( event, keepActiveItem ) {

				// If there's already an active item, keep it active
				// If not, activate the first item
				var item = this.active || this._menuItems().first();

				if ( !keepActiveItem ) {
					this.focus( event, item );
				}
			},
			blur: function( event ) {
				this._delay( function() {
					var notContained = !$.contains(
						this.element[ 0 ],
						$.ui.safeActiveElement( this.document[ 0 ] )
					);
					if ( notContained ) {
						this.collapseAll( event );
					}
				} );
			},
			keydown: "_keydown"
		} );

		this.refresh();

		// Clicks outside of a menu collapse any open menus
		this._on( this.document, {
			click: function( event ) {
				if ( this._closeOnDocumentClick( event ) ) {
					this.collapseAll( event, true );
				}

				// Reset the mouseHandled flag
				this.mouseHandled = false;
			}
		} );
	},

	_activateItem: function( event ) {

		// Ignore mouse events while typeahead is active, see #10458.
		// Prevents focusing the wrong item when typeahead causes a scroll while the mouse
		// is over an item in the menu
		if ( this.previousFilter ) {
			return;
		}

		// If the mouse didn't actually move, but the page was scrolled, ignore the event (#9356)
		if ( event.clientX === this.lastMousePosition.x &&
				event.clientY === this.lastMousePosition.y ) {
			return;
		}

		this.lastMousePosition = {
			x: event.clientX,
			y: event.clientY
		};

		var actualTarget = $( event.target ).closest( ".ui-menu-item" ),
			target = $( event.currentTarget );

		// Ignore bubbled events on parent items, see #11641
		if ( actualTarget[ 0 ] !== target[ 0 ] ) {
			return;
		}

		// If the item is already active, there's nothing to do
		if ( target.is( ".ui-state-active" ) ) {
			return;
		}

		// Remove ui-state-active class from siblings of the newly focused menu item
		// to avoid a jump caused by adjacent elements both having a class with a border
		this._removeClass( target.siblings().children( ".ui-state-active" ),
			null, "ui-state-active" );
		this.focus( event, target );
	},

	_destroy: function() {
		var items = this.element.find( ".ui-menu-item" )
				.removeAttr( "role aria-disabled" ),
			submenus = items.children( ".ui-menu-item-wrapper" )
				.removeUniqueId()
				.removeAttr( "tabIndex role aria-haspopup" );

		// Destroy (sub)menus
		this.element
			.removeAttr( "aria-activedescendant" )
			.find( ".ui-menu" ).addBack()
				.removeAttr( "role aria-labelledby aria-expanded aria-hidden aria-disabled " +
					"tabIndex" )
				.removeUniqueId()
				.show();

		submenus.children().each( function() {
			var elem = $( this );
			if ( elem.data( "ui-menu-submenu-caret" ) ) {
				elem.remove();
			}
		} );
	},

	_keydown: function( event ) {
		var match, prev, character, skip,
			preventDefault = true;

		switch ( event.keyCode ) {
		case $.ui.keyCode.PAGE_UP:
			this.previousPage( event );
			break;
		case $.ui.keyCode.PAGE_DOWN:
			this.nextPage( event );
			break;
		case $.ui.keyCode.HOME:
			this._move( "first", "first", event );
			break;
		case $.ui.keyCode.END:
			this._move( "last", "last", event );
			break;
		case $.ui.keyCode.UP:
			this.previous( event );
			break;
		case $.ui.keyCode.DOWN:
			this.next( event );
			break;
		case $.ui.keyCode.LEFT:
			this.collapse( event );
			break;
		case $.ui.keyCode.RIGHT:
			if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
				this.expand( event );
			}
			break;
		case $.ui.keyCode.ENTER:
		case $.ui.keyCode.SPACE:
			this._activate( event );
			break;
		case $.ui.keyCode.ESCAPE:
			this.collapse( event );
			break;
		default:
			preventDefault = false;
			prev = this.previousFilter || "";
			skip = false;

			// Support number pad values
			character = event.keyCode >= 96 && event.keyCode <= 105 ?
				( event.keyCode - 96 ).toString() : String.fromCharCode( event.keyCode );

			clearTimeout( this.filterTimer );

			if ( character === prev ) {
				skip = true;
			} else {
				character = prev + character;
			}

			match = this._filterMenuItems( character );
			match = skip && match.index( this.active.next() ) !== -1 ?
				this.active.nextAll( ".ui-menu-item" ) :
				match;

			// If no matches on the current filter, reset to the last character pressed
			// to move down the menu to the first item that starts with that character
			if ( !match.length ) {
				character = String.fromCharCode( event.keyCode );
				match = this._filterMenuItems( character );
			}

			if ( match.length ) {
				this.focus( event, match );
				this.previousFilter = character;
				this.filterTimer = this._delay( function() {
					delete this.previousFilter;
				}, 1000 );
			} else {
				delete this.previousFilter;
			}
		}

		if ( preventDefault ) {
			event.preventDefault();
		}
	},

	_activate: function( event ) {
		if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
			if ( this.active.children( "[aria-haspopup='true']" ).length ) {
				this.expand( event );
			} else {
				this.select( event );
			}
		}
	},

	refresh: function() {
		var menus, items, newSubmenus, newItems, newWrappers,
			that = this,
			icon = this.options.icons.submenu,
			submenus = this.element.find( this.options.menus );

		this._toggleClass( "ui-menu-icons", null, !!this.element.find( ".ui-icon" ).length );

		// Initialize nested menus
		newSubmenus = submenus.filter( ":not(.ui-menu)" )
			.hide()
			.attr( {
				role: this.options.role,
				"aria-hidden": "true",
				"aria-expanded": "false"
			} )
			.each( function() {
				var menu = $( this ),
					item = menu.prev(),
					submenuCaret = $( "<span>" ).data( "ui-menu-submenu-caret", true );

				that._addClass( submenuCaret, "ui-menu-icon", "ui-icon " + icon );
				item
					.attr( "aria-haspopup", "true" )
					.prepend( submenuCaret );
				menu.attr( "aria-labelledby", item.attr( "id" ) );
			} );

		this._addClass( newSubmenus, "ui-menu", "ui-widget ui-widget-content ui-front" );

		menus = submenus.add( this.element );
		items = menus.find( this.options.items );

		// Initialize menu-items containing spaces and/or dashes only as dividers
		items.not( ".ui-menu-item" ).each( function() {
			var item = $( this );
			if ( that._isDivider( item ) ) {
				that._addClass( item, "ui-menu-divider", "ui-widget-content" );
			}
		} );

		// Don't refresh list items that are already adapted
		newItems = items.not( ".ui-menu-item, .ui-menu-divider" );
		newWrappers = newItems.children()
			.not( ".ui-menu" )
				.uniqueId()
				.attr( {
					tabIndex: -1,
					role: this._itemRole()
				} );
		this._addClass( newItems, "ui-menu-item" )
			._addClass( newWrappers, "ui-menu-item-wrapper" );

		// Add aria-disabled attribute to any disabled menu item
		items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" );

		// If the active item has been removed, blur the menu
		if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
			this.blur();
		}
	},

	_itemRole: function() {
		return {
			menu: "menuitem",
			listbox: "option"
		}[ this.options.role ];
	},

	_setOption: function( key, value ) {
		if ( key === "icons" ) {
			var icons = this.element.find( ".ui-menu-icon" );
			this._removeClass( icons, null, this.options.icons.submenu )
				._addClass( icons, null, value.submenu );
		}
		this._super( key, value );
	},

	_setOptionDisabled: function( value ) {
		this._super( value );

		this.element.attr( "aria-disabled", String( value ) );
		this._toggleClass( null, "ui-state-disabled", !!value );
	},

	focus: function( event, item ) {
		var nested, focused, activeParent;
		this.blur( event, event && event.type === "focus" );

		this._scrollIntoView( item );

		this.active = item.first();

		focused = this.active.children( ".ui-menu-item-wrapper" );
		this._addClass( focused, null, "ui-state-active" );

		// Only update aria-activedescendant if there's a role
		// otherwise we assume focus is managed elsewhere
		if ( this.options.role ) {
			this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
		}

		// Highlight active parent menu item, if any
		activeParent = this.active
			.parent()
				.closest( ".ui-menu-item" )
					.children( ".ui-menu-item-wrapper" );
		this._addClass( activeParent, null, "ui-state-active" );

		if ( event && event.type === "keydown" ) {
			this._close();
		} else {
			this.timer = this._delay( function() {
				this._close();
			}, this.delay );
		}

		nested = item.children( ".ui-menu" );
		if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) {
			this._startOpening( nested );
		}
		this.activeMenu = item.parent();

		this._trigger( "focus", event, { item: item } );
	},

	_scrollIntoView: function( item ) {
		var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
		if ( this._hasScroll() ) {
			borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0;
			paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0;
			offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
			scroll = this.activeMenu.scrollTop();
			elementHeight = this.activeMenu.height();
			itemHeight = item.outerHeight();

			if ( offset < 0 ) {
				this.activeMenu.scrollTop( scroll + offset );
			} else if ( offset + itemHeight > elementHeight ) {
				this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
			}
		}
	},

	blur: function( event, fromFocus ) {
		if ( !fromFocus ) {
			clearTimeout( this.timer );
		}

		if ( !this.active ) {
			return;
		}

		this._removeClass( this.active.children( ".ui-menu-item-wrapper" ),
			null, "ui-state-active" );

		this._trigger( "blur", event, { item: this.active } );
		this.active = null;
	},

	_startOpening: function( submenu ) {
		clearTimeout( this.timer );

		// Don't open if already open fixes a Firefox bug that caused a .5 pixel
		// shift in the submenu position when mousing over the caret icon
		if ( submenu.attr( "aria-hidden" ) !== "true" ) {
			return;
		}

		this.timer = this._delay( function() {
			this._close();
			this._open( submenu );
		}, this.delay );
	},

	_open: function( submenu ) {
		var position = $.extend( {
			of: this.active
		}, this.options.position );

		clearTimeout( this.timer );
		this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
			.hide()
			.attr( "aria-hidden", "true" );

		submenu
			.show()
			.removeAttr( "aria-hidden" )
			.attr( "aria-expanded", "true" )
			.position( position );
	},

	collapseAll: function( event, all ) {
		clearTimeout( this.timer );
		this.timer = this._delay( function() {

			// If we were passed an event, look for the submenu that contains the event
			var currentMenu = all ? this.element :
				$( event && event.target ).closest( this.element.find( ".ui-menu" ) );

			// If we found no valid submenu ancestor, use the main menu to close all
			// sub menus anyway
			if ( !currentMenu.length ) {
				currentMenu = this.element;
			}

			this._close( currentMenu );

			this.blur( event );

			// Work around active item staying active after menu is blurred
			this._removeClass( currentMenu.find( ".ui-state-active" ), null, "ui-state-active" );

			this.activeMenu = currentMenu;
		}, all ? 0 : this.delay );
	},

	// With no arguments, closes the currently active menu - if nothing is active
	// it closes all menus.  If passed an argument, it will search for menus BELOW
	_close: function( startMenu ) {
		if ( !startMenu ) {
			startMenu = this.active ? this.active.parent() : this.element;
		}

		startMenu.find( ".ui-menu" )
			.hide()
			.attr( "aria-hidden", "true" )
			.attr( "aria-expanded", "false" );
	},

	_closeOnDocumentClick: function( event ) {
		return !$( event.target ).closest( ".ui-menu" ).length;
	},

	_isDivider: function( item ) {

		// Match hyphen, em dash, en dash
		return !/[^\-\u2014\u2013\s]/.test( item.text() );
	},

	collapse: function( event ) {
		var newItem = this.active &&
			this.active.parent().closest( ".ui-menu-item", this.element );
		if ( newItem && newItem.length ) {
			this._close();
			this.focus( event, newItem );
		}
	},

	expand: function( event ) {
		var newItem = this.active && this._menuItems( this.active.children( ".ui-menu" ) ).first();

		if ( newItem && newItem.length ) {
			this._open( newItem.parent() );

			// Delay so Firefox will not hide activedescendant change in expanding submenu from AT
			this._delay( function() {
				this.focus( event, newItem );
			} );
		}
	},

	next: function( event ) {
		this._move( "next", "first", event );
	},

	previous: function( event ) {
		this._move( "prev", "last", event );
	},

	isFirstItem: function() {
		return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
	},

	isLastItem: function() {
		return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
	},

	_menuItems: function( menu ) {
		return ( menu || this.element )
			.find( this.options.items )
			.filter( ".ui-menu-item" );
	},

	_move: function( direction, filter, event ) {
		var next;
		if ( this.active ) {
			if ( direction === "first" || direction === "last" ) {
				next = this.active
					[ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
					.last();
			} else {
				next = this.active
					[ direction + "All" ]( ".ui-menu-item" )
					.first();
			}
		}
		if ( !next || !next.length || !this.active ) {
			next = this._menuItems( this.activeMenu )[ filter ]();
		}

		this.focus( event, next );
	},

	nextPage: function( event ) {
		var item, base, height;

		if ( !this.active ) {
			this.next( event );
			return;
		}
		if ( this.isLastItem() ) {
			return;
		}
		if ( this._hasScroll() ) {
			base = this.active.offset().top;
			height = this.element.innerHeight();

			// jQuery 3.2 doesn't include scrollbars in innerHeight, add it back.
			if ( $.fn.jquery.indexOf( "3.2." ) === 0 ) {
				height += this.element[ 0 ].offsetHeight - this.element.outerHeight();
			}

			this.active.nextAll( ".ui-menu-item" ).each( function() {
				item = $( this );
				return item.offset().top - base - height < 0;
			} );

			this.focus( event, item );
		} else {
			this.focus( event, this._menuItems( this.activeMenu )
				[ !this.active ? "first" : "last" ]() );
		}
	},

	previousPage: function( event ) {
		var item, base, height;
		if ( !this.active ) {
			this.next( event );
			return;
		}
		if ( this.isFirstItem() ) {
			return;
		}
		if ( this._hasScroll() ) {
			base = this.active.offset().top;
			height = this.element.innerHeight();

			// jQuery 3.2 doesn't include scrollbars in innerHeight, add it back.
			if ( $.fn.jquery.indexOf( "3.2." ) === 0 ) {
				height += this.element[ 0 ].offsetHeight - this.element.outerHeight();
			}

			this.active.prevAll( ".ui-menu-item" ).each( function() {
				item = $( this );
				return item.offset().top - base + height > 0;
			} );

			this.focus( event, item );
		} else {
			this.focus( event, this._menuItems( this.activeMenu ).first() );
		}
	},

	_hasScroll: function() {
		return this.element.outerHeight() < this.element.prop( "scrollHeight" );
	},

	select: function( event ) {

		// TODO: It should never be possible to not have an active item at this
		// point, but the tests don't trigger mouseenter before click.
		this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
		var ui = { item: this.active };
		if ( !this.active.has( ".ui-menu" ).length ) {
			this.collapseAll( event, true );
		}
		this._trigger( "select", event, ui );
	},

	_filterMenuItems: function( character ) {
		var escapedCharacter = character.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ),
			regex = new RegExp( "^" + escapedCharacter, "i" );

		return this.activeMenu
			.find( this.options.items )

				// Only match on items, not dividers or other content (#10571)
				.filter( ".ui-menu-item" )
					.filter( function() {
						return regex.test(
							String.prototype.trim.call(
								$( this ).children( ".ui-menu-item-wrapper" ).text() ) );
					} );
	}
} );

} );







/*!
 * jQuery UI Autocomplete 1.13.0
 * http://jqueryui.com
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license.
 * http://jquery.org/license
 */

//>>label: Autocomplete
//>>group: Widgets
//>>description: Lists suggested words as the user is typing.
//>>docs: http://api.jqueryui.com/autocomplete/
//>>demos: http://jqueryui.com/autocomplete/
//>>css.structure: ../../themes/base/core.css
//>>css.structure: ../../themes/base/autocomplete.css
//>>css.theme: ../../themes/base/theme.css

( function( factory ) {
	"use strict";

	if ( typeof define === "function" && define.amd ) {

		// AMD. Register as an anonymous module.
		define( [
			"jquery",
			"./menu",
			"../keycode",
			"../position",
			"../safe-active-element",
			"../version",
			"../widget"
		], factory );
	} else {

		// Browser globals
		factory( jQuery );
	}
} )( function( $ ) {
"use strict";

$.widget( "ui.autocomplete", {
	version: "1.13.0",
	defaultElement: "<input>",
	options: {
		appendTo: null,
		autoFocus: false,
		delay: 300,
		minLength: 1,
		position: {
			my: "left top",
			at: "left bottom",
			collision: "none"
		},
		source: null,

		// Callbacks
		change: null,
		close: null,
		focus: null,
		open: null,
		response: null,
		search: null,
		select: null
	},

	requestIndex: 0,
	pending: 0,

	_create: function() {

		// Some browsers only repeat keydown events, not keypress events,
		// so we use the suppressKeyPress flag to determine if we've already
		// handled the keydown event. #7269
		// Unfortunately the code for & in keypress is the same as the up arrow,
		// so we use the suppressKeyPressRepeat flag to avoid handling keypress
		// events when we know the keydown event was used to modify the
		// search term. #7799
		var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
			nodeName = this.element[ 0 ].nodeName.toLowerCase(),
			isTextarea = nodeName === "textarea",
			isInput = nodeName === "input";

		// Textareas are always multi-line
		// Inputs are always single-line, even if inside a contentEditable element
		// IE also treats inputs as contentEditable
		// All other element types are determined by whether or not they're contentEditable
		this.isMultiLine = isTextarea || !isInput && this._isContentEditable( this.element );

		this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
		this.isNewMenu = true;

		this._addClass( "ui-autocomplete-input" );
		this.element.attr( "autocomplete", "off" );

		this._on( this.element, {
			keydown: function( event ) {
				if ( this.element.prop( "readOnly" ) ) {
					suppressKeyPress = true;
					suppressInput = true;
					suppressKeyPressRepeat = true;
					return;
				}

				suppressKeyPress = false;
				suppressInput = false;
				suppressKeyPressRepeat = false;
				var keyCode = $.ui.keyCode;
				switch ( event.keyCode ) {
				case keyCode.PAGE_UP:
					suppressKeyPress = true;
					this._move( "previousPage", event );
					break;
				case keyCode.PAGE_DOWN:
					suppressKeyPress = true;
					this._move( "nextPage", event );
					break;
				case keyCode.UP:
					suppressKeyPress = true;
					this._keyEvent( "previous", event );
					break;
				case keyCode.DOWN:
					suppressKeyPress = true;
					this._keyEvent( "next", event );
					break;
				case keyCode.ENTER:

					// when menu is open and has focus
					if ( this.menu.active ) {

						// #6055 - Opera still allows the keypress to occur
						// which causes forms to submit
						suppressKeyPress = true;
						event.preventDefault();
						this.menu.select( event );
					}
					break;
				case keyCode.TAB:
					if ( this.menu.active ) {
						this.menu.select( event );
					}
					break;
				case keyCode.ESCAPE:
					if ( this.menu.element.is( ":visible" ) ) {
						if ( !this.isMultiLine ) {
							this._value( this.term );
						}
						this.close( event );

						// Different browsers have different default behavior for escape
						// Single press can mean undo or clear
						// Double press in IE means clear the whole form
						event.preventDefault();
					}
					break;
				default:
					suppressKeyPressRepeat = true;

					// search timeout should be triggered before the input value is changed
					this._searchTimeout( event );
					break;
				}
			},
			keypress: function( event ) {
				if ( suppressKeyPress ) {
					suppressKeyPress = false;
					if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
						event.preventDefault();
					}
					return;
				}
				if ( suppressKeyPressRepeat ) {
					return;
				}

				// Replicate some key handlers to allow them to repeat in Firefox and Opera
				var keyCode = $.ui.keyCode;
				switch ( event.keyCode ) {
				case keyCode.PAGE_UP:
					this._move( "previousPage", event );
					break;
				case keyCode.PAGE_DOWN:
					this._move( "nextPage", event );
					break;
				case keyCode.UP:
					this._keyEvent( "previous", event );
					break;
				case keyCode.DOWN:
					this._keyEvent( "next", event );
					break;
				}
			},
			input: function( event ) {
				if ( suppressInput ) {
					suppressInput = false;
					event.preventDefault();
					return;
				}
				this._searchTimeout( event );
			},
			focus: function() {
				this.selectedItem = null;
				this.previous = this._value();
			},
			blur: function( event ) {
				clearTimeout( this.searching );
				this.close( event );
				this._change( event );
			}
		} );

		this._initSource();
		this.menu = $( "<ul>" )
			.appendTo( this._appendTo() )
			.menu( {

				// disable ARIA support, the live region takes care of that
				role: null
			} )
			.hide()

			// Support: IE 11 only, Edge <= 14
			// For other browsers, we preventDefault() on the mousedown event
			// to keep the dropdown from taking focus from the input. This doesn't
			// work for IE/Edge, causing problems with selection and scrolling (#9638)
			// Happily, IE and Edge support an "unselectable" attribute that
			// prevents an element from receiving focus, exactly what we want here.
			.attr( {
				"unselectable": "on"
			} )
			.menu( "instance" );

		this._addClass( this.menu.element, "ui-autocomplete", "ui-front" );
		this._on( this.menu.element, {
			mousedown: function( event ) {

				// Prevent moving focus out of the text field
				event.preventDefault();
			},
			menufocus: function( event, ui ) {
				var label, item;

				// support: Firefox
				// Prevent accidental activation of menu items in Firefox (#7024 #9118)
				if ( this.isNewMenu ) {
					this.isNewMenu = false;
					if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
						this.menu.blur();

						this.document.one( "mousemove", function() {
							$( event.target ).trigger( event.originalEvent );
						} );

						return;
					}
				}

				item = ui.item.data( "ui-autocomplete-item" );
				if ( false !== this._trigger( "focus", event, { item: item } ) ) {

					// use value to match what will end up in the input, if it was a key event
					if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
						this._value( item.value );
					}
				}

				// Announce the value in the liveRegion
				label = ui.item.attr( "aria-label" ) || item.value;
				if ( label && String.prototype.trim.call( label ).length ) {
					this.liveRegion.children().hide();
					$( "<div>" ).text( label ).appendTo( this.liveRegion );
				}
			},
			menuselect: function( event, ui ) {
				var item = ui.item.data( "ui-autocomplete-item" ),
					previous = this.previous;

				// Only trigger when focus was lost (click on menu)
				if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {
					this.element.trigger( "focus" );
					this.previous = previous;

					// #6109 - IE triggers two focus events and the second
					// is asynchronous, so we need to reset the previous
					// term synchronously and asynchronously :-(
					this._delay( function() {
						this.previous = previous;
						this.selectedItem = item;
					} );
				}

				if ( false !== this._trigger( "select", event, { item: item } ) ) {
					this._value( item.value );
				}

				// reset the term after the select event
				// this allows custom select handling to work properly
				this.term = this._value();

				this.close( event );
				this.selectedItem = item;
			}
		} );

		this.liveRegion = $( "<div>", {
			role: "status",
			"aria-live": "assertive",
			"aria-relevant": "additions"
		} )
			.appendTo( this.document[ 0 ].body );

		this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" );

		// Turning off autocomplete prevents the browser from remembering the
		// value when navigating through history, so we re-enable autocomplete
		// if the page is unloaded before the widget is destroyed. #7790
		this._on( this.window, {
			beforeunload: function() {
				this.element.removeAttr( "autocomplete" );
			}
		} );
	},

	_destroy: function() {
		clearTimeout( this.searching );
		this.element.removeAttr( "autocomplete" );
		this.menu.element.remove();
		this.liveRegion.remove();
	},

	_setOption: function( key, value ) {
		this._super( key, value );
		if ( key === "source" ) {
			this._initSource();
		}
		if ( key === "appendTo" ) {
			this.menu.element.appendTo( this._appendTo() );
		}
		if ( key === "disabled" && value && this.xhr ) {
			this.xhr.abort();
		}
	},

	_isEventTargetInWidget: function( event ) {
		var menuElement = this.menu.element[ 0 ];

		return event.target === this.element[ 0 ] ||
			event.target === menuElement ||
			$.contains( menuElement, event.target );
	},

	_closeOnClickOutside: function( event ) {
		if ( !this._isEventTargetInWidget( event ) ) {
			this.close();
		}
	},

	_appendTo: function() {
		var element = this.options.appendTo;

		if ( element ) {
			element = element.jquery || element.nodeType ?
				$( element ) :
				this.document.find( element ).eq( 0 );
		}

		if ( !element || !element[ 0 ] ) {
			element = this.element.closest( ".ui-front, dialog" );
		}

		if ( !element.length ) {
			element = this.document[ 0 ].body;
		}

		return element;
	},

	_initSource: function() {
		var array, url,
			that = this;
		if ( Array.isArray( this.options.source ) ) {
			array = this.options.source;
			this.source = function( request, response ) {
				response( $.ui.autocomplete.filter( array, request.term ) );
			};
		} else if ( typeof this.options.source === "string" ) {
			url = this.options.source;
			this.source = function( request, response ) {
				if ( that.xhr ) {
					that.xhr.abort();
				}
				that.xhr = $.ajax( {
					url: url,
					data: request,
					dataType: "json",
					success: function( data ) {
						response( data );
					},
					error: function() {
						response( [] );
					}
				} );
			};
		} else {
			this.source = this.options.source;
		}
	},

	_searchTimeout: function( event ) {
		clearTimeout( this.searching );
		this.searching = this._delay( function() {

			// Search if the value has changed, or if the user retypes the same value (see #7434)
			var equalValues = this.term === this._value(),
				menuVisible = this.menu.element.is( ":visible" ),
				modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;

			if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) {
				this.selectedItem = null;
				this.search( null, event );
			}
		}, this.options.delay );
	},

	search: function( value, event ) {
		value = value != null ? value : this._value();

		// Always save the actual value, not the one passed as an argument
		this.term = this._value();

		if ( value.length < this.options.minLength ) {
			return this.close( event );
		}

		if ( this._trigger( "search", event ) === false ) {
			return;
		}

		return this._search( value );
	},

	_search: function( value ) {
		this.pending++;
		this._addClass( "ui-autocomplete-loading" );
		this.cancelSearch = false;

		this.source( { term: value }, this._response() );
	},

	_response: function() {
		var index = ++this.requestIndex;

		return function( content ) {
			if ( index === this.requestIndex ) {
				this.__response( content );
			}

			this.pending--;
			if ( !this.pending ) {
				this._removeClass( "ui-autocomplete-loading" );
			}
		}.bind( this );
	},

	__response: function( content ) {
		if ( content ) {
			content = this._normalize( content );
		}
		this._trigger( "response", null, { content: content } );
		if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
			this._suggest( content );
			this._trigger( "open" );
		} else {

			// use ._close() instead of .close() so we don't cancel future searches
			this._close();
		}
	},

	close: function( event ) {
		this.cancelSearch = true;
		this._close( event );
	},

	_close: function( event ) {

		// Remove the handler that closes the menu on outside clicks
		this._off( this.document, "mousedown" );

		if ( this.menu.element.is( ":visible" ) ) {
			this.menu.element.hide();
			this.menu.blur();
			this.isNewMenu = true;
			this._trigger( "close", event );
		}
	},

	_change: function( event ) {
		if ( this.previous !== this._value() ) {
			this._trigger( "change", event, { item: this.selectedItem } );
		}
	},

	_normalize: function( items ) {

		// assume all items have the right format when the first item is complete
		if ( items.length && items[ 0 ].label && items[ 0 ].value ) {
			return items;
		}
		return $.map( items, function( item ) {
			if ( typeof item === "string" ) {
				return {
					label: item,
					value: item
				};
			}
			return $.extend( {}, item, {
				label: item.label || item.value,
				value: item.value || item.label
			} );
		} );
	},

	_suggest: function( items ) {
		var ul = this.menu.element.empty();
		this._renderMenu( ul, items );
		this.isNewMenu = true;
		this.menu.refresh();

		// Size and position menu
		ul.show();
		this._resizeMenu();
		ul.position( $.extend( {
			of: this.element
		}, this.options.position ) );

		if ( this.options.autoFocus ) {
			this.menu.next();
		}

		// Listen for interactions outside of the widget (#6642)
		this._on( this.document, {
			mousedown: "_closeOnClickOutside"
		} );
	},

	_resizeMenu: function() {
		var ul = this.menu.element;
		ul.outerWidth( Math.max(

			// Firefox wraps long text (possibly a rounding bug)
			// so we add 1px to avoid the wrapping (#7513)
			ul.width( "" ).outerWidth() + 1,
			this.element.outerWidth()
		) );
	},

	_renderMenu: function( ul, items ) {
		var that = this;
		$.each( items, function( index, item ) {
			that._renderItemData( ul, item );
		} );
	},

	_renderItemData: function( ul, item ) {
		return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
	},

	_renderItem: function( ul, item ) {
		return $( "<li>" )
			.append( $( "<div>" ).text( item.label ) )
			.appendTo( ul );
	},

	_move: function( direction, event ) {
		if ( !this.menu.element.is( ":visible" ) ) {
			this.search( null, event );
			return;
		}
		if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
				this.menu.isLastItem() && /^next/.test( direction ) ) {

			if ( !this.isMultiLine ) {
				this._value( this.term );
			}

			this.menu.blur();
			return;
		}
		this.menu[ direction ]( event );
	},

	widget: function() {
		return this.menu.element;
	},

	_value: function() {
		return this.valueMethod.apply( this.element, arguments );
	},

	_keyEvent: function( keyEvent, event ) {
		if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
			this._move( keyEvent, event );

			// Prevents moving cursor to beginning/end of the text field in some browsers
			event.preventDefault();
		}
	},

	// Support: Chrome <=50
	// We should be able to just use this.element.prop( "isContentEditable" )
	// but hidden elements always report false in Chrome.
	// https://code.google.com/p/chromium/issues/detail?id=313082
	_isContentEditable: function( element ) {
		if ( !element.length ) {
			return false;
		}

		var editable = element.prop( "contentEditable" );

		if ( editable === "inherit" ) {
			return this._isContentEditable( element.parent() );
		}

		return editable === "true";
	}
} );

$.extend( $.ui.autocomplete, {
	escapeRegex: function( value ) {
		return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
	},
	filter: function( array, term ) {
		var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" );
		return $.grep( array, function( value ) {
			return matcher.test( value.label || value.value || value );
		} );
	}
} );

// Live region extension, adding a `messages` option
// NOTE: This is an experimental API. We are still investigating
// a full solution for string manipulation and internationalization.
$.widget( "ui.autocomplete", $.ui.autocomplete, {
	options: {
		messages: {
			noResults: "No search results.",
			results: function( amount ) {
				return amount + ( amount > 1 ? " results are" : " result is" ) +
					" available, use up and down arrow keys to navigate.";
			}
		}
	},

	__response: function( content ) {
		var message;
		this._superApply( arguments );
		if ( this.options.disabled || this.cancelSearch ) {
			return;
		}
		if ( content && content.length ) {
			message = this.options.messages.results( content.length );
		} else {
			message = this.options.messages.noResults;
		}
		this.liveRegion.children().hide();
		$( "<div>" ).text( message ).appendTo( this.liveRegion );
	}
} );

return $.ui.autocomplete;

} );
!function(t){t.fn.railsAutocomplete=function(e){var a=function(){this.railsAutoCompleter||(this.railsAutoCompleter=new t.railsAutocomplete(this))};if(void 0!==t.fn.on){if(!e)return;return t(document).on("focus",e,a)}return this.live("focus",a)},t.railsAutocomplete=function(t){var e=t;this.init(e)},t.railsAutocomplete.options={showNoMatches:!0,noMatchesLabel:"no existing match"},t.railsAutocomplete.fn=t.railsAutocomplete.prototype={railsAutocomplete:"0.0.1"},t.railsAutocomplete.fn.extend=t.railsAutocomplete.extend=t.extend,t.railsAutocomplete.fn.extend({init:function(e){function a(t){return t.split(e.delimiter)}function i(t){return a(t).pop().replace(/^\s+/,"")}e.delimiter=t(e).attr("data-delimiter")||null,e.min_length=t(e).attr("data-min-length")||t(e).attr("min-length")||2,e.append_to=t(e).attr("data-append-to")||null,e.autoFocus=t(e).attr("data-auto-focus")||!1,t(e).autocomplete({appendTo:e.append_to,autoFocus:e.autoFocus,delay:t(e).attr("delay")||0,source:function(a,r){var n=this.element[0],o={term:i(a.term)};t(e).attr("data-autocomplete-fields")&&t.each(t.parseJSON(t(e).attr("data-autocomplete-fields")),function(e,a){o[e]=t(a).val()}),t.getJSON(t(e).attr("data-autocomplete"),o,function(){var a={};t.extend(a,t.railsAutocomplete.options),t.each(a,function(i,r){if(a.hasOwnProperty(i)){var n=t(e).attr("data-"+i);a[i]=n?n:r}}),0==arguments[0].length&&t.inArray(a.showNoMatches,[!0,"true"])>=0&&(arguments[0]=[],arguments[0][0]={id:"",label:a.noMatchesLabel}),t(arguments[0]).each(function(a,i){var r={};r[i.id]=i,t(e).data(r)}),r.apply(null,arguments),t(n).trigger("railsAutocomplete.source",arguments)})},change:function(e,a){if(t(this).is("[data-id-element]")&&""!==t(t(this).attr("data-id-element")).val()&&(t(t(this).attr("data-id-element")).val(a.item?a.item.id:"").trigger("change"),t(this).attr("data-update-elements"))){var i=t.parseJSON(t(this).attr("data-update-elements")),r=a.item?t(this).data(a.item.id.toString()):{};if(i&&""===t(i.id).val())return;for(var n in i){var o=t(i[n]);o.is(":checkbox")?null!=r[n]&&o.prop("checked",r[n]):o.val(a.item?r[n]:"").trigger("change")}}},search:function(){var t=i(this.value);return t.length<e.min_length?!1:void 0},focus:function(){return!1},select:function(i,r){if(r.item.value=r.item.value.toString(),-1!=r.item.value.toLowerCase().indexOf("no match")||-1!=r.item.value.toLowerCase().indexOf("too many results"))return t(this).trigger("railsAutocomplete.noMatch",r),!1;var n=a(this.value);if(n.pop(),n.push(r.item.value),null!=e.delimiter)n.push(""),this.value=n.join(e.delimiter);else if(this.value=n.join(""),t(this).attr("data-id-element")&&t(t(this).attr("data-id-element")).val(r.item.id).trigger("change"),t(this).attr("data-update-elements")){var o=r.item,l=-1!=r.item.value.indexOf("Create New")?!0:!1,u=t.parseJSON(t(this).attr("data-update-elements"));for(var s in u)"checkbox"===t(u[s]).attr("type")?o[s]===!0||1===o[s]?t(u[s]).attr("checked","checked"):t(u[s]).removeAttr("checked"):l&&o[s]&&-1==o[s].indexOf("Create New")||!l?t(u[s]).val(o[s]).trigger("change"):t(u[s]).val("").trigger("change")}var c=this.value;return t(this).bind("keyup.clearId",function(){t.trim(t(this).val())!=t.trim(c)&&(t(t(this).attr("data-id-element")).val("").trigger("change"),t(this).unbind("keyup.clearId"))}),t(e).trigger("railsAutocomplete.select",r),!1}}),t(e).trigger("railsAutocomplete.init")}}),t(document).ready(function(){t("input[data-autocomplete]").railsAutocomplete("input[data-autocomplete]")})}(jQuery);
/*! flipclock 2014-10-21 */

var Base=function(){};Base.extend=function(a,b){"use strict";var c=Base.prototype.extend;Base._prototyping=!0;var d=new this;c.call(d,a),d.base=function(){},delete Base._prototyping;var e=d.constructor,f=d.constructor=function(){if(!Base._prototyping)if(this._constructing||this.constructor==f)this._constructing=!0,e.apply(this,arguments),delete this._constructing;else if(null!==arguments[0])return(arguments[0].extend||c).call(arguments[0],d)};return f.ancestor=this,f.extend=this.extend,f.forEach=this.forEach,f.implement=this.implement,f.prototype=d,f.toString=this.toString,f.valueOf=function(a){return"object"==a?f:e.valueOf()},c.call(f,b),"function"==typeof f.init&&f.init(),f},Base.prototype={extend:function(a,b){if(arguments.length>1){var c=this[a];if(c&&"function"==typeof b&&(!c.valueOf||c.valueOf()!=b.valueOf())&&/\bbase\b/.test(b)){var d=b.valueOf();b=function(){var a=this.base||Base.prototype.base;this.base=c;var b=d.apply(this,arguments);return this.base=a,b},b.valueOf=function(a){return"object"==a?b:d},b.toString=Base.toString}this[a]=b}else if(a){var e=Base.prototype.extend;Base._prototyping||"function"==typeof this||(e=this.extend||e);for(var f={toSource:null},g=["constructor","toString","valueOf"],h=Base._prototyping?0:1;i=g[h++];)a[i]!=f[i]&&e.call(this,i,a[i]);for(var i in a)f[i]||e.call(this,i,a[i])}return this}},Base=Base.extend({constructor:function(){this.extend(arguments[0])}},{ancestor:Object,version:"1.1",forEach:function(a,b,c){for(var d in a)void 0===this.prototype[d]&&b.call(c,a[d],d,a)},implement:function(){for(var a=0;a<arguments.length;a++)"function"==typeof arguments[a]?arguments[a](this.prototype):this.prototype.extend(arguments[a]);return this},toString:function(){return String(this.valueOf())}});var FlipClock;!function(a){"use strict";FlipClock=function(a,b,c){return b instanceof Object&&b instanceof Date==!1&&(c=b,b=0),new FlipClock.Factory(a,b,c)},FlipClock.Lang={},FlipClock.Base=Base.extend({buildDate:"2014-10-06",version:"0.7.5",constructor:function(b,c){"object"!=typeof b&&(b={}),"object"!=typeof c&&(c={}),this.setOptions(a.extend(!0,{},b,c))},callback:function(a){if("function"==typeof a){for(var b=[],c=1;c<=arguments.length;c++)arguments[c]&&b.push(arguments[c]);a.apply(this,b)}},log:function(a){window.console&&console.log&&console.log(a)},getOption:function(a){return this[a]?this[a]:!1},getOptions:function(){return this},setOption:function(a,b){this[a]=b},setOptions:function(a){for(var b in a)"undefined"!=typeof a[b]&&this.setOption(b,a[b])}})}(jQuery),function(a){"use strict";FlipClock.Face=FlipClock.Base.extend({autoStart:!0,dividers:[],factory:!1,lists:[],constructor:function(a,b){this.dividers=[],this.lists=[],this.base(b),this.factory=a},build:function(){this.autoStart&&this.start()},createDivider:function(b,c,d){"boolean"!=typeof c&&c||(d=c,c=b);var e=['<span class="'+this.factory.classes.dot+' top"></span>','<span class="'+this.factory.classes.dot+' bottom"></span>'].join("");d&&(e=""),b=this.factory.localize(b);var f=['<span class="'+this.factory.classes.divider+" "+(c?c:"").toLowerCase()+'">','<span class="'+this.factory.classes.label+'">'+(b?b:"")+"</span>",e,"</span>"],g=a(f.join(""));return this.dividers.push(g),g},createList:function(a,b){"object"==typeof a&&(b=a,a=0);var c=new FlipClock.List(this.factory,a,b);return this.lists.push(c),c},reset:function(){this.factory.time=new FlipClock.Time(this.factor,this.factory.original?Math.round(this.factory.original):0,{minimumDigits:this.factory.minimumDigits}),this.flip(this.factory.original,!1)},appendDigitToClock:function(a){a.$el.append(!1)},addDigit:function(a){var b=this.createList(a,{classes:{active:this.factory.classes.active,before:this.factory.classes.before,flip:this.factory.classes.flip}});this.appendDigitToClock(b)},start:function(){},stop:function(){},autoIncrement:function(){this.factory.countdown?this.decrement():this.increment()},increment:function(){this.factory.time.addSecond()},decrement:function(){0==this.factory.time.getTimeSeconds()?this.factory.stop():this.factory.time.subSecond()},flip:function(b,c){var d=this;a.each(b,function(a,b){var e=d.lists[a];e?(c||b==e.digit||e.play(),e.select(b)):d.addDigit(b)})}})}(jQuery),function(a){"use strict";FlipClock.Factory=FlipClock.Base.extend({animationRate:1e3,autoStart:!0,callbacks:{destroy:!1,create:!1,init:!1,interval:!1,start:!1,stop:!1,reset:!1},classes:{active:"flip-clock-active",before:"flip-clock-before",divider:"flip-clock-divider",dot:"flip-clock-dot",label:"flip-clock-label",flip:"flip",play:"play",wrapper:"flip-clock-wrapper"},clockFace:"HourlyCounter",countdown:!1,defaultClockFace:"HourlyCounter",defaultLanguage:"english",$el:!1,face:!0,lang:!1,language:"english",minimumDigits:0,original:!1,running:!1,time:!1,timer:!1,$wrapper:!1,constructor:function(b,c,d){d||(d={}),this.lists=[],this.running=!1,this.base(d),this.$el=a(b).addClass(this.classes.wrapper),this.$wrapper=this.$el,this.original=c instanceof Date?c:c?Math.round(c):0,this.time=new FlipClock.Time(this,this.original,{minimumDigits:this.minimumDigits,animationRate:this.animationRate}),this.timer=new FlipClock.Timer(this,d),this.loadLanguage(this.language),this.loadClockFace(this.clockFace,d),this.autoStart&&this.start()},loadClockFace:function(a,b){var c,d="Face",e=!1;return a=a.ucfirst()+d,this.face.stop&&(this.stop(),e=!0),this.$el.html(""),this.time.minimumDigits=this.minimumDigits,c=FlipClock[a]?new FlipClock[a](this,b):new FlipClock[this.defaultClockFace+d](this,b),c.build(),this.face=c,e&&this.start(),this.face},loadLanguage:function(a){var b;return b=FlipClock.Lang[a.ucfirst()]?FlipClock.Lang[a.ucfirst()]:FlipClock.Lang[a]?FlipClock.Lang[a]:FlipClock.Lang[this.defaultLanguage],this.lang=b},localize:function(a,b){var c=this.lang;if(!a)return null;var d=a.toLowerCase();return"object"==typeof b&&(c=b),c&&c[d]?c[d]:a},start:function(a){var b=this;b.running||b.countdown&&!(b.countdown&&b.time.time>0)?b.log("Trying to start timer when countdown already at 0"):(b.face.start(b.time),b.timer.start(function(){b.flip(),"function"==typeof a&&a()}))},stop:function(a){this.face.stop(),this.timer.stop(a);for(var b in this.lists)this.lists.hasOwnProperty(b)&&this.lists[b].stop()},reset:function(a){this.timer.reset(a),this.face.reset()},setTime:function(a){this.time.time=a,this.flip(!0)},getTime:function(){return this.time},setCountdown:function(a){var b=this.running;this.countdown=a?!0:!1,b&&(this.stop(),this.start())},flip:function(a){this.face.flip(!1,a)}})}(jQuery),function(a){"use strict";FlipClock.List=FlipClock.Base.extend({digit:0,classes:{active:"flip-clock-active",before:"flip-clock-before",flip:"flip"},factory:!1,$el:!1,$obj:!1,items:[],lastDigit:0,constructor:function(a,b){this.factory=a,this.digit=b,this.lastDigit=b,this.$el=this.createList(),this.$obj=this.$el,b>0&&this.select(b),this.factory.$el.append(this.$el)},select:function(a){if("undefined"==typeof a?a=this.digit:this.digit=a,this.digit!=this.lastDigit){var b=this.$el.find("."+this.classes.before).removeClass(this.classes.before);this.$el.find("."+this.classes.active).removeClass(this.classes.active).addClass(this.classes.before),this.appendListItem(this.classes.active,this.digit),b.remove(),this.lastDigit=this.digit}},play:function(){this.$el.addClass(this.factory.classes.play)},stop:function(){var a=this;setTimeout(function(){a.$el.removeClass(a.factory.classes.play)},this.factory.timer.interval)},createListItem:function(a,b){return['<li class="'+(a?a:"")+'">','<a href="#">','<div class="up">','<div class="shadow"></div>','<div class="inn">'+(b?b:"")+"</div>","</div>",'<div class="down">','<div class="shadow"></div>','<div class="inn">'+(b?b:"")+"</div>","</div>","</a>","</li>"].join("")},appendListItem:function(a,b){var c=this.createListItem(a,b);this.$el.append(c)},createList:function(){var b=this.getPrevDigit()?this.getPrevDigit():this.digit,c=a(['<ul class="'+this.classes.flip+" "+(this.factory.running?this.factory.classes.play:"")+'">',this.createListItem(this.classes.before,b),this.createListItem(this.classes.active,this.digit),"</ul>"].join(""));return c},getNextDigit:function(){return 9==this.digit?0:this.digit+1},getPrevDigit:function(){return 0==this.digit?9:this.digit-1}})}(jQuery),function(a){"use strict";String.prototype.ucfirst=function(){return this.substr(0,1).toUpperCase()+this.substr(1)},a.fn.FlipClock=function(b,c){return new FlipClock(a(this),b,c)},a.fn.flipClock=function(b,c){return a.fn.FlipClock(b,c)}}(jQuery),function(a){"use strict";FlipClock.Time=FlipClock.Base.extend({time:0,factory:!1,minimumDigits:0,constructor:function(a,b,c){"object"!=typeof c&&(c={}),c.minimumDigits||(c.minimumDigits=a.minimumDigits),this.base(c),this.factory=a,b&&(this.time=b)},convertDigitsToArray:function(a){var b=[];a=a.toString();for(var c=0;c<a.length;c++)a[c].match(/^\d*$/g)&&b.push(a[c]);return b},digit:function(a){var b=this.toString(),c=b.length;return b[c-a]?b[c-a]:!1},digitize:function(b){var c=[];if(a.each(b,function(a,b){b=b.toString(),1==b.length&&(b="0"+b);for(var d=0;d<b.length;d++)c.push(b.charAt(d))}),c.length>this.minimumDigits&&(this.minimumDigits=c.length),this.minimumDigits>c.length)for(var d=c.length;d<this.minimumDigits;d++)c.unshift("0");return c},getDateObject:function(){return this.time instanceof Date?this.time:new Date((new Date).getTime()+1e3*this.getTimeSeconds())},getDayCounter:function(a){var b=[this.getDays(),this.getHours(!0),this.getMinutes(!0)];return a&&b.push(this.getSeconds(!0)),this.digitize(b)},getDays:function(a){var b=this.getTimeSeconds()/60/60/24;return a&&(b%=7),Math.floor(b)},getHourCounter:function(){var a=this.digitize([this.getHours(),this.getMinutes(!0),this.getSeconds(!0)]);return a},getHourly:function(){return this.getHourCounter()},getHours:function(a){var b=this.getTimeSeconds()/60/60;return a&&(b%=24),Math.floor(b)},getMilitaryTime:function(a,b){"undefined"==typeof b&&(b=!0),a||(a=this.getDateObject());var c=[a.getHours(),a.getMinutes()];return b===!0&&c.push(a.getSeconds()),this.digitize(c)},getMinutes:function(a){var b=this.getTimeSeconds()/60;return a&&(b%=60),Math.floor(b)},getMinuteCounter:function(){var a=this.digitize([this.getMinutes(),this.getSeconds(!0)]);return a},getTimeSeconds:function(a){return a||(a=new Date),this.time instanceof Date?this.factory.countdown?Math.max(this.time.getTime()/1e3-a.getTime()/1e3,0):a.getTime()/1e3-this.time.getTime()/1e3:this.time},getTime:function(a,b){"undefined"==typeof b&&(b=!0),a||(a=this.getDateObject()),console.log(a);var c=a.getHours(),d=[c>12?c-12:0===c?12:c,a.getMinutes()];return b===!0&&d.push(a.getSeconds()),this.digitize(d)},getSeconds:function(a){var b=this.getTimeSeconds();return a&&(60==b?b=0:b%=60),Math.ceil(b)},getWeeks:function(a){var b=this.getTimeSeconds()/60/60/24/7;return a&&(b%=52),Math.floor(b)},removeLeadingZeros:function(b,c){var d=0,e=[];return a.each(c,function(a){b>a?d+=parseInt(c[a],10):e.push(c[a])}),0===d?e:c},addSeconds:function(a){this.time instanceof Date?this.time.setSeconds(this.time.getSeconds()+a):this.time+=a},addSecond:function(){this.addSeconds(1)},subSeconds:function(a){this.time instanceof Date?this.time.setSeconds(this.time.getSeconds()-a):this.time-=a},subSecond:function(){this.subSeconds(1)},toString:function(){return this.getTimeSeconds().toString()}})}(jQuery),function(){"use strict";FlipClock.Timer=FlipClock.Base.extend({callbacks:{destroy:!1,create:!1,init:!1,interval:!1,start:!1,stop:!1,reset:!1},count:0,factory:!1,interval:1e3,animationRate:1e3,constructor:function(a,b){this.base(b),this.factory=a,this.callback(this.callbacks.init),this.callback(this.callbacks.create)},getElapsed:function(){return this.count*this.interval},getElapsedTime:function(){return new Date(this.time+this.getElapsed())},reset:function(a){clearInterval(this.timer),this.count=0,this._setInterval(a),this.callback(this.callbacks.reset)},start:function(a){this.factory.running=!0,this._createTimer(a),this.callback(this.callbacks.start)},stop:function(a){this.factory.running=!1,this._clearInterval(a),this.callback(this.callbacks.stop),this.callback(a)},_clearInterval:function(){clearInterval(this.timer)},_createTimer:function(a){this._setInterval(a)},_destroyTimer:function(a){this._clearInterval(),this.timer=!1,this.callback(a),this.callback(this.callbacks.destroy)},_interval:function(a){this.callback(this.callbacks.interval),this.callback(a),this.count++},_setInterval:function(a){var b=this;b._interval(a),b.timer=setInterval(function(){b._interval(a)},this.interval)}})}(jQuery),function(a){FlipClock.TwentyFourHourClockFace=FlipClock.Face.extend({constructor:function(a,b){this.base(a,b)},build:function(b){var c=this,d=this.factory.$el.find("ul");this.factory.time.time||(this.factory.original=new Date,this.factory.time=new FlipClock.Time(this.factory,this.factory.original));var b=b?b:this.factory.time.getMilitaryTime(!1,this.showSeconds);b.length>d.length&&a.each(b,function(a,b){c.createList(b)}),this.createDivider(),this.createDivider(),a(this.dividers[0]).insertBefore(this.lists[this.lists.length-2].$el),a(this.dividers[1]).insertBefore(this.lists[this.lists.length-4].$el),this.base()},flip:function(a,b){this.autoIncrement(),a=a?a:this.factory.time.getMilitaryTime(!1,this.showSeconds),this.base(a,b)}})}(jQuery),function(a){FlipClock.CounterFace=FlipClock.Face.extend({shouldAutoIncrement:!1,constructor:function(a,b){"object"!=typeof b&&(b={}),a.autoStart=b.autoStart?!0:!1,b.autoStart&&(this.shouldAutoIncrement=!0),a.increment=function(){a.countdown=!1,a.setTime(a.getTime().getTimeSeconds()+1)},a.decrement=function(){a.countdown=!0;var b=a.getTime().getTimeSeconds();b>0&&a.setTime(b-1)},a.setValue=function(b){a.setTime(b)},a.setCounter=function(b){a.setTime(b)},this.base(a,b)},build:function(){var b=this,c=this.factory.$el.find("ul"),d=this.factory.getTime().digitize([this.factory.getTime().time]);d.length>c.length&&a.each(d,function(a,c){var d=b.createList(c);d.select(c)}),a.each(this.lists,function(a,b){b.play()}),this.base()},flip:function(a,b){this.shouldAutoIncrement&&this.autoIncrement(),a||(a=this.factory.getTime().digitize([this.factory.getTime().time])),this.base(a,b)},reset:function(){this.factory.time=new FlipClock.Time(this.factory,this.factory.original?Math.round(this.factory.original):0),this.flip()}})}(jQuery),function(a){FlipClock.DailyCounterFace=FlipClock.Face.extend({showSeconds:!0,constructor:function(a,b){this.base(a,b)},build:function(b){var c=this,d=this.factory.$el.find("ul"),e=0;b=b?b:this.factory.time.getDayCounter(this.showSeconds),b.length>d.length&&a.each(b,function(a,b){c.createList(b)}),this.showSeconds?a(this.createDivider("Seconds")).insertBefore(this.lists[this.lists.length-2].$el):e=2,a(this.createDivider("Minutes")).insertBefore(this.lists[this.lists.length-4+e].$el),a(this.createDivider("Hours")).insertBefore(this.lists[this.lists.length-6+e].$el),a(this.createDivider("Days",!0)).insertBefore(this.lists[0].$el),this.base()},flip:function(a,b){a||(a=this.factory.time.getDayCounter(this.showSeconds)),this.autoIncrement(),this.base(a,b)}})}(jQuery),function(a){FlipClock.HourlyCounterFace=FlipClock.Face.extend({constructor:function(a,b){this.base(a,b)},build:function(b,c){var d=this,e=this.factory.$el.find("ul");c=c?c:this.factory.time.getHourCounter(),c.length>e.length&&a.each(c,function(a,b){d.createList(b)}),a(this.createDivider("Seconds")).insertBefore(this.lists[this.lists.length-2].$el),a(this.createDivider("Minutes")).insertBefore(this.lists[this.lists.length-4].$el),b||a(this.createDivider("Hours",!0)).insertBefore(this.lists[0].$el),this.base()},flip:function(a,b){a||(a=this.factory.time.getHourCounter()),this.autoIncrement(),this.base(a,b)},appendDigitToClock:function(a){this.base(a),this.dividers[0].insertAfter(this.dividers[0].next())}})}(jQuery),function(){FlipClock.MinuteCounterFace=FlipClock.HourlyCounterFace.extend({clearExcessDigits:!1,constructor:function(a,b){this.base(a,b)},build:function(){this.base(!0,this.factory.time.getMinuteCounter())},flip:function(a,b){a||(a=this.factory.time.getMinuteCounter()),this.base(a,b)}})}(jQuery),function(a){FlipClock.TwelveHourClockFace=FlipClock.TwentyFourHourClockFace.extend({meridium:!1,meridiumText:"AM",build:function(){var b=this.factory.time.getTime(!1,this.showSeconds);this.base(b),this.meridiumText=this.getMeridium(),this.meridium=a(['<ul class="flip-clock-meridium">',"<li>",'<a href="#">'+this.meridiumText+"</a>","</li>","</ul>"].join("")),this.meridium.insertAfter(this.lists[this.lists.length-1].$el)},flip:function(a,b){this.meridiumText!=this.getMeridium()&&(this.meridiumText=this.getMeridium(),this.meridium.find("a").html(this.meridiumText)),this.base(this.factory.time.getTime(!1,this.showSeconds),b)},getMeridium:function(){return(new Date).getHours()>=12?"PM":"AM"},isPM:function(){return"PM"==this.getMeridium()?!0:!1},isAM:function(){return"AM"==this.getMeridium()?!0:!1}})}(jQuery),function(){FlipClock.Lang.Arabic={years:"سنوات",months:"شهور",days:"أيام",hours:"ساعات",minutes:"دقائق",seconds:"ثواني"},FlipClock.Lang.ar=FlipClock.Lang.Arabic,FlipClock.Lang["ar-ar"]=FlipClock.Lang.Arabic,FlipClock.Lang.arabic=FlipClock.Lang.Arabic}(jQuery),function(){FlipClock.Lang.Danish={years:"År",months:"Måneder",days:"Dage",hours:"Timer",minutes:"Minutter",seconds:"Sekunder"},FlipClock.Lang.da=FlipClock.Lang.Danish,FlipClock.Lang["da-dk"]=FlipClock.Lang.Danish,FlipClock.Lang.danish=FlipClock.Lang.Danish}(jQuery),function(){FlipClock.Lang.German={years:"Jahre",months:"Monate",days:"Tage",hours:"Stunden",minutes:"Minuten",seconds:"Sekunden"},FlipClock.Lang.de=FlipClock.Lang.German,FlipClock.Lang["de-de"]=FlipClock.Lang.German,FlipClock.Lang.german=FlipClock.Lang.German}(jQuery),function(){FlipClock.Lang.English={years:"Years",months:"Months",days:"Days",hours:"Hours",minutes:"Minutes",seconds:"Seconds"},FlipClock.Lang.en=FlipClock.Lang.English,FlipClock.Lang["en-us"]=FlipClock.Lang.English,FlipClock.Lang.english=FlipClock.Lang.English}(jQuery),function(){FlipClock.Lang.Spanish={years:"A&#241;os",months:"Meses",days:"D&#205;as",hours:"Horas",minutes:"Minutos",seconds:"Segundo"},FlipClock.Lang.es=FlipClock.Lang.Spanish,FlipClock.Lang["es-es"]=FlipClock.Lang.Spanish,FlipClock.Lang.spanish=FlipClock.Lang.Spanish}(jQuery),function(){FlipClock.Lang.Finnish={years:"Vuotta",months:"Kuukautta",days:"Päivää",hours:"Tuntia",minutes:"Minuuttia",seconds:"Sekuntia"},FlipClock.Lang.fi=FlipClock.Lang.Finnish,FlipClock.Lang["fi-fi"]=FlipClock.Lang.Finnish,FlipClock.Lang.finnish=FlipClock.Lang.Finnish}(jQuery),function(){FlipClock.Lang.French={years:"Ans",months:"Mois",days:"Jours",hours:"Heures",minutes:"Minutes",seconds:"Secondes"},FlipClock.Lang.fr=FlipClock.Lang.French,FlipClock.Lang["fr-ca"]=FlipClock.Lang.French,FlipClock.Lang.french=FlipClock.Lang.French}(jQuery),function(){FlipClock.Lang.Italian={years:"Anni",months:"Mesi",days:"Giorni",hours:"Ore",minutes:"Minuti",seconds:"Secondi"},FlipClock.Lang.it=FlipClock.Lang.Italian,FlipClock.Lang["it-it"]=FlipClock.Lang.Italian,FlipClock.Lang.italian=FlipClock.Lang.Italian}(jQuery),function(){FlipClock.Lang.Latvian={years:"Gadi",months:"Mēneši",days:"Dienas",hours:"Stundas",minutes:"Minūtes",seconds:"Sekundes"},FlipClock.Lang.lv=FlipClock.Lang.Latvian,FlipClock.Lang["lv-lv"]=FlipClock.Lang.Latvian,FlipClock.Lang.latvian=FlipClock.Lang.Latvian}(jQuery),function(){FlipClock.Lang.Dutch={years:"Jaren",months:"Maanden",days:"Dagen",hours:"Uren",minutes:"Minuten",seconds:"Seconden"},FlipClock.Lang.nl=FlipClock.Lang.Dutch,FlipClock.Lang["nl-be"]=FlipClock.Lang.Dutch,FlipClock.Lang.dutch=FlipClock.Lang.Dutch}(jQuery),function(){FlipClock.Lang.Norwegian={years:"År",months:"Måneder",days:"Dager",hours:"Timer",minutes:"Minutter",seconds:"Sekunder"},FlipClock.Lang.no=FlipClock.Lang.Norwegian,FlipClock.Lang.nb=FlipClock.Lang.Norwegian,FlipClock.Lang["no-nb"]=FlipClock.Lang.Norwegian,FlipClock.Lang.norwegian=FlipClock.Lang.Norwegian}(jQuery),function(){FlipClock.Lang.Portuguese={years:"Anos",months:"Meses",days:"Dias",hours:"Horas",minutes:"Minutos",seconds:"Segundos"},FlipClock.Lang.pt=FlipClock.Lang.Portuguese,FlipClock.Lang["pt-br"]=FlipClock.Lang.Portuguese,FlipClock.Lang.portuguese=FlipClock.Lang.Portuguese}(jQuery),function(){FlipClock.Lang.Russian={years:"лет",months:"месяцев",days:"дней",hours:"часов",minutes:"минут",seconds:"секунд"},FlipClock.Lang.ru=FlipClock.Lang.Russian,FlipClock.Lang["ru-ru"]=FlipClock.Lang.Russian,FlipClock.Lang.russian=FlipClock.Lang.Russian}(jQuery),function(){FlipClock.Lang.Swedish={years:"År",months:"Månader",days:"Dagar",hours:"Timmar",minutes:"Minuter",seconds:"Sekunder"},FlipClock.Lang.sv=FlipClock.Lang.Swedish,FlipClock.Lang["sv-se"]=FlipClock.Lang.Swedish,FlipClock.Lang.swedish=FlipClock.Lang.Swedish}(jQuery);
/*!
 * Select2 4.0.13
 * https://select2.github.io
 *
 * Released under the MIT license
 * https://github.com/select2/select2/blob/master/LICENSE.md
 */

;(function (factory) {
  if (typeof define === 'function' && define.amd) {
    // AMD. Register as an anonymous module.
    define(['jquery'], factory);
  } else if (typeof module === 'object' && module.exports) {
    // Node/CommonJS
    module.exports = function (root, jQuery) {
      if (jQuery === undefined) {
        // require('jQuery') returns a factory that requires window to
        // build a jQuery instance, we normalize how we use modules
        // that require this pattern but the window provided is a noop
        // if it's defined (how jquery works)
        if (typeof window !== 'undefined') {
          jQuery = require('jquery');
        }
        else {
          jQuery = require('jquery')(root);
        }
      }
      factory(jQuery);
      return jQuery;
    };
  } else {
    // Browser globals
    factory(jQuery);
  }
} (function (jQuery) {
  // This is needed so we can catch the AMD loader configuration and use it
  // The inner file should be wrapped (by `banner.start.js`) in a function that
  // returns the AMD loader references.
  var S2 =(function () {
  // Restore the Select2 AMD loader so it can be used
  // Needed mostly in the language files, where the loader is not inserted
  if (jQuery && jQuery.fn && jQuery.fn.select2 && jQuery.fn.select2.amd) {
    var S2 = jQuery.fn.select2.amd;
  }
var S2;(function () { if (!S2 || !S2.requirejs) {
if (!S2) { S2 = {}; } else { require = S2; }
/**
 * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
 * Released under MIT license, http://github.com/requirejs/almond/LICENSE
 */
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*global setTimeout: false */

var requirejs, require, define;
(function (undef) {
    var main, req, makeMap, handlers,
        defined = {},
        waiting = {},
        config = {},
        defining = {},
        hasOwn = Object.prototype.hasOwnProperty,
        aps = [].slice,
        jsSuffixRegExp = /\.js$/;

    function hasProp(obj, prop) {
        return hasOwn.call(obj, prop);
    }

    /**
     * Given a relative module name, like ./something, normalize it to
     * a real name that can be mapped to a path.
     * @param {String} name the relative name
     * @param {String} baseName a real name that the name arg is relative
     * to.
     * @returns {String} normalized name
     */
    function normalize(name, baseName) {
        var nameParts, nameSegment, mapValue, foundMap, lastIndex,
            foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
            baseParts = baseName && baseName.split("/"),
            map = config.map,
            starMap = (map && map['*']) || {};

        //Adjust any relative paths.
        if (name) {
            name = name.split('/');
            lastIndex = name.length - 1;

            // If wanting node ID compatibility, strip .js from end
            // of IDs. Have to do this here, and not in nameToUrl
            // because node allows either .js or non .js to map
            // to same file.
            if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
                name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
            }

            // Starts with a '.' so need the baseName
            if (name[0].charAt(0) === '.' && baseParts) {
                //Convert baseName to array, and lop off the last part,
                //so that . matches that 'directory' and not name of the baseName's
                //module. For instance, baseName of 'one/two/three', maps to
                //'one/two/three.js', but we want the directory, 'one/two' for
                //this normalization.
                normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
                name = normalizedBaseParts.concat(name);
            }

            //start trimDots
            for (i = 0; i < name.length; i++) {
                part = name[i];
                if (part === '.') {
                    name.splice(i, 1);
                    i -= 1;
                } else if (part === '..') {
                    // If at the start, or previous value is still ..,
                    // keep them so that when converted to a path it may
                    // still work when converted to a path, even though
                    // as an ID it is less than ideal. In larger point
                    // releases, may be better to just kick out an error.
                    if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
                        continue;
                    } else if (i > 0) {
                        name.splice(i - 1, 2);
                        i -= 2;
                    }
                }
            }
            //end trimDots

            name = name.join('/');
        }

        //Apply map config if available.
        if ((baseParts || starMap) && map) {
            nameParts = name.split('/');

            for (i = nameParts.length; i > 0; i -= 1) {
                nameSegment = nameParts.slice(0, i).join("/");

                if (baseParts) {
                    //Find the longest baseName segment match in the config.
                    //So, do joins on the biggest to smallest lengths of baseParts.
                    for (j = baseParts.length; j > 0; j -= 1) {
                        mapValue = map[baseParts.slice(0, j).join('/')];

                        //baseName segment has  config, find if it has one for
                        //this name.
                        if (mapValue) {
                            mapValue = mapValue[nameSegment];
                            if (mapValue) {
                                //Match, update name to the new value.
                                foundMap = mapValue;
                                foundI = i;
                                break;
                            }
                        }
                    }
                }

                if (foundMap) {
                    break;
                }

                //Check for a star map match, but just hold on to it,
                //if there is a shorter segment match later in a matching
                //config, then favor over this star map.
                if (!foundStarMap && starMap && starMap[nameSegment]) {
                    foundStarMap = starMap[nameSegment];
                    starI = i;
                }
            }

            if (!foundMap && foundStarMap) {
                foundMap = foundStarMap;
                foundI = starI;
            }

            if (foundMap) {
                nameParts.splice(0, foundI, foundMap);
                name = nameParts.join('/');
            }
        }

        return name;
    }

    function makeRequire(relName, forceSync) {
        return function () {
            //A version of a require function that passes a moduleName
            //value for items that may need to
            //look up paths relative to the moduleName
            var args = aps.call(arguments, 0);

            //If first arg is not require('string'), and there is only
            //one arg, it is the array form without a callback. Insert
            //a null so that the following concat is correct.
            if (typeof args[0] !== 'string' && args.length === 1) {
                args.push(null);
            }
            return req.apply(undef, args.concat([relName, forceSync]));
        };
    }

    function makeNormalize(relName) {
        return function (name) {
            return normalize(name, relName);
        };
    }

    function makeLoad(depName) {
        return function (value) {
            defined[depName] = value;
        };
    }

    function callDep(name) {
        if (hasProp(waiting, name)) {
            var args = waiting[name];
            delete waiting[name];
            defining[name] = true;
            main.apply(undef, args);
        }

        if (!hasProp(defined, name) && !hasProp(defining, name)) {
            throw new Error('No ' + name);
        }
        return defined[name];
    }

    //Turns a plugin!resource to [plugin, resource]
    //with the plugin being undefined if the name
    //did not have a plugin prefix.
    function splitPrefix(name) {
        var prefix,
            index = name ? name.indexOf('!') : -1;
        if (index > -1) {
            prefix = name.substring(0, index);
            name = name.substring(index + 1, name.length);
        }
        return [prefix, name];
    }

    //Creates a parts array for a relName where first part is plugin ID,
    //second part is resource ID. Assumes relName has already been normalized.
    function makeRelParts(relName) {
        return relName ? splitPrefix(relName) : [];
    }

    /**
     * Makes a name map, normalizing the name, and using a plugin
     * for normalization if necessary. Grabs a ref to plugin
     * too, as an optimization.
     */
    makeMap = function (name, relParts) {
        var plugin,
            parts = splitPrefix(name),
            prefix = parts[0],
            relResourceName = relParts[1];

        name = parts[1];

        if (prefix) {
            prefix = normalize(prefix, relResourceName);
            plugin = callDep(prefix);
        }

        //Normalize according
        if (prefix) {
            if (plugin && plugin.normalize) {
                name = plugin.normalize(name, makeNormalize(relResourceName));
            } else {
                name = normalize(name, relResourceName);
            }
        } else {
            name = normalize(name, relResourceName);
            parts = splitPrefix(name);
            prefix = parts[0];
            name = parts[1];
            if (prefix) {
                plugin = callDep(prefix);
            }
        }

        //Using ridiculous property names for space reasons
        return {
            f: prefix ? prefix + '!' + name : name, //fullName
            n: name,
            pr: prefix,
            p: plugin
        };
    };

    function makeConfig(name) {
        return function () {
            return (config && config.config && config.config[name]) || {};
        };
    }

    handlers = {
        require: function (name) {
            return makeRequire(name);
        },
        exports: function (name) {
            var e = defined[name];
            if (typeof e !== 'undefined') {
                return e;
            } else {
                return (defined[name] = {});
            }
        },
        module: function (name) {
            return {
                id: name,
                uri: '',
                exports: defined[name],
                config: makeConfig(name)
            };
        }
    };

    main = function (name, deps, callback, relName) {
        var cjsModule, depName, ret, map, i, relParts,
            args = [],
            callbackType = typeof callback,
            usingExports;

        //Use name if no relName
        relName = relName || name;
        relParts = makeRelParts(relName);

        //Call the callback to define the module, if necessary.
        if (callbackType === 'undefined' || callbackType === 'function') {
            //Pull out the defined dependencies and pass the ordered
            //values to the callback.
            //Default to [require, exports, module] if no deps
            deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
            for (i = 0; i < deps.length; i += 1) {
                map = makeMap(deps[i], relParts);
                depName = map.f;

                //Fast path CommonJS standard dependencies.
                if (depName === "require") {
                    args[i] = handlers.require(name);
                } else if (depName === "exports") {
                    //CommonJS module spec 1.1
                    args[i] = handlers.exports(name);
                    usingExports = true;
                } else if (depName === "module") {
                    //CommonJS module spec 1.1
                    cjsModule = args[i] = handlers.module(name);
                } else if (hasProp(defined, depName) ||
                           hasProp(waiting, depName) ||
                           hasProp(defining, depName)) {
                    args[i] = callDep(depName);
                } else if (map.p) {
                    map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
                    args[i] = defined[depName];
                } else {
                    throw new Error(name + ' missing ' + depName);
                }
            }

            ret = callback ? callback.apply(defined[name], args) : undefined;

            if (name) {
                //If setting exports via "module" is in play,
                //favor that over return value and exports. After that,
                //favor a non-undefined return value over exports use.
                if (cjsModule && cjsModule.exports !== undef &&
                        cjsModule.exports !== defined[name]) {
                    defined[name] = cjsModule.exports;
                } else if (ret !== undef || !usingExports) {
                    //Use the return value from the function.
                    defined[name] = ret;
                }
            }
        } else if (name) {
            //May just be an object definition for the module. Only
            //worry about defining if have a module name.
            defined[name] = callback;
        }
    };

    requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
        if (typeof deps === "string") {
            if (handlers[deps]) {
                //callback in this case is really relName
                return handlers[deps](callback);
            }
            //Just return the module wanted. In this scenario, the
            //deps arg is the module name, and second arg (if passed)
            //is just the relName.
            //Normalize module name, if it contains . or ..
            return callDep(makeMap(deps, makeRelParts(callback)).f);
        } else if (!deps.splice) {
            //deps is a config object, not an array.
            config = deps;
            if (config.deps) {
                req(config.deps, config.callback);
            }
            if (!callback) {
                return;
            }

            if (callback.splice) {
                //callback is an array, which means it is a dependency list.
                //Adjust args if there are dependencies
                deps = callback;
                callback = relName;
                relName = null;
            } else {
                deps = undef;
            }
        }

        //Support require(['a'])
        callback = callback || function () {};

        //If relName is a function, it is an errback handler,
        //so remove it.
        if (typeof relName === 'function') {
            relName = forceSync;
            forceSync = alt;
        }

        //Simulate async callback;
        if (forceSync) {
            main(undef, deps, callback, relName);
        } else {
            //Using a non-zero value because of concern for what old browsers
            //do, and latest browsers "upgrade" to 4 if lower value is used:
            //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
            //If want a value immediately, use require('id') instead -- something
            //that works in almond on the global level, but not guaranteed and
            //unlikely to work in other AMD implementations.
            setTimeout(function () {
                main(undef, deps, callback, relName);
            }, 4);
        }

        return req;
    };

    /**
     * Just drops the config on the floor, but returns req in case
     * the config return value is used.
     */
    req.config = function (cfg) {
        return req(cfg);
    };

    /**
     * Expose module registry for debugging and tooling
     */
    requirejs._defined = defined;

    define = function (name, deps, callback) {
        if (typeof name !== 'string') {
            throw new Error('See almond README: incorrect module build, no module name');
        }

        //This module may not have dependencies
        if (!deps.splice) {
            //deps is not an array, so probably means
            //an object literal or factory function for
            //the value. Adjust args.
            callback = deps;
            deps = [];
        }

        if (!hasProp(defined, name) && !hasProp(waiting, name)) {
            waiting[name] = [name, deps, callback];
        }
    };

    define.amd = {
        jQuery: true
    };
}());

S2.requirejs = requirejs;S2.require = require;S2.define = define;
}
}());
S2.define("almond", function(){});

/* global jQuery:false, $:false */
S2.define('jquery',[],function () {
  var _$ = jQuery || $;

  if (_$ == null && console && console.error) {
    console.error(
      'Select2: An instance of jQuery or a jQuery-compatible library was not ' +
      'found. Make sure that you are including jQuery before Select2 on your ' +
      'web page.'
    );
  }

  return _$;
});

S2.define('select2/utils',[
  'jquery'
], function ($) {
  var Utils = {};

  Utils.Extend = function (ChildClass, SuperClass) {
    var __hasProp = {}.hasOwnProperty;

    function BaseConstructor () {
      this.constructor = ChildClass;
    }

    for (var key in SuperClass) {
      if (__hasProp.call(SuperClass, key)) {
        ChildClass[key] = SuperClass[key];
      }
    }

    BaseConstructor.prototype = SuperClass.prototype;
    ChildClass.prototype = new BaseConstructor();
    ChildClass.__super__ = SuperClass.prototype;

    return ChildClass;
  };

  function getMethods (theClass) {
    var proto = theClass.prototype;

    var methods = [];

    for (var methodName in proto) {
      var m = proto[methodName];

      if (typeof m !== 'function') {
        continue;
      }

      if (methodName === 'constructor') {
        continue;
      }

      methods.push(methodName);
    }

    return methods;
  }

  Utils.Decorate = function (SuperClass, DecoratorClass) {
    var decoratedMethods = getMethods(DecoratorClass);
    var superMethods = getMethods(SuperClass);

    function DecoratedClass () {
      var unshift = Array.prototype.unshift;

      var argCount = DecoratorClass.prototype.constructor.length;

      var calledConstructor = SuperClass.prototype.constructor;

      if (argCount > 0) {
        unshift.call(arguments, SuperClass.prototype.constructor);

        calledConstructor = DecoratorClass.prototype.constructor;
      }

      calledConstructor.apply(this, arguments);
    }

    DecoratorClass.displayName = SuperClass.displayName;

    function ctr () {
      this.constructor = DecoratedClass;
    }

    DecoratedClass.prototype = new ctr();

    for (var m = 0; m < superMethods.length; m++) {
      var superMethod = superMethods[m];

      DecoratedClass.prototype[superMethod] =
        SuperClass.prototype[superMethod];
    }

    var calledMethod = function (methodName) {
      // Stub out the original method if it's not decorating an actual method
      var originalMethod = function () {};

      if (methodName in DecoratedClass.prototype) {
        originalMethod = DecoratedClass.prototype[methodName];
      }

      var decoratedMethod = DecoratorClass.prototype[methodName];

      return function () {
        var unshift = Array.prototype.unshift;

        unshift.call(arguments, originalMethod);

        return decoratedMethod.apply(this, arguments);
      };
    };

    for (var d = 0; d < decoratedMethods.length; d++) {
      var decoratedMethod = decoratedMethods[d];

      DecoratedClass.prototype[decoratedMethod] = calledMethod(decoratedMethod);
    }

    return DecoratedClass;
  };

  var Observable = function () {
    this.listeners = {};
  };

  Observable.prototype.on = function (event, callback) {
    this.listeners = this.listeners || {};

    if (event in this.listeners) {
      this.listeners[event].push(callback);
    } else {
      this.listeners[event] = [callback];
    }
  };

  Observable.prototype.trigger = function (event) {
    var slice = Array.prototype.slice;
    var params = slice.call(arguments, 1);

    this.listeners = this.listeners || {};

    // Params should always come in as an array
    if (params == null) {
      params = [];
    }

    // If there are no arguments to the event, use a temporary object
    if (params.length === 0) {
      params.push({});
    }

    // Set the `_type` of the first object to the event
    params[0]._type = event;

    if (event in this.listeners) {
      this.invoke(this.listeners[event], slice.call(arguments, 1));
    }

    if ('*' in this.listeners) {
      this.invoke(this.listeners['*'], arguments);
    }
  };

  Observable.prototype.invoke = function (listeners, params) {
    for (var i = 0, len = listeners.length; i < len; i++) {
      listeners[i].apply(this, params);
    }
  };

  Utils.Observable = Observable;

  Utils.generateChars = function (length) {
    var chars = '';

    for (var i = 0; i < length; i++) {
      var randomChar = Math.floor(Math.random() * 36);
      chars += randomChar.toString(36);
    }

    return chars;
  };

  Utils.bind = function (func, context) {
    return function () {
      func.apply(context, arguments);
    };
  };

  Utils._convertData = function (data) {
    for (var originalKey in data) {
      var keys = originalKey.split('-');

      var dataLevel = data;

      if (keys.length === 1) {
        continue;
      }

      for (var k = 0; k < keys.length; k++) {
        var key = keys[k];

        // Lowercase the first letter
        // By default, dash-separated becomes camelCase
        key = key.substring(0, 1).toLowerCase() + key.substring(1);

        if (!(key in dataLevel)) {
          dataLevel[key] = {};
        }

        if (k == keys.length - 1) {
          dataLevel[key] = data[originalKey];
        }

        dataLevel = dataLevel[key];
      }

      delete data[originalKey];
    }

    return data;
  };

  Utils.hasScroll = function (index, el) {
    // Adapted from the function created by @ShadowScripter
    // and adapted by @BillBarry on the Stack Exchange Code Review website.
    // The original code can be found at
    // http://codereview.stackexchange.com/q/13338
    // and was designed to be used with the Sizzle selector engine.

    var $el = $(el);
    var overflowX = el.style.overflowX;
    var overflowY = el.style.overflowY;

    //Check both x and y declarations
    if (overflowX === overflowY &&
        (overflowY === 'hidden' || overflowY === 'visible')) {
      return false;
    }

    if (overflowX === 'scroll' || overflowY === 'scroll') {
      return true;
    }

    return ($el.innerHeight() < el.scrollHeight ||
      $el.innerWidth() < el.scrollWidth);
  };

  Utils.escapeMarkup = function (markup) {
    var replaceMap = {
      '\\': '&#92;',
      '&': '&amp;',
      '<': '&lt;',
      '>': '&gt;',
      '"': '&quot;',
      '\'': '&#39;',
      '/': '&#47;'
    };

    // Do not try to escape the markup if it's not a string
    if (typeof markup !== 'string') {
      return markup;
    }

    return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
      return replaceMap[match];
    });
  };

  // Append an array of jQuery nodes to a given element.
  Utils.appendMany = function ($element, $nodes) {
    // jQuery 1.7.x does not support $.fn.append() with an array
    // Fall back to a jQuery object collection using $.fn.add()
    if ($.fn.jquery.substr(0, 3) === '1.7') {
      var $jqNodes = $();

      $.map($nodes, function (node) {
        $jqNodes = $jqNodes.add(node);
      });

      $nodes = $jqNodes;
    }

    $element.append($nodes);
  };

  // Cache objects in Utils.__cache instead of $.data (see #4346)
  Utils.__cache = {};

  var id = 0;
  Utils.GetUniqueElementId = function (element) {
    // Get a unique element Id. If element has no id,
    // creates a new unique number, stores it in the id
    // attribute and returns the new id.
    // If an id already exists, it simply returns it.

    var select2Id = element.getAttribute('data-select2-id');
    if (select2Id == null) {
      // If element has id, use it.
      if (element.id) {
        select2Id = element.id;
        element.setAttribute('data-select2-id', select2Id);
      } else {
        element.setAttribute('data-select2-id', ++id);
        select2Id = id.toString();
      }
    }
    return select2Id;
  };

  Utils.StoreData = function (element, name, value) {
    // Stores an item in the cache for a specified element.
    // name is the cache key.
    var id = Utils.GetUniqueElementId(element);
    if (!Utils.__cache[id]) {
      Utils.__cache[id] = {};
    }

    Utils.__cache[id][name] = value;
  };

  Utils.GetData = function (element, name) {
    // Retrieves a value from the cache by its key (name)
    // name is optional. If no name specified, return
    // all cache items for the specified element.
    // and for a specified element.
    var id = Utils.GetUniqueElementId(element);
    if (name) {
      if (Utils.__cache[id]) {
        if (Utils.__cache[id][name] != null) {
          return Utils.__cache[id][name];
        }
        return $(element).data(name); // Fallback to HTML5 data attribs.
      }
      return $(element).data(name); // Fallback to HTML5 data attribs.
    } else {
      return Utils.__cache[id];
    }
  };

  Utils.RemoveData = function (element) {
    // Removes all cached items for a specified element.
    var id = Utils.GetUniqueElementId(element);
    if (Utils.__cache[id] != null) {
      delete Utils.__cache[id];
    }

    element.removeAttribute('data-select2-id');
  };

  return Utils;
});

S2.define('select2/results',[
  'jquery',
  './utils'
], function ($, Utils) {
  function Results ($element, options, dataAdapter) {
    this.$element = $element;
    this.data = dataAdapter;
    this.options = options;

    Results.__super__.constructor.call(this);
  }

  Utils.Extend(Results, Utils.Observable);

  Results.prototype.render = function () {
    var $results = $(
      '<ul class="select2-results__options" role="listbox"></ul>'
    );

    if (this.options.get('multiple')) {
      $results.attr('aria-multiselectable', 'true');
    }

    this.$results = $results;

    return $results;
  };

  Results.prototype.clear = function () {
    this.$results.empty();
  };

  Results.prototype.displayMessage = function (params) {
    var escapeMarkup = this.options.get('escapeMarkup');

    this.clear();
    this.hideLoading();

    var $message = $(
      '<li role="alert" aria-live="assertive"' +
      ' class="select2-results__option"></li>'
    );

    var message = this.options.get('translations').get(params.message);

    $message.append(
      escapeMarkup(
        message(params.args)
      )
    );

    $message[0].className += ' select2-results__message';

    this.$results.append($message);
  };

  Results.prototype.hideMessages = function () {
    this.$results.find('.select2-results__message').remove();
  };

  Results.prototype.append = function (data) {
    this.hideLoading();

    var $options = [];

    if (data.results == null || data.results.length === 0) {
      if (this.$results.children().length === 0) {
        this.trigger('results:message', {
          message: 'noResults'
        });
      }

      return;
    }

    data.results = this.sort(data.results);

    for (var d = 0; d < data.results.length; d++) {
      var item = data.results[d];

      var $option = this.option(item);

      $options.push($option);
    }

    this.$results.append($options);
  };

  Results.prototype.position = function ($results, $dropdown) {
    var $resultsContainer = $dropdown.find('.select2-results');
    $resultsContainer.append($results);
  };

  Results.prototype.sort = function (data) {
    var sorter = this.options.get('sorter');

    return sorter(data);
  };

  Results.prototype.highlightFirstItem = function () {
    var $options = this.$results
      .find('.select2-results__option[aria-selected]');

    var $selected = $options.filter('[aria-selected=true]');

    // Check if there are any selected options
    if ($selected.length > 0) {
      // If there are selected options, highlight the first
      $selected.first().trigger('mouseenter');
    } else {
      // If there are no selected options, highlight the first option
      // in the dropdown
      $options.first().trigger('mouseenter');
    }

    this.ensureHighlightVisible();
  };

  Results.prototype.setClasses = function () {
    var self = this;

    this.data.current(function (selected) {
      var selectedIds = $.map(selected, function (s) {
        return s.id.toString();
      });

      var $options = self.$results
        .find('.select2-results__option[aria-selected]');

      $options.each(function () {
        var $option = $(this);

        var item = Utils.GetData(this, 'data');

        // id needs to be converted to a string when comparing
        var id = '' + item.id;

        if ((item.element != null && item.element.selected) ||
            (item.element == null && $.inArray(id, selectedIds) > -1)) {
          $option.attr('aria-selected', 'true');
        } else {
          $option.attr('aria-selected', 'false');
        }
      });

    });
  };

  Results.prototype.showLoading = function (params) {
    this.hideLoading();

    var loadingMore = this.options.get('translations').get('searching');

    var loading = {
      disabled: true,
      loading: true,
      text: loadingMore(params)
    };
    var $loading = this.option(loading);
    $loading.className += ' loading-results';

    this.$results.prepend($loading);
  };

  Results.prototype.hideLoading = function () {
    this.$results.find('.loading-results').remove();
  };

  Results.prototype.option = function (data) {
    var option = document.createElement('li');
    option.className = 'select2-results__option';

    var attrs = {
      'role': 'option',
      'aria-selected': 'false'
    };

    var matches = window.Element.prototype.matches ||
      window.Element.prototype.msMatchesSelector ||
      window.Element.prototype.webkitMatchesSelector;

    if ((data.element != null && matches.call(data.element, ':disabled')) ||
        (data.element == null && data.disabled)) {
      delete attrs['aria-selected'];
      attrs['aria-disabled'] = 'true';
    }

    if (data.id == null) {
      delete attrs['aria-selected'];
    }

    if (data._resultId != null) {
      option.id = data._resultId;
    }

    if (data.title) {
      option.title = data.title;
    }

    if (data.children) {
      attrs.role = 'group';
      attrs['aria-label'] = data.text;
      delete attrs['aria-selected'];
    }

    for (var attr in attrs) {
      var val = attrs[attr];

      option.setAttribute(attr, val);
    }

    if (data.children) {
      var $option = $(option);

      var label = document.createElement('strong');
      label.className = 'select2-results__group';

      var $label = $(label);
      this.template(data, label);

      var $children = [];

      for (var c = 0; c < data.children.length; c++) {
        var child = data.children[c];

        var $child = this.option(child);

        $children.push($child);
      }

      var $childrenContainer = $('<ul></ul>', {
        'class': 'select2-results__options select2-results__options--nested'
      });

      $childrenContainer.append($children);

      $option.append(label);
      $option.append($childrenContainer);
    } else {
      this.template(data, option);
    }

    Utils.StoreData(option, 'data', data);

    return option;
  };

  Results.prototype.bind = function (container, $container) {
    var self = this;

    var id = container.id + '-results';

    this.$results.attr('id', id);

    container.on('results:all', function (params) {
      self.clear();
      self.append(params.data);

      if (container.isOpen()) {
        self.setClasses();
        self.highlightFirstItem();
      }
    });

    container.on('results:append', function (params) {
      self.append(params.data);

      if (container.isOpen()) {
        self.setClasses();
      }
    });

    container.on('query', function (params) {
      self.hideMessages();
      self.showLoading(params);
    });

    container.on('select', function () {
      if (!container.isOpen()) {
        return;
      }

      self.setClasses();

      if (self.options.get('scrollAfterSelect')) {
        self.highlightFirstItem();
      }
    });

    container.on('unselect', function () {
      if (!container.isOpen()) {
        return;
      }

      self.setClasses();

      if (self.options.get('scrollAfterSelect')) {
        self.highlightFirstItem();
      }
    });

    container.on('open', function () {
      // When the dropdown is open, aria-expended="true"
      self.$results.attr('aria-expanded', 'true');
      self.$results.attr('aria-hidden', 'false');

      self.setClasses();
      self.ensureHighlightVisible();
    });

    container.on('close', function () {
      // When the dropdown is closed, aria-expended="false"
      self.$results.attr('aria-expanded', 'false');
      self.$results.attr('aria-hidden', 'true');
      self.$results.removeAttr('aria-activedescendant');
    });

    container.on('results:toggle', function () {
      var $highlighted = self.getHighlightedResults();

      if ($highlighted.length === 0) {
        return;
      }

      $highlighted.trigger('mouseup');
    });

    container.on('results:select', function () {
      var $highlighted = self.getHighlightedResults();

      if ($highlighted.length === 0) {
        return;
      }

      var data = Utils.GetData($highlighted[0], 'data');

      if ($highlighted.attr('aria-selected') == 'true') {
        self.trigger('close', {});
      } else {
        self.trigger('select', {
          data: data
        });
      }
    });

    container.on('results:previous', function () {
      var $highlighted = self.getHighlightedResults();

      var $options = self.$results.find('[aria-selected]');

      var currentIndex = $options.index($highlighted);

      // If we are already at the top, don't move further
      // If no options, currentIndex will be -1
      if (currentIndex <= 0) {
        return;
      }

      var nextIndex = currentIndex - 1;

      // If none are highlighted, highlight the first
      if ($highlighted.length === 0) {
        nextIndex = 0;
      }

      var $next = $options.eq(nextIndex);

      $next.trigger('mouseenter');

      var currentOffset = self.$results.offset().top;
      var nextTop = $next.offset().top;
      var nextOffset = self.$results.scrollTop() + (nextTop - currentOffset);

      if (nextIndex === 0) {
        self.$results.scrollTop(0);
      } else if (nextTop - currentOffset < 0) {
        self.$results.scrollTop(nextOffset);
      }
    });

    container.on('results:next', function () {
      var $highlighted = self.getHighlightedResults();

      var $options = self.$results.find('[aria-selected]');

      var currentIndex = $options.index($highlighted);

      var nextIndex = currentIndex + 1;

      // If we are at the last option, stay there
      if (nextIndex >= $options.length) {
        return;
      }

      var $next = $options.eq(nextIndex);

      $next.trigger('mouseenter');

      var currentOffset = self.$results.offset().top +
        self.$results.outerHeight(false);
      var nextBottom = $next.offset().top + $next.outerHeight(false);
      var nextOffset = self.$results.scrollTop() + nextBottom - currentOffset;

      if (nextIndex === 0) {
        self.$results.scrollTop(0);
      } else if (nextBottom > currentOffset) {
        self.$results.scrollTop(nextOffset);
      }
    });

    container.on('results:focus', function (params) {
      params.element.addClass('select2-results__option--highlighted');
    });

    container.on('results:message', function (params) {
      self.displayMessage(params);
    });

    if ($.fn.mousewheel) {
      this.$results.on('mousewheel', function (e) {
        var top = self.$results.scrollTop();

        var bottom = self.$results.get(0).scrollHeight - top + e.deltaY;

        var isAtTop = e.deltaY > 0 && top - e.deltaY <= 0;
        var isAtBottom = e.deltaY < 0 && bottom <= self.$results.height();

        if (isAtTop) {
          self.$results.scrollTop(0);

          e.preventDefault();
          e.stopPropagation();
        } else if (isAtBottom) {
          self.$results.scrollTop(
            self.$results.get(0).scrollHeight - self.$results.height()
          );

          e.preventDefault();
          e.stopPropagation();
        }
      });
    }

    this.$results.on('mouseup', '.select2-results__option[aria-selected]',
      function (evt) {
      var $this = $(this);

      var data = Utils.GetData(this, 'data');

      if ($this.attr('aria-selected') === 'true') {
        if (self.options.get('multiple')) {
          self.trigger('unselect', {
            originalEvent: evt,
            data: data
          });
        } else {
          self.trigger('close', {});
        }

        return;
      }

      self.trigger('select', {
        originalEvent: evt,
        data: data
      });
    });

    this.$results.on('mouseenter', '.select2-results__option[aria-selected]',
      function (evt) {
      var data = Utils.GetData(this, 'data');

      self.getHighlightedResults()
          .removeClass('select2-results__option--highlighted');

      self.trigger('results:focus', {
        data: data,
        element: $(this)
      });
    });
  };

  Results.prototype.getHighlightedResults = function () {
    var $highlighted = this.$results
    .find('.select2-results__option--highlighted');

    return $highlighted;
  };

  Results.prototype.destroy = function () {
    this.$results.remove();
  };

  Results.prototype.ensureHighlightVisible = function () {
    var $highlighted = this.getHighlightedResults();

    if ($highlighted.length === 0) {
      return;
    }

    var $options = this.$results.find('[aria-selected]');

    var currentIndex = $options.index($highlighted);

    var currentOffset = this.$results.offset().top;
    var nextTop = $highlighted.offset().top;
    var nextOffset = this.$results.scrollTop() + (nextTop - currentOffset);

    var offsetDelta = nextTop - currentOffset;
    nextOffset -= $highlighted.outerHeight(false) * 2;

    if (currentIndex <= 2) {
      this.$results.scrollTop(0);
    } else if (offsetDelta > this.$results.outerHeight() || offsetDelta < 0) {
      this.$results.scrollTop(nextOffset);
    }
  };

  Results.prototype.template = function (result, container) {
    var template = this.options.get('templateResult');
    var escapeMarkup = this.options.get('escapeMarkup');

    var content = template(result, container);

    if (content == null) {
      container.style.display = 'none';
    } else if (typeof content === 'string') {
      container.innerHTML = escapeMarkup(content);
    } else {
      $(container).append(content);
    }
  };

  return Results;
});

S2.define('select2/keys',[

], function () {
  var KEYS = {
    BACKSPACE: 8,
    TAB: 9,
    ENTER: 13,
    SHIFT: 16,
    CTRL: 17,
    ALT: 18,
    ESC: 27,
    SPACE: 32,
    PAGE_UP: 33,
    PAGE_DOWN: 34,
    END: 35,
    HOME: 36,
    LEFT: 37,
    UP: 38,
    RIGHT: 39,
    DOWN: 40,
    DELETE: 46
  };

  return KEYS;
});

S2.define('select2/selection/base',[
  'jquery',
  '../utils',
  '../keys'
], function ($, Utils, KEYS) {
  function BaseSelection ($element, options) {
    this.$element = $element;
    this.options = options;

    BaseSelection.__super__.constructor.call(this);
  }

  Utils.Extend(BaseSelection, Utils.Observable);

  BaseSelection.prototype.render = function () {
    var $selection = $(
      '<span class="select2-selection" role="combobox" ' +
      ' aria-haspopup="true" aria-expanded="false">' +
      '</span>'
    );

    this._tabindex = 0;

    if (Utils.GetData(this.$element[0], 'old-tabindex') != null) {
      this._tabindex = Utils.GetData(this.$element[0], 'old-tabindex');
    } else if (this.$element.attr('tabindex') != null) {
      this._tabindex = this.$element.attr('tabindex');
    }

    $selection.attr('title', this.$element.attr('title'));
    $selection.attr('tabindex', this._tabindex);
    $selection.attr('aria-disabled', 'false');

    this.$selection = $selection;

    return $selection;
  };

  BaseSelection.prototype.bind = function (container, $container) {
    var self = this;

    var resultsId = container.id + '-results';

    this.container = container;

    this.$selection.on('focus', function (evt) {
      self.trigger('focus', evt);
    });

    this.$selection.on('blur', function (evt) {
      self._handleBlur(evt);
    });

    this.$selection.on('keydown', function (evt) {
      self.trigger('keypress', evt);

      if (evt.which === KEYS.SPACE) {
        evt.preventDefault();
      }
    });

    container.on('results:focus', function (params) {
      self.$selection.attr('aria-activedescendant', params.data._resultId);
    });

    container.on('selection:update', function (params) {
      self.update(params.data);
    });

    container.on('open', function () {
      // When the dropdown is open, aria-expanded="true"
      self.$selection.attr('aria-expanded', 'true');
      self.$selection.attr('aria-owns', resultsId);

      self._attachCloseHandler(container);
    });

    container.on('close', function () {
      // When the dropdown is closed, aria-expanded="false"
      self.$selection.attr('aria-expanded', 'false');
      self.$selection.removeAttr('aria-activedescendant');
      self.$selection.removeAttr('aria-owns');

      self.$selection.trigger('focus');

      self._detachCloseHandler(container);
    });

    container.on('enable', function () {
      self.$selection.attr('tabindex', self._tabindex);
      self.$selection.attr('aria-disabled', 'false');
    });

    container.on('disable', function () {
      self.$selection.attr('tabindex', '-1');
      self.$selection.attr('aria-disabled', 'true');
    });
  };

  BaseSelection.prototype._handleBlur = function (evt) {
    var self = this;

    // This needs to be delayed as the active element is the body when the tab
    // key is pressed, possibly along with others.
    window.setTimeout(function () {
      // Don't trigger `blur` if the focus is still in the selection
      if (
        (document.activeElement == self.$selection[0]) ||
        ($.contains(self.$selection[0], document.activeElement))
      ) {
        return;
      }

      self.trigger('blur', evt);
    }, 1);
  };

  BaseSelection.prototype._attachCloseHandler = function (container) {

    $(document.body).on('mousedown.select2.' + container.id, function (e) {
      var $target = $(e.target);

      var $select = $target.closest('.select2');

      var $all = $('.select2.select2-container--open');

      $all.each(function () {
        if (this == $select[0]) {
          return;
        }

        var $element = Utils.GetData(this, 'element');

        $element.select2('close');
      });
    });
  };

  BaseSelection.prototype._detachCloseHandler = function (container) {
    $(document.body).off('mousedown.select2.' + container.id);
  };

  BaseSelection.prototype.position = function ($selection, $container) {
    var $selectionContainer = $container.find('.selection');
    $selectionContainer.append($selection);
  };

  BaseSelection.prototype.destroy = function () {
    this._detachCloseHandler(this.container);
  };

  BaseSelection.prototype.update = function (data) {
    throw new Error('The `update` method must be defined in child classes.');
  };

  /**
   * Helper method to abstract the "enabled" (not "disabled") state of this
   * object.
   *
   * @return {true} if the instance is not disabled.
   * @return {false} if the instance is disabled.
   */
  BaseSelection.prototype.isEnabled = function () {
    return !this.isDisabled();
  };

  /**
   * Helper method to abstract the "disabled" state of this object.
   *
   * @return {true} if the disabled option is true.
   * @return {false} if the disabled option is false.
   */
  BaseSelection.prototype.isDisabled = function () {
    return this.options.get('disabled');
  };

  return BaseSelection;
});

S2.define('select2/selection/single',[
  'jquery',
  './base',
  '../utils',
  '../keys'
], function ($, BaseSelection, Utils, KEYS) {
  function SingleSelection () {
    SingleSelection.__super__.constructor.apply(this, arguments);
  }

  Utils.Extend(SingleSelection, BaseSelection);

  SingleSelection.prototype.render = function () {
    var $selection = SingleSelection.__super__.render.call(this);

    $selection.addClass('select2-selection--single');

    $selection.html(
      '<span class="select2-selection__rendered"></span>' +
      '<span class="select2-selection__arrow" role="presentation">' +
        '<b role="presentation"></b>' +
      '</span>'
    );

    return $selection;
  };

  SingleSelection.prototype.bind = function (container, $container) {
    var self = this;

    SingleSelection.__super__.bind.apply(this, arguments);

    var id = container.id + '-container';

    this.$selection.find('.select2-selection__rendered')
      .attr('id', id)
      .attr('role', 'textbox')
      .attr('aria-readonly', 'true');
    this.$selection.attr('aria-labelledby', id);

    this.$selection.on('mousedown', function (evt) {
      // Only respond to left clicks
      if (evt.which !== 1) {
        return;
      }

      self.trigger('toggle', {
        originalEvent: evt
      });
    });

    this.$selection.on('focus', function (evt) {
      // User focuses on the container
    });

    this.$selection.on('blur', function (evt) {
      // User exits the container
    });

    container.on('focus', function (evt) {
      if (!container.isOpen()) {
        self.$selection.trigger('focus');
      }
    });
  };

  SingleSelection.prototype.clear = function () {
    var $rendered = this.$selection.find('.select2-selection__rendered');
    $rendered.empty();
    $rendered.removeAttr('title'); // clear tooltip on empty
  };

  SingleSelection.prototype.display = function (data, container) {
    var template = this.options.get('templateSelection');
    var escapeMarkup = this.options.get('escapeMarkup');

    return escapeMarkup(template(data, container));
  };

  SingleSelection.prototype.selectionContainer = function () {
    return $('<span></span>');
  };

  SingleSelection.prototype.update = function (data) {
    if (data.length === 0) {
      this.clear();
      return;
    }

    var selection = data[0];

    var $rendered = this.$selection.find('.select2-selection__rendered');
    var formatted = this.display(selection, $rendered);

    $rendered.empty().append(formatted);

    var title = selection.title || selection.text;

    if (title) {
      $rendered.attr('title', title);
    } else {
      $rendered.removeAttr('title');
    }
  };

  return SingleSelection;
});

S2.define('select2/selection/multiple',[
  'jquery',
  './base',
  '../utils'
], function ($, BaseSelection, Utils) {
  function MultipleSelection ($element, options) {
    MultipleSelection.__super__.constructor.apply(this, arguments);
  }

  Utils.Extend(MultipleSelection, BaseSelection);

  MultipleSelection.prototype.render = function () {
    var $selection = MultipleSelection.__super__.render.call(this);

    $selection.addClass('select2-selection--multiple');

    $selection.html(
      '<ul class="select2-selection__rendered"></ul>'
    );

    return $selection;
  };

  MultipleSelection.prototype.bind = function (container, $container) {
    var self = this;

    MultipleSelection.__super__.bind.apply(this, arguments);

    this.$selection.on('click', function (evt) {
      self.trigger('toggle', {
        originalEvent: evt
      });
    });

    this.$selection.on(
      'click',
      '.select2-selection__choice__remove',
      function (evt) {
        // Ignore the event if it is disabled
        if (self.isDisabled()) {
          return;
        }

        var $remove = $(this);
        var $selection = $remove.parent();

        var data = Utils.GetData($selection[0], 'data');

        self.trigger('unselect', {
          originalEvent: evt,
          data: data
        });
      }
    );
  };

  MultipleSelection.prototype.clear = function () {
    var $rendered = this.$selection.find('.select2-selection__rendered');
    $rendered.empty();
    $rendered.removeAttr('title');
  };

  MultipleSelection.prototype.display = function (data, container) {
    var template = this.options.get('templateSelection');
    var escapeMarkup = this.options.get('escapeMarkup');

    return escapeMarkup(template(data, container));
  };

  MultipleSelection.prototype.selectionContainer = function () {
    var $container = $(
      '<li class="select2-selection__choice">' +
        '<span class="select2-selection__choice__remove" role="presentation">' +
          '&times;' +
        '</span>' +
      '</li>'
    );

    return $container;
  };

  MultipleSelection.prototype.update = function (data) {
    this.clear();

    if (data.length === 0) {
      return;
    }

    var $selections = [];

    for (var d = 0; d < data.length; d++) {
      var selection = data[d];

      var $selection = this.selectionContainer();
      var formatted = this.display(selection, $selection);

      $selection.append(formatted);

      var title = selection.title || selection.text;

      if (title) {
        $selection.attr('title', title);
      }

      Utils.StoreData($selection[0], 'data', selection);

      $selections.push($selection);
    }

    var $rendered = this.$selection.find('.select2-selection__rendered');

    Utils.appendMany($rendered, $selections);
  };

  return MultipleSelection;
});

S2.define('select2/selection/placeholder',[
  '../utils'
], function (Utils) {
  function Placeholder (decorated, $element, options) {
    this.placeholder = this.normalizePlaceholder(options.get('placeholder'));

    decorated.call(this, $element, options);
  }

  Placeholder.prototype.normalizePlaceholder = function (_, placeholder) {
    if (typeof placeholder === 'string') {
      placeholder = {
        id: '',
        text: placeholder
      };
    }

    return placeholder;
  };

  Placeholder.prototype.createPlaceholder = function (decorated, placeholder) {
    var $placeholder = this.selectionContainer();

    $placeholder.html(this.display(placeholder));
    $placeholder.addClass('select2-selection__placeholder')
                .removeClass('select2-selection__choice');

    return $placeholder;
  };

  Placeholder.prototype.update = function (decorated, data) {
    var singlePlaceholder = (
      data.length == 1 && data[0].id != this.placeholder.id
    );
    var multipleSelections = data.length > 1;

    if (multipleSelections || singlePlaceholder) {
      return decorated.call(this, data);
    }

    this.clear();

    var $placeholder = this.createPlaceholder(this.placeholder);

    this.$selection.find('.select2-selection__rendered').append($placeholder);
  };

  return Placeholder;
});

S2.define('select2/selection/allowClear',[
  'jquery',
  '../keys',
  '../utils'
], function ($, KEYS, Utils) {
  function AllowClear () { }

  AllowClear.prototype.bind = function (decorated, container, $container) {
    var self = this;

    decorated.call(this, container, $container);

    if (this.placeholder == null) {
      if (this.options.get('debug') && window.console && console.error) {
        console.error(
          'Select2: The `allowClear` option should be used in combination ' +
          'with the `placeholder` option.'
        );
      }
    }

    this.$selection.on('mousedown', '.select2-selection__clear',
      function (evt) {
        self._handleClear(evt);
    });

    container.on('keypress', function (evt) {
      self._handleKeyboardClear(evt, container);
    });
  };

  AllowClear.prototype._handleClear = function (_, evt) {
    // Ignore the event if it is disabled
    if (this.isDisabled()) {
      return;
    }

    var $clear = this.$selection.find('.select2-selection__clear');

    // Ignore the event if nothing has been selected
    if ($clear.length === 0) {
      return;
    }

    evt.stopPropagation();

    var data = Utils.GetData($clear[0], 'data');

    var previousVal = this.$element.val();
    this.$element.val(this.placeholder.id);

    var unselectData = {
      data: data
    };
    this.trigger('clear', unselectData);
    if (unselectData.prevented) {
      this.$element.val(previousVal);
      return;
    }

    for (var d = 0; d < data.length; d++) {
      unselectData = {
        data: data[d]
      };

      // Trigger the `unselect` event, so people can prevent it from being
      // cleared.
      this.trigger('unselect', unselectData);

      // If the event was prevented, don't clear it out.
      if (unselectData.prevented) {
        this.$element.val(previousVal);
        return;
      }
    }

    this.$element.trigger('input').trigger('change');

    this.trigger('toggle', {});
  };

  AllowClear.prototype._handleKeyboardClear = function (_, evt, container) {
    if (container.isOpen()) {
      return;
    }

    if (evt.which == KEYS.DELETE || evt.which == KEYS.BACKSPACE) {
      this._handleClear(evt);
    }
  };

  AllowClear.prototype.update = function (decorated, data) {
    decorated.call(this, data);

    if (this.$selection.find('.select2-selection__placeholder').length > 0 ||
        data.length === 0) {
      return;
    }

    var removeAll = this.options.get('translations').get('removeAllItems');

    var $remove = $(
      '<span class="select2-selection__clear" title="' + removeAll() +'">' +
        '&times;' +
      '</span>'
    );
    Utils.StoreData($remove[0], 'data', data);

    this.$selection.find('.select2-selection__rendered').prepend($remove);
  };

  return AllowClear;
});

S2.define('select2/selection/search',[
  'jquery',
  '../utils',
  '../keys'
], function ($, Utils, KEYS) {
  function Search (decorated, $element, options) {
    decorated.call(this, $element, options);
  }

  Search.prototype.render = function (decorated) {
    var $search = $(
      '<li class="select2-search select2-search--inline">' +
        '<input class="select2-search__field" type="search" tabindex="-1"' +
        ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
        ' spellcheck="false" role="searchbox" aria-autocomplete="list" />' +
      '</li>'
    );

    this.$searchContainer = $search;
    this.$search = $search.find('input');

    var $rendered = decorated.call(this);

    this._transferTabIndex();

    return $rendered;
  };

  Search.prototype.bind = function (decorated, container, $container) {
    var self = this;

    var resultsId = container.id + '-results';

    decorated.call(this, container, $container);

    container.on('open', function () {
      self.$search.attr('aria-controls', resultsId);
      self.$search.trigger('focus');
    });

    container.on('close', function () {
      self.$search.val('');
      self.$search.removeAttr('aria-controls');
      self.$search.removeAttr('aria-activedescendant');
      self.$search.trigger('focus');
    });

    container.on('enable', function () {
      self.$search.prop('disabled', false);

      self._transferTabIndex();
    });

    container.on('disable', function () {
      self.$search.prop('disabled', true);
    });

    container.on('focus', function (evt) {
      self.$search.trigger('focus');
    });

    container.on('results:focus', function (params) {
      if (params.data._resultId) {
        self.$search.attr('aria-activedescendant', params.data._resultId);
      } else {
        self.$search.removeAttr('aria-activedescendant');
      }
    });

    this.$selection.on('focusin', '.select2-search--inline', function (evt) {
      self.trigger('focus', evt);
    });

    this.$selection.on('focusout', '.select2-search--inline', function (evt) {
      self._handleBlur(evt);
    });

    this.$selection.on('keydown', '.select2-search--inline', function (evt) {
      evt.stopPropagation();

      self.trigger('keypress', evt);

      self._keyUpPrevented = evt.isDefaultPrevented();

      var key = evt.which;

      if (key === KEYS.BACKSPACE && self.$search.val() === '') {
        var $previousChoice = self.$searchContainer
          .prev('.select2-selection__choice');

        if ($previousChoice.length > 0) {
          var item = Utils.GetData($previousChoice[0], 'data');

          self.searchRemoveChoice(item);

          evt.preventDefault();
        }
      }
    });

    this.$selection.on('click', '.select2-search--inline', function (evt) {
      if (self.$search.val()) {
        evt.stopPropagation();
      }
    });

    // Try to detect the IE version should the `documentMode` property that
    // is stored on the document. This is only implemented in IE and is
    // slightly cleaner than doing a user agent check.
    // This property is not available in Edge, but Edge also doesn't have
    // this bug.
    var msie = document.documentMode;
    var disableInputEvents = msie && msie <= 11;

    // Workaround for browsers which do not support the `input` event
    // This will prevent double-triggering of events for browsers which support
    // both the `keyup` and `input` events.
    this.$selection.on(
      'input.searchcheck',
      '.select2-search--inline',
      function (evt) {
        // IE will trigger the `input` event when a placeholder is used on a
        // search box. To get around this issue, we are forced to ignore all
        // `input` events in IE and keep using `keyup`.
        if (disableInputEvents) {
          self.$selection.off('input.search input.searchcheck');
          return;
        }

        // Unbind the duplicated `keyup` event
        self.$selection.off('keyup.search');
      }
    );

    this.$selection.on(
      'keyup.search input.search',
      '.select2-search--inline',
      function (evt) {
        // IE will trigger the `input` event when a placeholder is used on a
        // search box. To get around this issue, we are forced to ignore all
        // `input` events in IE and keep using `keyup`.
        if (disableInputEvents && evt.type === 'input') {
          self.$selection.off('input.search input.searchcheck');
          return;
        }

        var key = evt.which;

        // We can freely ignore events from modifier keys
        if (key == KEYS.SHIFT || key == KEYS.CTRL || key == KEYS.ALT) {
          return;
        }

        // Tabbing will be handled during the `keydown` phase
        if (key == KEYS.TAB) {
          return;
        }

        self.handleSearch(evt);
      }
    );
  };

  /**
   * This method will transfer the tabindex attribute from the rendered
   * selection to the search box. This allows for the search box to be used as
   * the primary focus instead of the selection container.
   *
   * @private
   */
  Search.prototype._transferTabIndex = function (decorated) {
    this.$search.attr('tabindex', this.$selection.attr('tabindex'));
    this.$selection.attr('tabindex', '-1');
  };

  Search.prototype.createPlaceholder = function (decorated, placeholder) {
    this.$search.attr('placeholder', placeholder.text);
  };

  Search.prototype.update = function (decorated, data) {
    var searchHadFocus = this.$search[0] == document.activeElement;

    this.$search.attr('placeholder', '');

    decorated.call(this, data);

    this.$selection.find('.select2-selection__rendered')
                   .append(this.$searchContainer);

    this.resizeSearch();
    if (searchHadFocus) {
      this.$search.trigger('focus');
    }
  };

  Search.prototype.handleSearch = function () {
    this.resizeSearch();

    if (!this._keyUpPrevented) {
      var input = this.$search.val();

      this.trigger('query', {
        term: input
      });
    }

    this._keyUpPrevented = false;
  };

  Search.prototype.searchRemoveChoice = function (decorated, item) {
    this.trigger('unselect', {
      data: item
    });

    this.$search.val(item.text);
    this.handleSearch();
  };

  Search.prototype.resizeSearch = function () {
    this.$search.css('width', '25px');

    var width = '';

    if (this.$search.attr('placeholder') !== '') {
      width = this.$selection.find('.select2-selection__rendered').width();
    } else {
      var minimumWidth = this.$search.val().length + 1;

      width = (minimumWidth * 0.75) + 'em';
    }

    this.$search.css('width', width);
  };

  return Search;
});

S2.define('select2/selection/eventRelay',[
  'jquery'
], function ($) {
  function EventRelay () { }

  EventRelay.prototype.bind = function (decorated, container, $container) {
    var self = this;
    var relayEvents = [
      'open', 'opening',
      'close', 'closing',
      'select', 'selecting',
      'unselect', 'unselecting',
      'clear', 'clearing'
    ];

    var preventableEvents = [
      'opening', 'closing', 'selecting', 'unselecting', 'clearing'
    ];

    decorated.call(this, container, $container);

    container.on('*', function (name, params) {
      // Ignore events that should not be relayed
      if ($.inArray(name, relayEvents) === -1) {
        return;
      }

      // The parameters should always be an object
      params = params || {};

      // Generate the jQuery event for the Select2 event
      var evt = $.Event('select2:' + name, {
        params: params
      });

      self.$element.trigger(evt);

      // Only handle preventable events if it was one
      if ($.inArray(name, preventableEvents) === -1) {
        return;
      }

      params.prevented = evt.isDefaultPrevented();
    });
  };

  return EventRelay;
});

S2.define('select2/translation',[
  'jquery',
  'require'
], function ($, require) {
  function Translation (dict) {
    this.dict = dict || {};
  }

  Translation.prototype.all = function () {
    return this.dict;
  };

  Translation.prototype.get = function (key) {
    return this.dict[key];
  };

  Translation.prototype.extend = function (translation) {
    this.dict = $.extend({}, translation.all(), this.dict);
  };

  // Static functions

  Translation._cache = {};

  Translation.loadPath = function (path) {
    if (!(path in Translation._cache)) {
      var translations = require(path);

      Translation._cache[path] = translations;
    }

    return new Translation(Translation._cache[path]);
  };

  return Translation;
});

S2.define('select2/diacritics',[

], function () {
  var diacritics = {
    '\u24B6': 'A',
    '\uFF21': 'A',
    '\u00C0': 'A',
    '\u00C1': 'A',
    '\u00C2': 'A',
    '\u1EA6': 'A',
    '\u1EA4': 'A',
    '\u1EAA': 'A',
    '\u1EA8': 'A',
    '\u00C3': 'A',
    '\u0100': 'A',
    '\u0102': 'A',
    '\u1EB0': 'A',
    '\u1EAE': 'A',
    '\u1EB4': 'A',
    '\u1EB2': 'A',
    '\u0226': 'A',
    '\u01E0': 'A',
    '\u00C4': 'A',
    '\u01DE': 'A',
    '\u1EA2': 'A',
    '\u00C5': 'A',
    '\u01FA': 'A',
    '\u01CD': 'A',
    '\u0200': 'A',
    '\u0202': 'A',
    '\u1EA0': 'A',
    '\u1EAC': 'A',
    '\u1EB6': 'A',
    '\u1E00': 'A',
    '\u0104': 'A',
    '\u023A': 'A',
    '\u2C6F': 'A',
    '\uA732': 'AA',
    '\u00C6': 'AE',
    '\u01FC': 'AE',
    '\u01E2': 'AE',
    '\uA734': 'AO',
    '\uA736': 'AU',
    '\uA738': 'AV',
    '\uA73A': 'AV',
    '\uA73C': 'AY',
    '\u24B7': 'B',
    '\uFF22': 'B',
    '\u1E02': 'B',
    '\u1E04': 'B',
    '\u1E06': 'B',
    '\u0243': 'B',
    '\u0182': 'B',
    '\u0181': 'B',
    '\u24B8': 'C',
    '\uFF23': 'C',
    '\u0106': 'C',
    '\u0108': 'C',
    '\u010A': 'C',
    '\u010C': 'C',
    '\u00C7': 'C',
    '\u1E08': 'C',
    '\u0187': 'C',
    '\u023B': 'C',
    '\uA73E': 'C',
    '\u24B9': 'D',
    '\uFF24': 'D',
    '\u1E0A': 'D',
    '\u010E': 'D',
    '\u1E0C': 'D',
    '\u1E10': 'D',
    '\u1E12': 'D',
    '\u1E0E': 'D',
    '\u0110': 'D',
    '\u018B': 'D',
    '\u018A': 'D',
    '\u0189': 'D',
    '\uA779': 'D',
    '\u01F1': 'DZ',
    '\u01C4': 'DZ',
    '\u01F2': 'Dz',
    '\u01C5': 'Dz',
    '\u24BA': 'E',
    '\uFF25': 'E',
    '\u00C8': 'E',
    '\u00C9': 'E',
    '\u00CA': 'E',
    '\u1EC0': 'E',
    '\u1EBE': 'E',
    '\u1EC4': 'E',
    '\u1EC2': 'E',
    '\u1EBC': 'E',
    '\u0112': 'E',
    '\u1E14': 'E',
    '\u1E16': 'E',
    '\u0114': 'E',
    '\u0116': 'E',
    '\u00CB': 'E',
    '\u1EBA': 'E',
    '\u011A': 'E',
    '\u0204': 'E',
    '\u0206': 'E',
    '\u1EB8': 'E',
    '\u1EC6': 'E',
    '\u0228': 'E',
    '\u1E1C': 'E',
    '\u0118': 'E',
    '\u1E18': 'E',
    '\u1E1A': 'E',
    '\u0190': 'E',
    '\u018E': 'E',
    '\u24BB': 'F',
    '\uFF26': 'F',
    '\u1E1E': 'F',
    '\u0191': 'F',
    '\uA77B': 'F',
    '\u24BC': 'G',
    '\uFF27': 'G',
    '\u01F4': 'G',
    '\u011C': 'G',
    '\u1E20': 'G',
    '\u011E': 'G',
    '\u0120': 'G',
    '\u01E6': 'G',
    '\u0122': 'G',
    '\u01E4': 'G',
    '\u0193': 'G',
    '\uA7A0': 'G',
    '\uA77D': 'G',
    '\uA77E': 'G',
    '\u24BD': 'H',
    '\uFF28': 'H',
    '\u0124': 'H',
    '\u1E22': 'H',
    '\u1E26': 'H',
    '\u021E': 'H',
    '\u1E24': 'H',
    '\u1E28': 'H',
    '\u1E2A': 'H',
    '\u0126': 'H',
    '\u2C67': 'H',
    '\u2C75': 'H',
    '\uA78D': 'H',
    '\u24BE': 'I',
    '\uFF29': 'I',
    '\u00CC': 'I',
    '\u00CD': 'I',
    '\u00CE': 'I',
    '\u0128': 'I',
    '\u012A': 'I',
    '\u012C': 'I',
    '\u0130': 'I',
    '\u00CF': 'I',
    '\u1E2E': 'I',
    '\u1EC8': 'I',
    '\u01CF': 'I',
    '\u0208': 'I',
    '\u020A': 'I',
    '\u1ECA': 'I',
    '\u012E': 'I',
    '\u1E2C': 'I',
    '\u0197': 'I',
    '\u24BF': 'J',
    '\uFF2A': 'J',
    '\u0134': 'J',
    '\u0248': 'J',
    '\u24C0': 'K',
    '\uFF2B': 'K',
    '\u1E30': 'K',
    '\u01E8': 'K',
    '\u1E32': 'K',
    '\u0136': 'K',
    '\u1E34': 'K',
    '\u0198': 'K',
    '\u2C69': 'K',
    '\uA740': 'K',
    '\uA742': 'K',
    '\uA744': 'K',
    '\uA7A2': 'K',
    '\u24C1': 'L',
    '\uFF2C': 'L',
    '\u013F': 'L',
    '\u0139': 'L',
    '\u013D': 'L',
    '\u1E36': 'L',
    '\u1E38': 'L',
    '\u013B': 'L',
    '\u1E3C': 'L',
    '\u1E3A': 'L',
    '\u0141': 'L',
    '\u023D': 'L',
    '\u2C62': 'L',
    '\u2C60': 'L',
    '\uA748': 'L',
    '\uA746': 'L',
    '\uA780': 'L',
    '\u01C7': 'LJ',
    '\u01C8': 'Lj',
    '\u24C2': 'M',
    '\uFF2D': 'M',
    '\u1E3E': 'M',
    '\u1E40': 'M',
    '\u1E42': 'M',
    '\u2C6E': 'M',
    '\u019C': 'M',
    '\u24C3': 'N',
    '\uFF2E': 'N',
    '\u01F8': 'N',
    '\u0143': 'N',
    '\u00D1': 'N',
    '\u1E44': 'N',
    '\u0147': 'N',
    '\u1E46': 'N',
    '\u0145': 'N',
    '\u1E4A': 'N',
    '\u1E48': 'N',
    '\u0220': 'N',
    '\u019D': 'N',
    '\uA790': 'N',
    '\uA7A4': 'N',
    '\u01CA': 'NJ',
    '\u01CB': 'Nj',
    '\u24C4': 'O',
    '\uFF2F': 'O',
    '\u00D2': 'O',
    '\u00D3': 'O',
    '\u00D4': 'O',
    '\u1ED2': 'O',
    '\u1ED0': 'O',
    '\u1ED6': 'O',
    '\u1ED4': 'O',
    '\u00D5': 'O',
    '\u1E4C': 'O',
    '\u022C': 'O',
    '\u1E4E': 'O',
    '\u014C': 'O',
    '\u1E50': 'O',
    '\u1E52': 'O',
    '\u014E': 'O',
    '\u022E': 'O',
    '\u0230': 'O',
    '\u00D6': 'O',
    '\u022A': 'O',
    '\u1ECE': 'O',
    '\u0150': 'O',
    '\u01D1': 'O',
    '\u020C': 'O',
    '\u020E': 'O',
    '\u01A0': 'O',
    '\u1EDC': 'O',
    '\u1EDA': 'O',
    '\u1EE0': 'O',
    '\u1EDE': 'O',
    '\u1EE2': 'O',
    '\u1ECC': 'O',
    '\u1ED8': 'O',
    '\u01EA': 'O',
    '\u01EC': 'O',
    '\u00D8': 'O',
    '\u01FE': 'O',
    '\u0186': 'O',
    '\u019F': 'O',
    '\uA74A': 'O',
    '\uA74C': 'O',
    '\u0152': 'OE',
    '\u01A2': 'OI',
    '\uA74E': 'OO',
    '\u0222': 'OU',
    '\u24C5': 'P',
    '\uFF30': 'P',
    '\u1E54': 'P',
    '\u1E56': 'P',
    '\u01A4': 'P',
    '\u2C63': 'P',
    '\uA750': 'P',
    '\uA752': 'P',
    '\uA754': 'P',
    '\u24C6': 'Q',
    '\uFF31': 'Q',
    '\uA756': 'Q',
    '\uA758': 'Q',
    '\u024A': 'Q',
    '\u24C7': 'R',
    '\uFF32': 'R',
    '\u0154': 'R',
    '\u1E58': 'R',
    '\u0158': 'R',
    '\u0210': 'R',
    '\u0212': 'R',
    '\u1E5A': 'R',
    '\u1E5C': 'R',
    '\u0156': 'R',
    '\u1E5E': 'R',
    '\u024C': 'R',
    '\u2C64': 'R',
    '\uA75A': 'R',
    '\uA7A6': 'R',
    '\uA782': 'R',
    '\u24C8': 'S',
    '\uFF33': 'S',
    '\u1E9E': 'S',
    '\u015A': 'S',
    '\u1E64': 'S',
    '\u015C': 'S',
    '\u1E60': 'S',
    '\u0160': 'S',
    '\u1E66': 'S',
    '\u1E62': 'S',
    '\u1E68': 'S',
    '\u0218': 'S',
    '\u015E': 'S',
    '\u2C7E': 'S',
    '\uA7A8': 'S',
    '\uA784': 'S',
    '\u24C9': 'T',
    '\uFF34': 'T',
    '\u1E6A': 'T',
    '\u0164': 'T',
    '\u1E6C': 'T',
    '\u021A': 'T',
    '\u0162': 'T',
    '\u1E70': 'T',
    '\u1E6E': 'T',
    '\u0166': 'T',
    '\u01AC': 'T',
    '\u01AE': 'T',
    '\u023E': 'T',
    '\uA786': 'T',
    '\uA728': 'TZ',
    '\u24CA': 'U',
    '\uFF35': 'U',
    '\u00D9': 'U',
    '\u00DA': 'U',
    '\u00DB': 'U',
    '\u0168': 'U',
    '\u1E78': 'U',
    '\u016A': 'U',
    '\u1E7A': 'U',
    '\u016C': 'U',
    '\u00DC': 'U',
    '\u01DB': 'U',
    '\u01D7': 'U',
    '\u01D5': 'U',
    '\u01D9': 'U',
    '\u1EE6': 'U',
    '\u016E': 'U',
    '\u0170': 'U',
    '\u01D3': 'U',
    '\u0214': 'U',
    '\u0216': 'U',
    '\u01AF': 'U',
    '\u1EEA': 'U',
    '\u1EE8': 'U',
    '\u1EEE': 'U',
    '\u1EEC': 'U',
    '\u1EF0': 'U',
    '\u1EE4': 'U',
    '\u1E72': 'U',
    '\u0172': 'U',
    '\u1E76': 'U',
    '\u1E74': 'U',
    '\u0244': 'U',
    '\u24CB': 'V',
    '\uFF36': 'V',
    '\u1E7C': 'V',
    '\u1E7E': 'V',
    '\u01B2': 'V',
    '\uA75E': 'V',
    '\u0245': 'V',
    '\uA760': 'VY',
    '\u24CC': 'W',
    '\uFF37': 'W',
    '\u1E80': 'W',
    '\u1E82': 'W',
    '\u0174': 'W',
    '\u1E86': 'W',
    '\u1E84': 'W',
    '\u1E88': 'W',
    '\u2C72': 'W',
    '\u24CD': 'X',
    '\uFF38': 'X',
    '\u1E8A': 'X',
    '\u1E8C': 'X',
    '\u24CE': 'Y',
    '\uFF39': 'Y',
    '\u1EF2': 'Y',
    '\u00DD': 'Y',
    '\u0176': 'Y',
    '\u1EF8': 'Y',
    '\u0232': 'Y',
    '\u1E8E': 'Y',
    '\u0178': 'Y',
    '\u1EF6': 'Y',
    '\u1EF4': 'Y',
    '\u01B3': 'Y',
    '\u024E': 'Y',
    '\u1EFE': 'Y',
    '\u24CF': 'Z',
    '\uFF3A': 'Z',
    '\u0179': 'Z',
    '\u1E90': 'Z',
    '\u017B': 'Z',
    '\u017D': 'Z',
    '\u1E92': 'Z',
    '\u1E94': 'Z',
    '\u01B5': 'Z',
    '\u0224': 'Z',
    '\u2C7F': 'Z',
    '\u2C6B': 'Z',
    '\uA762': 'Z',
    '\u24D0': 'a',
    '\uFF41': 'a',
    '\u1E9A': 'a',
    '\u00E0': 'a',
    '\u00E1': 'a',
    '\u00E2': 'a',
    '\u1EA7': 'a',
    '\u1EA5': 'a',
    '\u1EAB': 'a',
    '\u1EA9': 'a',
    '\u00E3': 'a',
    '\u0101': 'a',
    '\u0103': 'a',
    '\u1EB1': 'a',
    '\u1EAF': 'a',
    '\u1EB5': 'a',
    '\u1EB3': 'a',
    '\u0227': 'a',
    '\u01E1': 'a',
    '\u00E4': 'a',
    '\u01DF': 'a',
    '\u1EA3': 'a',
    '\u00E5': 'a',
    '\u01FB': 'a',
    '\u01CE': 'a',
    '\u0201': 'a',
    '\u0203': 'a',
    '\u1EA1': 'a',
    '\u1EAD': 'a',
    '\u1EB7': 'a',
    '\u1E01': 'a',
    '\u0105': 'a',
    '\u2C65': 'a',
    '\u0250': 'a',
    '\uA733': 'aa',
    '\u00E6': 'ae',
    '\u01FD': 'ae',
    '\u01E3': 'ae',
    '\uA735': 'ao',
    '\uA737': 'au',
    '\uA739': 'av',
    '\uA73B': 'av',
    '\uA73D': 'ay',
    '\u24D1': 'b',
    '\uFF42': 'b',
    '\u1E03': 'b',
    '\u1E05': 'b',
    '\u1E07': 'b',
    '\u0180': 'b',
    '\u0183': 'b',
    '\u0253': 'b',
    '\u24D2': 'c',
    '\uFF43': 'c',
    '\u0107': 'c',
    '\u0109': 'c',
    '\u010B': 'c',
    '\u010D': 'c',
    '\u00E7': 'c',
    '\u1E09': 'c',
    '\u0188': 'c',
    '\u023C': 'c',
    '\uA73F': 'c',
    '\u2184': 'c',
    '\u24D3': 'd',
    '\uFF44': 'd',
    '\u1E0B': 'd',
    '\u010F': 'd',
    '\u1E0D': 'd',
    '\u1E11': 'd',
    '\u1E13': 'd',
    '\u1E0F': 'd',
    '\u0111': 'd',
    '\u018C': 'd',
    '\u0256': 'd',
    '\u0257': 'd',
    '\uA77A': 'd',
    '\u01F3': 'dz',
    '\u01C6': 'dz',
    '\u24D4': 'e',
    '\uFF45': 'e',
    '\u00E8': 'e',
    '\u00E9': 'e',
    '\u00EA': 'e',
    '\u1EC1': 'e',
    '\u1EBF': 'e',
    '\u1EC5': 'e',
    '\u1EC3': 'e',
    '\u1EBD': 'e',
    '\u0113': 'e',
    '\u1E15': 'e',
    '\u1E17': 'e',
    '\u0115': 'e',
    '\u0117': 'e',
    '\u00EB': 'e',
    '\u1EBB': 'e',
    '\u011B': 'e',
    '\u0205': 'e',
    '\u0207': 'e',
    '\u1EB9': 'e',
    '\u1EC7': 'e',
    '\u0229': 'e',
    '\u1E1D': 'e',
    '\u0119': 'e',
    '\u1E19': 'e',
    '\u1E1B': 'e',
    '\u0247': 'e',
    '\u025B': 'e',
    '\u01DD': 'e',
    '\u24D5': 'f',
    '\uFF46': 'f',
    '\u1E1F': 'f',
    '\u0192': 'f',
    '\uA77C': 'f',
    '\u24D6': 'g',
    '\uFF47': 'g',
    '\u01F5': 'g',
    '\u011D': 'g',
    '\u1E21': 'g',
    '\u011F': 'g',
    '\u0121': 'g',
    '\u01E7': 'g',
    '\u0123': 'g',
    '\u01E5': 'g',
    '\u0260': 'g',
    '\uA7A1': 'g',
    '\u1D79': 'g',
    '\uA77F': 'g',
    '\u24D7': 'h',
    '\uFF48': 'h',
    '\u0125': 'h',
    '\u1E23': 'h',
    '\u1E27': 'h',
    '\u021F': 'h',
    '\u1E25': 'h',
    '\u1E29': 'h',
    '\u1E2B': 'h',
    '\u1E96': 'h',
    '\u0127': 'h',
    '\u2C68': 'h',
    '\u2C76': 'h',
    '\u0265': 'h',
    '\u0195': 'hv',
    '\u24D8': 'i',
    '\uFF49': 'i',
    '\u00EC': 'i',
    '\u00ED': 'i',
    '\u00EE': 'i',
    '\u0129': 'i',
    '\u012B': 'i',
    '\u012D': 'i',
    '\u00EF': 'i',
    '\u1E2F': 'i',
    '\u1EC9': 'i',
    '\u01D0': 'i',
    '\u0209': 'i',
    '\u020B': 'i',
    '\u1ECB': 'i',
    '\u012F': 'i',
    '\u1E2D': 'i',
    '\u0268': 'i',
    '\u0131': 'i',
    '\u24D9': 'j',
    '\uFF4A': 'j',
    '\u0135': 'j',
    '\u01F0': 'j',
    '\u0249': 'j',
    '\u24DA': 'k',
    '\uFF4B': 'k',
    '\u1E31': 'k',
    '\u01E9': 'k',
    '\u1E33': 'k',
    '\u0137': 'k',
    '\u1E35': 'k',
    '\u0199': 'k',
    '\u2C6A': 'k',
    '\uA741': 'k',
    '\uA743': 'k',
    '\uA745': 'k',
    '\uA7A3': 'k',
    '\u24DB': 'l',
    '\uFF4C': 'l',
    '\u0140': 'l',
    '\u013A': 'l',
    '\u013E': 'l',
    '\u1E37': 'l',
    '\u1E39': 'l',
    '\u013C': 'l',
    '\u1E3D': 'l',
    '\u1E3B': 'l',
    '\u017F': 'l',
    '\u0142': 'l',
    '\u019A': 'l',
    '\u026B': 'l',
    '\u2C61': 'l',
    '\uA749': 'l',
    '\uA781': 'l',
    '\uA747': 'l',
    '\u01C9': 'lj',
    '\u24DC': 'm',
    '\uFF4D': 'm',
    '\u1E3F': 'm',
    '\u1E41': 'm',
    '\u1E43': 'm',
    '\u0271': 'm',
    '\u026F': 'm',
    '\u24DD': 'n',
    '\uFF4E': 'n',
    '\u01F9': 'n',
    '\u0144': 'n',
    '\u00F1': 'n',
    '\u1E45': 'n',
    '\u0148': 'n',
    '\u1E47': 'n',
    '\u0146': 'n',
    '\u1E4B': 'n',
    '\u1E49': 'n',
    '\u019E': 'n',
    '\u0272': 'n',
    '\u0149': 'n',
    '\uA791': 'n',
    '\uA7A5': 'n',
    '\u01CC': 'nj',
    '\u24DE': 'o',
    '\uFF4F': 'o',
    '\u00F2': 'o',
    '\u00F3': 'o',
    '\u00F4': 'o',
    '\u1ED3': 'o',
    '\u1ED1': 'o',
    '\u1ED7': 'o',
    '\u1ED5': 'o',
    '\u00F5': 'o',
    '\u1E4D': 'o',
    '\u022D': 'o',
    '\u1E4F': 'o',
    '\u014D': 'o',
    '\u1E51': 'o',
    '\u1E53': 'o',
    '\u014F': 'o',
    '\u022F': 'o',
    '\u0231': 'o',
    '\u00F6': 'o',
    '\u022B': 'o',
    '\u1ECF': 'o',
    '\u0151': 'o',
    '\u01D2': 'o',
    '\u020D': 'o',
    '\u020F': 'o',
    '\u01A1': 'o',
    '\u1EDD': 'o',
    '\u1EDB': 'o',
    '\u1EE1': 'o',
    '\u1EDF': 'o',
    '\u1EE3': 'o',
    '\u1ECD': 'o',
    '\u1ED9': 'o',
    '\u01EB': 'o',
    '\u01ED': 'o',
    '\u00F8': 'o',
    '\u01FF': 'o',
    '\u0254': 'o',
    '\uA74B': 'o',
    '\uA74D': 'o',
    '\u0275': 'o',
    '\u0153': 'oe',
    '\u01A3': 'oi',
    '\u0223': 'ou',
    '\uA74F': 'oo',
    '\u24DF': 'p',
    '\uFF50': 'p',
    '\u1E55': 'p',
    '\u1E57': 'p',
    '\u01A5': 'p',
    '\u1D7D': 'p',
    '\uA751': 'p',
    '\uA753': 'p',
    '\uA755': 'p',
    '\u24E0': 'q',
    '\uFF51': 'q',
    '\u024B': 'q',
    '\uA757': 'q',
    '\uA759': 'q',
    '\u24E1': 'r',
    '\uFF52': 'r',
    '\u0155': 'r',
    '\u1E59': 'r',
    '\u0159': 'r',
    '\u0211': 'r',
    '\u0213': 'r',
    '\u1E5B': 'r',
    '\u1E5D': 'r',
    '\u0157': 'r',
    '\u1E5F': 'r',
    '\u024D': 'r',
    '\u027D': 'r',
    '\uA75B': 'r',
    '\uA7A7': 'r',
    '\uA783': 'r',
    '\u24E2': 's',
    '\uFF53': 's',
    '\u00DF': 's',
    '\u015B': 's',
    '\u1E65': 's',
    '\u015D': 's',
    '\u1E61': 's',
    '\u0161': 's',
    '\u1E67': 's',
    '\u1E63': 's',
    '\u1E69': 's',
    '\u0219': 's',
    '\u015F': 's',
    '\u023F': 's',
    '\uA7A9': 's',
    '\uA785': 's',
    '\u1E9B': 's',
    '\u24E3': 't',
    '\uFF54': 't',
    '\u1E6B': 't',
    '\u1E97': 't',
    '\u0165': 't',
    '\u1E6D': 't',
    '\u021B': 't',
    '\u0163': 't',
    '\u1E71': 't',
    '\u1E6F': 't',
    '\u0167': 't',
    '\u01AD': 't',
    '\u0288': 't',
    '\u2C66': 't',
    '\uA787': 't',
    '\uA729': 'tz',
    '\u24E4': 'u',
    '\uFF55': 'u',
    '\u00F9': 'u',
    '\u00FA': 'u',
    '\u00FB': 'u',
    '\u0169': 'u',
    '\u1E79': 'u',
    '\u016B': 'u',
    '\u1E7B': 'u',
    '\u016D': 'u',
    '\u00FC': 'u',
    '\u01DC': 'u',
    '\u01D8': 'u',
    '\u01D6': 'u',
    '\u01DA': 'u',
    '\u1EE7': 'u',
    '\u016F': 'u',
    '\u0171': 'u',
    '\u01D4': 'u',
    '\u0215': 'u',
    '\u0217': 'u',
    '\u01B0': 'u',
    '\u1EEB': 'u',
    '\u1EE9': 'u',
    '\u1EEF': 'u',
    '\u1EED': 'u',
    '\u1EF1': 'u',
    '\u1EE5': 'u',
    '\u1E73': 'u',
    '\u0173': 'u',
    '\u1E77': 'u',
    '\u1E75': 'u',
    '\u0289': 'u',
    '\u24E5': 'v',
    '\uFF56': 'v',
    '\u1E7D': 'v',
    '\u1E7F': 'v',
    '\u028B': 'v',
    '\uA75F': 'v',
    '\u028C': 'v',
    '\uA761': 'vy',
    '\u24E6': 'w',
    '\uFF57': 'w',
    '\u1E81': 'w',
    '\u1E83': 'w',
    '\u0175': 'w',
    '\u1E87': 'w',
    '\u1E85': 'w',
    '\u1E98': 'w',
    '\u1E89': 'w',
    '\u2C73': 'w',
    '\u24E7': 'x',
    '\uFF58': 'x',
    '\u1E8B': 'x',
    '\u1E8D': 'x',
    '\u24E8': 'y',
    '\uFF59': 'y',
    '\u1EF3': 'y',
    '\u00FD': 'y',
    '\u0177': 'y',
    '\u1EF9': 'y',
    '\u0233': 'y',
    '\u1E8F': 'y',
    '\u00FF': 'y',
    '\u1EF7': 'y',
    '\u1E99': 'y',
    '\u1EF5': 'y',
    '\u01B4': 'y',
    '\u024F': 'y',
    '\u1EFF': 'y',
    '\u24E9': 'z',
    '\uFF5A': 'z',
    '\u017A': 'z',
    '\u1E91': 'z',
    '\u017C': 'z',
    '\u017E': 'z',
    '\u1E93': 'z',
    '\u1E95': 'z',
    '\u01B6': 'z',
    '\u0225': 'z',
    '\u0240': 'z',
    '\u2C6C': 'z',
    '\uA763': 'z',
    '\u0386': '\u0391',
    '\u0388': '\u0395',
    '\u0389': '\u0397',
    '\u038A': '\u0399',
    '\u03AA': '\u0399',
    '\u038C': '\u039F',
    '\u038E': '\u03A5',
    '\u03AB': '\u03A5',
    '\u038F': '\u03A9',
    '\u03AC': '\u03B1',
    '\u03AD': '\u03B5',
    '\u03AE': '\u03B7',
    '\u03AF': '\u03B9',
    '\u03CA': '\u03B9',
    '\u0390': '\u03B9',
    '\u03CC': '\u03BF',
    '\u03CD': '\u03C5',
    '\u03CB': '\u03C5',
    '\u03B0': '\u03C5',
    '\u03CE': '\u03C9',
    '\u03C2': '\u03C3',
    '\u2019': '\''
  };

  return diacritics;
});

S2.define('select2/data/base',[
  '../utils'
], function (Utils) {
  function BaseAdapter ($element, options) {
    BaseAdapter.__super__.constructor.call(this);
  }

  Utils.Extend(BaseAdapter, Utils.Observable);

  BaseAdapter.prototype.current = function (callback) {
    throw new Error('The `current` method must be defined in child classes.');
  };

  BaseAdapter.prototype.query = function (params, callback) {
    throw new Error('The `query` method must be defined in child classes.');
  };

  BaseAdapter.prototype.bind = function (container, $container) {
    // Can be implemented in subclasses
  };

  BaseAdapter.prototype.destroy = function () {
    // Can be implemented in subclasses
  };

  BaseAdapter.prototype.generateResultId = function (container, data) {
    var id = container.id + '-result-';

    id += Utils.generateChars(4);

    if (data.id != null) {
      id += '-' + data.id.toString();
    } else {
      id += '-' + Utils.generateChars(4);
    }
    return id;
  };

  return BaseAdapter;
});

S2.define('select2/data/select',[
  './base',
  '../utils',
  'jquery'
], function (BaseAdapter, Utils, $) {
  function SelectAdapter ($element, options) {
    this.$element = $element;
    this.options = options;

    SelectAdapter.__super__.constructor.call(this);
  }

  Utils.Extend(SelectAdapter, BaseAdapter);

  SelectAdapter.prototype.current = function (callback) {
    var data = [];
    var self = this;

    this.$element.find(':selected').each(function () {
      var $option = $(this);

      var option = self.item($option);

      data.push(option);
    });

    callback(data);
  };

  SelectAdapter.prototype.select = function (data) {
    var self = this;

    data.selected = true;

    // If data.element is a DOM node, use it instead
    if ($(data.element).is('option')) {
      data.element.selected = true;

      this.$element.trigger('input').trigger('change');

      return;
    }

    if (this.$element.prop('multiple')) {
      this.current(function (currentData) {
        var val = [];

        data = [data];
        data.push.apply(data, currentData);

        for (var d = 0; d < data.length; d++) {
          var id = data[d].id;

          if ($.inArray(id, val) === -1) {
            val.push(id);
          }
        }

        self.$element.val(val);
        self.$element.trigger('input').trigger('change');
      });
    } else {
      var val = data.id;

      this.$element.val(val);
      this.$element.trigger('input').trigger('change');
    }
  };

  SelectAdapter.prototype.unselect = function (data) {
    var self = this;

    if (!this.$element.prop('multiple')) {
      return;
    }

    data.selected = false;

    if ($(data.element).is('option')) {
      data.element.selected = false;

      this.$element.trigger('input').trigger('change');

      return;
    }

    this.current(function (currentData) {
      var val = [];

      for (var d = 0; d < currentData.length; d++) {
        var id = currentData[d].id;

        if (id !== data.id && $.inArray(id, val) === -1) {
          val.push(id);
        }
      }

      self.$element.val(val);

      self.$element.trigger('input').trigger('change');
    });
  };

  SelectAdapter.prototype.bind = function (container, $container) {
    var self = this;

    this.container = container;

    container.on('select', function (params) {
      self.select(params.data);
    });

    container.on('unselect', function (params) {
      self.unselect(params.data);
    });
  };

  SelectAdapter.prototype.destroy = function () {
    // Remove anything added to child elements
    this.$element.find('*').each(function () {
      // Remove any custom data set by Select2
      Utils.RemoveData(this);
    });
  };

  SelectAdapter.prototype.query = function (params, callback) {
    var data = [];
    var self = this;

    var $options = this.$element.children();

    $options.each(function () {
      var $option = $(this);

      if (!$option.is('option') && !$option.is('optgroup')) {
        return;
      }

      var option = self.item($option);

      var matches = self.matches(params, option);

      if (matches !== null) {
        data.push(matches);
      }
    });

    callback({
      results: data
    });
  };

  SelectAdapter.prototype.addOptions = function ($options) {
    Utils.appendMany(this.$element, $options);
  };

  SelectAdapter.prototype.option = function (data) {
    var option;

    if (data.children) {
      option = document.createElement('optgroup');
      option.label = data.text;
    } else {
      option = document.createElement('option');

      if (option.textContent !== undefined) {
        option.textContent = data.text;
      } else {
        option.innerText = data.text;
      }
    }

    if (data.id !== undefined) {
      option.value = data.id;
    }

    if (data.disabled) {
      option.disabled = true;
    }

    if (data.selected) {
      option.selected = true;
    }

    if (data.title) {
      option.title = data.title;
    }

    var $option = $(option);

    var normalizedData = this._normalizeItem(data);
    normalizedData.element = option;

    // Override the option's data with the combined data
    Utils.StoreData(option, 'data', normalizedData);

    return $option;
  };

  SelectAdapter.prototype.item = function ($option) {
    var data = {};

    data = Utils.GetData($option[0], 'data');

    if (data != null) {
      return data;
    }

    if ($option.is('option')) {
      data = {
        id: $option.val(),
        text: $option.text(),
        disabled: $option.prop('disabled'),
        selected: $option.prop('selected'),
        title: $option.prop('title')
      };
    } else if ($option.is('optgroup')) {
      data = {
        text: $option.prop('label'),
        children: [],
        title: $option.prop('title')
      };

      var $children = $option.children('option');
      var children = [];

      for (var c = 0; c < $children.length; c++) {
        var $child = $($children[c]);

        var child = this.item($child);

        children.push(child);
      }

      data.children = children;
    }

    data = this._normalizeItem(data);
    data.element = $option[0];

    Utils.StoreData($option[0], 'data', data);

    return data;
  };

  SelectAdapter.prototype._normalizeItem = function (item) {
    if (item !== Object(item)) {
      item = {
        id: item,
        text: item
      };
    }

    item = $.extend({}, {
      text: ''
    }, item);

    var defaults = {
      selected: false,
      disabled: false
    };

    if (item.id != null) {
      item.id = item.id.toString();
    }

    if (item.text != null) {
      item.text = item.text.toString();
    }

    if (item._resultId == null && item.id && this.container != null) {
      item._resultId = this.generateResultId(this.container, item);
    }

    return $.extend({}, defaults, item);
  };

  SelectAdapter.prototype.matches = function (params, data) {
    var matcher = this.options.get('matcher');

    return matcher(params, data);
  };

  return SelectAdapter;
});

S2.define('select2/data/array',[
  './select',
  '../utils',
  'jquery'
], function (SelectAdapter, Utils, $) {
  function ArrayAdapter ($element, options) {
    this._dataToConvert = options.get('data') || [];

    ArrayAdapter.__super__.constructor.call(this, $element, options);
  }

  Utils.Extend(ArrayAdapter, SelectAdapter);

  ArrayAdapter.prototype.bind = function (container, $container) {
    ArrayAdapter.__super__.bind.call(this, container, $container);

    this.addOptions(this.convertToOptions(this._dataToConvert));
  };

  ArrayAdapter.prototype.select = function (data) {
    var $option = this.$element.find('option').filter(function (i, elm) {
      return elm.value == data.id.toString();
    });

    if ($option.length === 0) {
      $option = this.option(data);

      this.addOptions($option);
    }

    ArrayAdapter.__super__.select.call(this, data);
  };

  ArrayAdapter.prototype.convertToOptions = function (data) {
    var self = this;

    var $existing = this.$element.find('option');
    var existingIds = $existing.map(function () {
      return self.item($(this)).id;
    }).get();

    var $options = [];

    // Filter out all items except for the one passed in the argument
    function onlyItem (item) {
      return function () {
        return $(this).val() == item.id;
      };
    }

    for (var d = 0; d < data.length; d++) {
      var item = this._normalizeItem(data[d]);

      // Skip items which were pre-loaded, only merge the data
      if ($.inArray(item.id, existingIds) >= 0) {
        var $existingOption = $existing.filter(onlyItem(item));

        var existingData = this.item($existingOption);
        var newData = $.extend(true, {}, item, existingData);

        var $newOption = this.option(newData);

        $existingOption.replaceWith($newOption);

        continue;
      }

      var $option = this.option(item);

      if (item.children) {
        var $children = this.convertToOptions(item.children);

        Utils.appendMany($option, $children);
      }

      $options.push($option);
    }

    return $options;
  };

  return ArrayAdapter;
});

S2.define('select2/data/ajax',[
  './array',
  '../utils',
  'jquery'
], function (ArrayAdapter, Utils, $) {
  function AjaxAdapter ($element, options) {
    this.ajaxOptions = this._applyDefaults(options.get('ajax'));

    if (this.ajaxOptions.processResults != null) {
      this.processResults = this.ajaxOptions.processResults;
    }

    AjaxAdapter.__super__.constructor.call(this, $element, options);
  }

  Utils.Extend(AjaxAdapter, ArrayAdapter);

  AjaxAdapter.prototype._applyDefaults = function (options) {
    var defaults = {
      data: function (params) {
        return $.extend({}, params, {
          q: params.term
        });
      },
      transport: function (params, success, failure) {
        var $request = $.ajax(params);

        $request.then(success);
        $request.fail(failure);

        return $request;
      }
    };

    return $.extend({}, defaults, options, true);
  };

  AjaxAdapter.prototype.processResults = function (results) {
    return results;
  };

  AjaxAdapter.prototype.query = function (params, callback) {
    var matches = [];
    var self = this;

    if (this._request != null) {
      // JSONP requests cannot always be aborted
      if ($.isFunction(this._request.abort)) {
        this._request.abort();
      }

      this._request = null;
    }

    var options = $.extend({
      type: 'GET'
    }, this.ajaxOptions);

    if (typeof options.url === 'function') {
      options.url = options.url.call(this.$element, params);
    }

    if (typeof options.data === 'function') {
      options.data = options.data.call(this.$element, params);
    }

    function request () {
      var $request = options.transport(options, function (data) {
        var results = self.processResults(data, params);

        if (self.options.get('debug') && window.console && console.error) {
          // Check to make sure that the response included a `results` key.
          if (!results || !results.results || !$.isArray(results.results)) {
            console.error(
              'Select2: The AJAX results did not return an array in the ' +
              '`results` key of the response.'
            );
          }
        }

        callback(results);
      }, function () {
        // Attempt to detect if a request was aborted
        // Only works if the transport exposes a status property
        if ('status' in $request &&
            ($request.status === 0 || $request.status === '0')) {
          return;
        }

        self.trigger('results:message', {
          message: 'errorLoading'
        });
      });

      self._request = $request;
    }

    if (this.ajaxOptions.delay && params.term != null) {
      if (this._queryTimeout) {
        window.clearTimeout(this._queryTimeout);
      }

      this._queryTimeout = window.setTimeout(request, this.ajaxOptions.delay);
    } else {
      request();
    }
  };

  return AjaxAdapter;
});

S2.define('select2/data/tags',[
  'jquery'
], function ($) {
  function Tags (decorated, $element, options) {
    var tags = options.get('tags');

    var createTag = options.get('createTag');

    if (createTag !== undefined) {
      this.createTag = createTag;
    }

    var insertTag = options.get('insertTag');

    if (insertTag !== undefined) {
        this.insertTag = insertTag;
    }

    decorated.call(this, $element, options);

    if ($.isArray(tags)) {
      for (var t = 0; t < tags.length; t++) {
        var tag = tags[t];
        var item = this._normalizeItem(tag);

        var $option = this.option(item);

        this.$element.append($option);
      }
    }
  }

  Tags.prototype.query = function (decorated, params, callback) {
    var self = this;

    this._removeOldTags();

    if (params.term == null || params.page != null) {
      decorated.call(this, params, callback);
      return;
    }

    function wrapper (obj, child) {
      var data = obj.results;

      for (var i = 0; i < data.length; i++) {
        var option = data[i];

        var checkChildren = (
          option.children != null &&
          !wrapper({
            results: option.children
          }, true)
        );

        var optionText = (option.text || '').toUpperCase();
        var paramsTerm = (params.term || '').toUpperCase();

        var checkText = optionText === paramsTerm;

        if (checkText || checkChildren) {
          if (child) {
            return false;
          }

          obj.data = data;
          callback(obj);

          return;
        }
      }

      if (child) {
        return true;
      }

      var tag = self.createTag(params);

      if (tag != null) {
        var $option = self.option(tag);
        $option.attr('data-select2-tag', true);

        self.addOptions([$option]);

        self.insertTag(data, tag);
      }

      obj.results = data;

      callback(obj);
    }

    decorated.call(this, params, wrapper);
  };

  Tags.prototype.createTag = function (decorated, params) {
    var term = $.trim(params.term);

    if (term === '') {
      return null;
    }

    return {
      id: term,
      text: term
    };
  };

  Tags.prototype.insertTag = function (_, data, tag) {
    data.unshift(tag);
  };

  Tags.prototype._removeOldTags = function (_) {
    var $options = this.$element.find('option[data-select2-tag]');

    $options.each(function () {
      if (this.selected) {
        return;
      }

      $(this).remove();
    });
  };

  return Tags;
});

S2.define('select2/data/tokenizer',[
  'jquery'
], function ($) {
  function Tokenizer (decorated, $element, options) {
    var tokenizer = options.get('tokenizer');

    if (tokenizer !== undefined) {
      this.tokenizer = tokenizer;
    }

    decorated.call(this, $element, options);
  }

  Tokenizer.prototype.bind = function (decorated, container, $container) {
    decorated.call(this, container, $container);

    this.$search =  container.dropdown.$search || container.selection.$search ||
      $container.find('.select2-search__field');
  };

  Tokenizer.prototype.query = function (decorated, params, callback) {
    var self = this;

    function createAndSelect (data) {
      // Normalize the data object so we can use it for checks
      var item = self._normalizeItem(data);

      // Check if the data object already exists as a tag
      // Select it if it doesn't
      var $existingOptions = self.$element.find('option').filter(function () {
        return $(this).val() === item.id;
      });

      // If an existing option wasn't found for it, create the option
      if (!$existingOptions.length) {
        var $option = self.option(item);
        $option.attr('data-select2-tag', true);

        self._removeOldTags();
        self.addOptions([$option]);
      }

      // Select the item, now that we know there is an option for it
      select(item);
    }

    function select (data) {
      self.trigger('select', {
        data: data
      });
    }

    params.term = params.term || '';

    var tokenData = this.tokenizer(params, this.options, createAndSelect);

    if (tokenData.term !== params.term) {
      // Replace the search term if we have the search box
      if (this.$search.length) {
        this.$search.val(tokenData.term);
        this.$search.trigger('focus');
      }

      params.term = tokenData.term;
    }

    decorated.call(this, params, callback);
  };

  Tokenizer.prototype.tokenizer = function (_, params, options, callback) {
    var separators = options.get('tokenSeparators') || [];
    var term = params.term;
    var i = 0;

    var createTag = this.createTag || function (params) {
      return {
        id: params.term,
        text: params.term
      };
    };

    while (i < term.length) {
      var termChar = term[i];

      if ($.inArray(termChar, separators) === -1) {
        i++;

        continue;
      }

      var part = term.substr(0, i);
      var partParams = $.extend({}, params, {
        term: part
      });

      var data = createTag(partParams);

      if (data == null) {
        i++;
        continue;
      }

      callback(data);

      // Reset the term to not include the tokenized portion
      term = term.substr(i + 1) || '';
      i = 0;
    }

    return {
      term: term
    };
  };

  return Tokenizer;
});

S2.define('select2/data/minimumInputLength',[

], function () {
  function MinimumInputLength (decorated, $e, options) {
    this.minimumInputLength = options.get('minimumInputLength');

    decorated.call(this, $e, options);
  }

  MinimumInputLength.prototype.query = function (decorated, params, callback) {
    params.term = params.term || '';

    if (params.term.length < this.minimumInputLength) {
      this.trigger('results:message', {
        message: 'inputTooShort',
        args: {
          minimum: this.minimumInputLength,
          input: params.term,
          params: params
        }
      });

      return;
    }

    decorated.call(this, params, callback);
  };

  return MinimumInputLength;
});

S2.define('select2/data/maximumInputLength',[

], function () {
  function MaximumInputLength (decorated, $e, options) {
    this.maximumInputLength = options.get('maximumInputLength');

    decorated.call(this, $e, options);
  }

  MaximumInputLength.prototype.query = function (decorated, params, callback) {
    params.term = params.term || '';

    if (this.maximumInputLength > 0 &&
        params.term.length > this.maximumInputLength) {
      this.trigger('results:message', {
        message: 'inputTooLong',
        args: {
          maximum: this.maximumInputLength,
          input: params.term,
          params: params
        }
      });

      return;
    }

    decorated.call(this, params, callback);
  };

  return MaximumInputLength;
});

S2.define('select2/data/maximumSelectionLength',[

], function (){
  function MaximumSelectionLength (decorated, $e, options) {
    this.maximumSelectionLength = options.get('maximumSelectionLength');

    decorated.call(this, $e, options);
  }

  MaximumSelectionLength.prototype.bind =
    function (decorated, container, $container) {
      var self = this;

      decorated.call(this, container, $container);

      container.on('select', function () {
        self._checkIfMaximumSelected();
      });
  };

  MaximumSelectionLength.prototype.query =
    function (decorated, params, callback) {
      var self = this;

      this._checkIfMaximumSelected(function () {
        decorated.call(self, params, callback);
      });
  };

  MaximumSelectionLength.prototype._checkIfMaximumSelected =
    function (_, successCallback) {
      var self = this;

      this.current(function (currentData) {
        var count = currentData != null ? currentData.length : 0;
        if (self.maximumSelectionLength > 0 &&
          count >= self.maximumSelectionLength) {
          self.trigger('results:message', {
            message: 'maximumSelected',
            args: {
              maximum: self.maximumSelectionLength
            }
          });
          return;
        }

        if (successCallback) {
          successCallback();
        }
      });
  };

  return MaximumSelectionLength;
});

S2.define('select2/dropdown',[
  'jquery',
  './utils'
], function ($, Utils) {
  function Dropdown ($element, options) {
    this.$element = $element;
    this.options = options;

    Dropdown.__super__.constructor.call(this);
  }

  Utils.Extend(Dropdown, Utils.Observable);

  Dropdown.prototype.render = function () {
    var $dropdown = $(
      '<span class="select2-dropdown">' +
        '<span class="select2-results"></span>' +
      '</span>'
    );

    $dropdown.attr('dir', this.options.get('dir'));

    this.$dropdown = $dropdown;

    return $dropdown;
  };

  Dropdown.prototype.bind = function () {
    // Should be implemented in subclasses
  };

  Dropdown.prototype.position = function ($dropdown, $container) {
    // Should be implemented in subclasses
  };

  Dropdown.prototype.destroy = function () {
    // Remove the dropdown from the DOM
    this.$dropdown.remove();
  };

  return Dropdown;
});

S2.define('select2/dropdown/search',[
  'jquery',
  '../utils'
], function ($, Utils) {
  function Search () { }

  Search.prototype.render = function (decorated) {
    var $rendered = decorated.call(this);

    var $search = $(
      '<span class="select2-search select2-search--dropdown">' +
        '<input class="select2-search__field" type="search" tabindex="-1"' +
        ' autocomplete="off" autocorrect="off" autocapitalize="none"' +
        ' spellcheck="false" role="searchbox" aria-autocomplete="list" />' +
      '</span>'
    );

    this.$searchContainer = $search;
    this.$search = $search.find('input');

    $rendered.prepend($search);

    return $rendered;
  };

  Search.prototype.bind = function (decorated, container, $container) {
    var self = this;

    var resultsId = container.id + '-results';

    decorated.call(this, container, $container);

    this.$search.on('keydown', function (evt) {
      self.trigger('keypress', evt);

      self._keyUpPrevented = evt.isDefaultPrevented();
    });

    // Workaround for browsers which do not support the `input` event
    // This will prevent double-triggering of events for browsers which support
    // both the `keyup` and `input` events.
    this.$search.on('input', function (evt) {
      // Unbind the duplicated `keyup` event
      $(this).off('keyup');
    });

    this.$search.on('keyup input', function (evt) {
      self.handleSearch(evt);
    });

    container.on('open', function () {
      self.$search.attr('tabindex', 0);
      self.$search.attr('aria-controls', resultsId);

      self.$search.trigger('focus');

      window.setTimeout(function () {
        self.$search.trigger('focus');
      }, 0);
    });

    container.on('close', function () {
      self.$search.attr('tabindex', -1);
      self.$search.removeAttr('aria-controls');
      self.$search.removeAttr('aria-activedescendant');

      self.$search.val('');
      self.$search.trigger('blur');
    });

    container.on('focus', function () {
      if (!container.isOpen()) {
        self.$search.trigger('focus');
      }
    });

    container.on('results:all', function (params) {
      if (params.query.term == null || params.query.term === '') {
        var showSearch = self.showSearch(params);

        if (showSearch) {
          self.$searchContainer.removeClass('select2-search--hide');
        } else {
          self.$searchContainer.addClass('select2-search--hide');
        }
      }
    });

    container.on('results:focus', function (params) {
      if (params.data._resultId) {
        self.$search.attr('aria-activedescendant', params.data._resultId);
      } else {
        self.$search.removeAttr('aria-activedescendant');
      }
    });
  };

  Search.prototype.handleSearch = function (evt) {
    if (!this._keyUpPrevented) {
      var input = this.$search.val();

      this.trigger('query', {
        term: input
      });
    }

    this._keyUpPrevented = false;
  };

  Search.prototype.showSearch = function (_, params) {
    return true;
  };

  return Search;
});

S2.define('select2/dropdown/hidePlaceholder',[

], function () {
  function HidePlaceholder (decorated, $element, options, dataAdapter) {
    this.placeholder = this.normalizePlaceholder(options.get('placeholder'));

    decorated.call(this, $element, options, dataAdapter);
  }

  HidePlaceholder.prototype.append = function (decorated, data) {
    data.results = this.removePlaceholder(data.results);

    decorated.call(this, data);
  };

  HidePlaceholder.prototype.normalizePlaceholder = function (_, placeholder) {
    if (typeof placeholder === 'string') {
      placeholder = {
        id: '',
        text: placeholder
      };
    }

    return placeholder;
  };

  HidePlaceholder.prototype.removePlaceholder = function (_, data) {
    var modifiedData = data.slice(0);

    for (var d = data.length - 1; d >= 0; d--) {
      var item = data[d];

      if (this.placeholder.id === item.id) {
        modifiedData.splice(d, 1);
      }
    }

    return modifiedData;
  };

  return HidePlaceholder;
});

S2.define('select2/dropdown/infiniteScroll',[
  'jquery'
], function ($) {
  function InfiniteScroll (decorated, $element, options, dataAdapter) {
    this.lastParams = {};

    decorated.call(this, $element, options, dataAdapter);

    this.$loadingMore = this.createLoadingMore();
    this.loading = false;
  }

  InfiniteScroll.prototype.append = function (decorated, data) {
    this.$loadingMore.remove();
    this.loading = false;

    decorated.call(this, data);

    if (this.showLoadingMore(data)) {
      this.$results.append(this.$loadingMore);
      this.loadMoreIfNeeded();
    }
  };

  InfiniteScroll.prototype.bind = function (decorated, container, $container) {
    var self = this;

    decorated.call(this, container, $container);

    container.on('query', function (params) {
      self.lastParams = params;
      self.loading = true;
    });

    container.on('query:append', function (params) {
      self.lastParams = params;
      self.loading = true;
    });

    this.$results.on('scroll', this.loadMoreIfNeeded.bind(this));
  };

  InfiniteScroll.prototype.loadMoreIfNeeded = function () {
    var isLoadMoreVisible = $.contains(
      document.documentElement,
      this.$loadingMore[0]
    );

    if (this.loading || !isLoadMoreVisible) {
      return;
    }

    var currentOffset = this.$results.offset().top +
      this.$results.outerHeight(false);
    var loadingMoreOffset = this.$loadingMore.offset().top +
      this.$loadingMore.outerHeight(false);

    if (currentOffset + 50 >= loadingMoreOffset) {
      this.loadMore();
    }
  };

  InfiniteScroll.prototype.loadMore = function () {
    this.loading = true;

    var params = $.extend({}, {page: 1}, this.lastParams);

    params.page++;

    this.trigger('query:append', params);
  };

  InfiniteScroll.prototype.showLoadingMore = function (_, data) {
    return data.pagination && data.pagination.more;
  };

  InfiniteScroll.prototype.createLoadingMore = function () {
    var $option = $(
      '<li ' +
      'class="select2-results__option select2-results__option--load-more"' +
      'role="option" aria-disabled="true"></li>'
    );

    var message = this.options.get('translations').get('loadingMore');

    $option.html(message(this.lastParams));

    return $option;
  };

  return InfiniteScroll;
});

S2.define('select2/dropdown/attachBody',[
  'jquery',
  '../utils'
], function ($, Utils) {
  function AttachBody (decorated, $element, options) {
    this.$dropdownParent = $(options.get('dropdownParent') || document.body);

    decorated.call(this, $element, options);
  }

  AttachBody.prototype.bind = function (decorated, container, $container) {
    var self = this;

    decorated.call(this, container, $container);

    container.on('open', function () {
      self._showDropdown();
      self._attachPositioningHandler(container);

      // Must bind after the results handlers to ensure correct sizing
      self._bindContainerResultHandlers(container);
    });

    container.on('close', function () {
      self._hideDropdown();
      self._detachPositioningHandler(container);
    });

    this.$dropdownContainer.on('mousedown', function (evt) {
      evt.stopPropagation();
    });
  };

  AttachBody.prototype.destroy = function (decorated) {
    decorated.call(this);

    this.$dropdownContainer.remove();
  };

  AttachBody.prototype.position = function (decorated, $dropdown, $container) {
    // Clone all of the container classes
    $dropdown.attr('class', $container.attr('class'));

    $dropdown.removeClass('select2');
    $dropdown.addClass('select2-container--open');

    $dropdown.css({
      position: 'absolute',
      top: -999999
    });

    this.$container = $container;
  };

  AttachBody.prototype.render = function (decorated) {
    var $container = $('<span></span>');

    var $dropdown = decorated.call(this);
    $container.append($dropdown);

    this.$dropdownContainer = $container;

    return $container;
  };

  AttachBody.prototype._hideDropdown = function (decorated) {
    this.$dropdownContainer.detach();
  };

  AttachBody.prototype._bindContainerResultHandlers =
      function (decorated, container) {

    // These should only be bound once
    if (this._containerResultsHandlersBound) {
      return;
    }

    var self = this;

    container.on('results:all', function () {
      self._positionDropdown();
      self._resizeDropdown();
    });

    container.on('results:append', function () {
      self._positionDropdown();
      self._resizeDropdown();
    });

    container.on('results:message', function () {
      self._positionDropdown();
      self._resizeDropdown();
    });

    container.on('select', function () {
      self._positionDropdown();
      self._resizeDropdown();
    });

    container.on('unselect', function () {
      self._positionDropdown();
      self._resizeDropdown();
    });

    this._containerResultsHandlersBound = true;
  };

  AttachBody.prototype._attachPositioningHandler =
      function (decorated, container) {
    var self = this;

    var scrollEvent = 'scroll.select2.' + container.id;
    var resizeEvent = 'resize.select2.' + container.id;
    var orientationEvent = 'orientationchange.select2.' + container.id;

    var $watchers = this.$container.parents().filter(Utils.hasScroll);
    $watchers.each(function () {
      Utils.StoreData(this, 'select2-scroll-position', {
        x: $(this).scrollLeft(),
        y: $(this).scrollTop()
      });
    });

    $watchers.on(scrollEvent, function (ev) {
      var position = Utils.GetData(this, 'select2-scroll-position');
      $(this).scrollTop(position.y);
    });

    $(window).on(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent,
      function (e) {
      self._positionDropdown();
      self._resizeDropdown();
    });
  };

  AttachBody.prototype._detachPositioningHandler =
      function (decorated, container) {
    var scrollEvent = 'scroll.select2.' + container.id;
    var resizeEvent = 'resize.select2.' + container.id;
    var orientationEvent = 'orientationchange.select2.' + container.id;

    var $watchers = this.$container.parents().filter(Utils.hasScroll);
    $watchers.off(scrollEvent);

    $(window).off(scrollEvent + ' ' + resizeEvent + ' ' + orientationEvent);
  };

  AttachBody.prototype._positionDropdown = function () {
    var $window = $(window);

    var isCurrentlyAbove = this.$dropdown.hasClass('select2-dropdown--above');
    var isCurrentlyBelow = this.$dropdown.hasClass('select2-dropdown--below');

    var newDirection = null;

    var offset = this.$container.offset();

    offset.bottom = offset.top + this.$container.outerHeight(false);

    var container = {
      height: this.$container.outerHeight(false)
    };

    container.top = offset.top;
    container.bottom = offset.top + container.height;

    var dropdown = {
      height: this.$dropdown.outerHeight(false)
    };

    var viewport = {
      top: $window.scrollTop(),
      bottom: $window.scrollTop() + $window.height()
    };

    var enoughRoomAbove = viewport.top < (offset.top - dropdown.height);
    var enoughRoomBelow = viewport.bottom > (offset.bottom + dropdown.height);

    var css = {
      left: offset.left,
      top: container.bottom
    };

    // Determine what the parent element is to use for calculating the offset
    var $offsetParent = this.$dropdownParent;

    // For statically positioned elements, we need to get the element
    // that is determining the offset
    if ($offsetParent.css('position') === 'static') {
      $offsetParent = $offsetParent.offsetParent();
    }

    var parentOffset = {
      top: 0,
      left: 0
    };

    if (
      $.contains(document.body, $offsetParent[0]) ||
      $offsetParent[0].isConnected
      ) {
      parentOffset = $offsetParent.offset();
    }

    css.top -= parentOffset.top;
    css.left -= parentOffset.left;

    if (!isCurrentlyAbove && !isCurrentlyBelow) {
      newDirection = 'below';
    }

    if (!enoughRoomBelow && enoughRoomAbove && !isCurrentlyAbove) {
      newDirection = 'above';
    } else if (!enoughRoomAbove && enoughRoomBelow && isCurrentlyAbove) {
      newDirection = 'below';
    }

    if (newDirection == 'above' ||
      (isCurrentlyAbove && newDirection !== 'below')) {
      css.top = container.top - parentOffset.top - dropdown.height;
    }

    if (newDirection != null) {
      this.$dropdown
        .removeClass('select2-dropdown--below select2-dropdown--above')
        .addClass('select2-dropdown--' + newDirection);
      this.$container
        .removeClass('select2-container--below select2-container--above')
        .addClass('select2-container--' + newDirection);
    }

    this.$dropdownContainer.css(css);
  };

  AttachBody.prototype._resizeDropdown = function () {
    var css = {
      width: this.$container.outerWidth(false) + 'px'
    };

    if (this.options.get('dropdownAutoWidth')) {
      css.minWidth = css.width;
      css.position = 'relative';
      css.width = 'auto';
    }

    this.$dropdown.css(css);
  };

  AttachBody.prototype._showDropdown = function (decorated) {
    this.$dropdownContainer.appendTo(this.$dropdownParent);

    this._positionDropdown();
    this._resizeDropdown();
  };

  return AttachBody;
});

S2.define('select2/dropdown/minimumResultsForSearch',[

], function () {
  function countResults (data) {
    var count = 0;

    for (var d = 0; d < data.length; d++) {
      var item = data[d];

      if (item.children) {
        count += countResults(item.children);
      } else {
        count++;
      }
    }

    return count;
  }

  function MinimumResultsForSearch (decorated, $element, options, dataAdapter) {
    this.minimumResultsForSearch = options.get('minimumResultsForSearch');

    if (this.minimumResultsForSearch < 0) {
      this.minimumResultsForSearch = Infinity;
    }

    decorated.call(this, $element, options, dataAdapter);
  }

  MinimumResultsForSearch.prototype.showSearch = function (decorated, params) {
    if (countResults(params.data.results) < this.minimumResultsForSearch) {
      return false;
    }

    return decorated.call(this, params);
  };

  return MinimumResultsForSearch;
});

S2.define('select2/dropdown/selectOnClose',[
  '../utils'
], function (Utils) {
  function SelectOnClose () { }

  SelectOnClose.prototype.bind = function (decorated, container, $container) {
    var self = this;

    decorated.call(this, container, $container);

    container.on('close', function (params) {
      self._handleSelectOnClose(params);
    });
  };

  SelectOnClose.prototype._handleSelectOnClose = function (_, params) {
    if (params && params.originalSelect2Event != null) {
      var event = params.originalSelect2Event;

      // Don't select an item if the close event was triggered from a select or
      // unselect event
      if (event._type === 'select' || event._type === 'unselect') {
        return;
      }
    }

    var $highlightedResults = this.getHighlightedResults();

    // Only select highlighted results
    if ($highlightedResults.length < 1) {
      return;
    }

    var data = Utils.GetData($highlightedResults[0], 'data');

    // Don't re-select already selected resulte
    if (
      (data.element != null && data.element.selected) ||
      (data.element == null && data.selected)
    ) {
      return;
    }

    this.trigger('select', {
        data: data
    });
  };

  return SelectOnClose;
});

S2.define('select2/dropdown/closeOnSelect',[

], function () {
  function CloseOnSelect () { }

  CloseOnSelect.prototype.bind = function (decorated, container, $container) {
    var self = this;

    decorated.call(this, container, $container);

    container.on('select', function (evt) {
      self._selectTriggered(evt);
    });

    container.on('unselect', function (evt) {
      self._selectTriggered(evt);
    });
  };

  CloseOnSelect.prototype._selectTriggered = function (_, evt) {
    var originalEvent = evt.originalEvent;

    // Don't close if the control key is being held
    if (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey)) {
      return;
    }

    this.trigger('close', {
      originalEvent: originalEvent,
      originalSelect2Event: evt
    });
  };

  return CloseOnSelect;
});

S2.define('select2/i18n/en',[],function () {
  // English
  return {
    errorLoading: function () {
      return 'The results could not be loaded.';
    },
    inputTooLong: function (args) {
      var overChars = args.input.length - args.maximum;

      var message = 'Please delete ' + overChars + ' character';

      if (overChars != 1) {
        message += 's';
      }

      return message;
    },
    inputTooShort: function (args) {
      var remainingChars = args.minimum - args.input.length;

      var message = 'Please enter ' + remainingChars + ' or more characters';

      return message;
    },
    loadingMore: function () {
      return 'Loading more results…';
    },
    maximumSelected: function (args) {
      var message = 'You can only select ' + args.maximum + ' item';

      if (args.maximum != 1) {
        message += 's';
      }

      return message;
    },
    noResults: function () {
      return 'No results found';
    },
    searching: function () {
      return 'Searching…';
    },
    removeAllItems: function () {
      return 'Remove all items';
    }
  };
});

S2.define('select2/defaults',[
  'jquery',
  'require',

  './results',

  './selection/single',
  './selection/multiple',
  './selection/placeholder',
  './selection/allowClear',
  './selection/search',
  './selection/eventRelay',

  './utils',
  './translation',
  './diacritics',

  './data/select',
  './data/array',
  './data/ajax',
  './data/tags',
  './data/tokenizer',
  './data/minimumInputLength',
  './data/maximumInputLength',
  './data/maximumSelectionLength',

  './dropdown',
  './dropdown/search',
  './dropdown/hidePlaceholder',
  './dropdown/infiniteScroll',
  './dropdown/attachBody',
  './dropdown/minimumResultsForSearch',
  './dropdown/selectOnClose',
  './dropdown/closeOnSelect',

  './i18n/en'
], function ($, require,

             ResultsList,

             SingleSelection, MultipleSelection, Placeholder, AllowClear,
             SelectionSearch, EventRelay,

             Utils, Translation, DIACRITICS,

             SelectData, ArrayData, AjaxData, Tags, Tokenizer,
             MinimumInputLength, MaximumInputLength, MaximumSelectionLength,

             Dropdown, DropdownSearch, HidePlaceholder, InfiniteScroll,
             AttachBody, MinimumResultsForSearch, SelectOnClose, CloseOnSelect,

             EnglishTranslation) {
  function Defaults () {
    this.reset();
  }

  Defaults.prototype.apply = function (options) {
    options = $.extend(true, {}, this.defaults, options);

    if (options.dataAdapter == null) {
      if (options.ajax != null) {
        options.dataAdapter = AjaxData;
      } else if (options.data != null) {
        options.dataAdapter = ArrayData;
      } else {
        options.dataAdapter = SelectData;
      }

      if (options.minimumInputLength > 0) {
        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          MinimumInputLength
        );
      }

      if (options.maximumInputLength > 0) {
        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          MaximumInputLength
        );
      }

      if (options.maximumSelectionLength > 0) {
        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          MaximumSelectionLength
        );
      }

      if (options.tags) {
        options.dataAdapter = Utils.Decorate(options.dataAdapter, Tags);
      }

      if (options.tokenSeparators != null || options.tokenizer != null) {
        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          Tokenizer
        );
      }

      if (options.query != null) {
        var Query = require(options.amdBase + 'compat/query');

        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          Query
        );
      }

      if (options.initSelection != null) {
        var InitSelection = require(options.amdBase + 'compat/initSelection');

        options.dataAdapter = Utils.Decorate(
          options.dataAdapter,
          InitSelection
        );
      }
    }

    if (options.resultsAdapter == null) {
      options.resultsAdapter = ResultsList;

      if (options.ajax != null) {
        options.resultsAdapter = Utils.Decorate(
          options.resultsAdapter,
          InfiniteScroll
        );
      }

      if (options.placeholder != null) {
        options.resultsAdapter = Utils.Decorate(
          options.resultsAdapter,
          HidePlaceholder
        );
      }

      if (options.selectOnClose) {
        options.resultsAdapter = Utils.Decorate(
          options.resultsAdapter,
          SelectOnClose
        );
      }
    }

    if (options.dropdownAdapter == null) {
      if (options.multiple) {
        options.dropdownAdapter = Dropdown;
      } else {
        var SearchableDropdown = Utils.Decorate(Dropdown, DropdownSearch);

        options.dropdownAdapter = SearchableDropdown;
      }

      if (options.minimumResultsForSearch !== 0) {
        options.dropdownAdapter = Utils.Decorate(
          options.dropdownAdapter,
          MinimumResultsForSearch
        );
      }

      if (options.closeOnSelect) {
        options.dropdownAdapter = Utils.Decorate(
          options.dropdownAdapter,
          CloseOnSelect
        );
      }

      if (
        options.dropdownCssClass != null ||
        options.dropdownCss != null ||
        options.adaptDropdownCssClass != null
      ) {
        var DropdownCSS = require(options.amdBase + 'compat/dropdownCss');

        options.dropdownAdapter = Utils.Decorate(
          options.dropdownAdapter,
          DropdownCSS
        );
      }

      options.dropdownAdapter = Utils.Decorate(
        options.dropdownAdapter,
        AttachBody
      );
    }

    if (options.selectionAdapter == null) {
      if (options.multiple) {
        options.selectionAdapter = MultipleSelection;
      } else {
        options.selectionAdapter = SingleSelection;
      }

      // Add the placeholder mixin if a placeholder was specified
      if (options.placeholder != null) {
        options.selectionAdapter = Utils.Decorate(
          options.selectionAdapter,
          Placeholder
        );
      }

      if (options.allowClear) {
        options.selectionAdapter = Utils.Decorate(
          options.selectionAdapter,
          AllowClear
        );
      }

      if (options.multiple) {
        options.selectionAdapter = Utils.Decorate(
          options.selectionAdapter,
          SelectionSearch
        );
      }

      if (
        options.containerCssClass != null ||
        options.containerCss != null ||
        options.adaptContainerCssClass != null
      ) {
        var ContainerCSS = require(options.amdBase + 'compat/containerCss');

        options.selectionAdapter = Utils.Decorate(
          options.selectionAdapter,
          ContainerCSS
        );
      }

      options.selectionAdapter = Utils.Decorate(
        options.selectionAdapter,
        EventRelay
      );
    }

    // If the defaults were not previously applied from an element, it is
    // possible for the language option to have not been resolved
    options.language = this._resolveLanguage(options.language);

    // Always fall back to English since it will always be complete
    options.language.push('en');

    var uniqueLanguages = [];

    for (var l = 0; l < options.language.length; l++) {
      var language = options.language[l];

      if (uniqueLanguages.indexOf(language) === -1) {
        uniqueLanguages.push(language);
      }
    }

    options.language = uniqueLanguages;

    options.translations = this._processTranslations(
      options.language,
      options.debug
    );

    return options;
  };

  Defaults.prototype.reset = function () {
    function stripDiacritics (text) {
      // Used 'uni range + named function' from http://jsperf.com/diacritics/18
      function match(a) {
        return DIACRITICS[a] || a;
      }

      return text.replace(/[^\u0000-\u007E]/g, match);
    }

    function matcher (params, data) {
      // Always return the object if there is nothing to compare
      if ($.trim(params.term) === '') {
        return data;
      }

      // Do a recursive check for options with children
      if (data.children && data.children.length > 0) {
        // Clone the data object if there are children
        // This is required as we modify the object to remove any non-matches
        var match = $.extend(true, {}, data);

        // Check each child of the option
        for (var c = data.children.length - 1; c >= 0; c--) {
          var child = data.children[c];

          var matches = matcher(params, child);

          // If there wasn't a match, remove the object in the array
          if (matches == null) {
            match.children.splice(c, 1);
          }
        }

        // If any children matched, return the new object
        if (match.children.length > 0) {
          return match;
        }

        // If there were no matching children, check just the plain object
        return matcher(params, match);
      }

      var original = stripDiacritics(data.text).toUpperCase();
      var term = stripDiacritics(params.term).toUpperCase();

      // Check if the text contains the term
      if (original.indexOf(term) > -1) {
        return data;
      }

      // If it doesn't contain the term, don't return anything
      return null;
    }

    this.defaults = {
      amdBase: './',
      amdLanguageBase: './i18n/',
      closeOnSelect: true,
      debug: false,
      dropdownAutoWidth: false,
      escapeMarkup: Utils.escapeMarkup,
      language: {},
      matcher: matcher,
      minimumInputLength: 0,
      maximumInputLength: 0,
      maximumSelectionLength: 0,
      minimumResultsForSearch: 0,
      selectOnClose: false,
      scrollAfterSelect: false,
      sorter: function (data) {
        return data;
      },
      templateResult: function (result) {
        return result.text;
      },
      templateSelection: function (selection) {
        return selection.text;
      },
      theme: 'default',
      width: 'resolve'
    };
  };

  Defaults.prototype.applyFromElement = function (options, $element) {
    var optionLanguage = options.language;
    var defaultLanguage = this.defaults.language;
    var elementLanguage = $element.prop('lang');
    var parentLanguage = $element.closest('[lang]').prop('lang');

    var languages = Array.prototype.concat.call(
      this._resolveLanguage(elementLanguage),
      this._resolveLanguage(optionLanguage),
      this._resolveLanguage(defaultLanguage),
      this._resolveLanguage(parentLanguage)
    );

    options.language = languages;

    return options;
  };

  Defaults.prototype._resolveLanguage = function (language) {
    if (!language) {
      return [];
    }

    if ($.isEmptyObject(language)) {
      return [];
    }

    if ($.isPlainObject(language)) {
      return [language];
    }

    var languages;

    if (!$.isArray(language)) {
      languages = [language];
    } else {
      languages = language;
    }

    var resolvedLanguages = [];

    for (var l = 0; l < languages.length; l++) {
      resolvedLanguages.push(languages[l]);

      if (typeof languages[l] === 'string' && languages[l].indexOf('-') > 0) {
        // Extract the region information if it is included
        var languageParts = languages[l].split('-');
        var baseLanguage = languageParts[0];

        resolvedLanguages.push(baseLanguage);
      }
    }

    return resolvedLanguages;
  };

  Defaults.prototype._processTranslations = function (languages, debug) {
    var translations = new Translation();

    for (var l = 0; l < languages.length; l++) {
      var languageData = new Translation();

      var language = languages[l];

      if (typeof language === 'string') {
        try {
          // Try to load it with the original name
          languageData = Translation.loadPath(language);
        } catch (e) {
          try {
            // If we couldn't load it, check if it wasn't the full path
            language = this.defaults.amdLanguageBase + language;
            languageData = Translation.loadPath(language);
          } catch (ex) {
            // The translation could not be loaded at all. Sometimes this is
            // because of a configuration problem, other times this can be
            // because of how Select2 helps load all possible translation files
            if (debug && window.console && console.warn) {
              console.warn(
                'Select2: The language file for "' + language + '" could ' +
                'not be automatically loaded. A fallback will be used instead.'
              );
            }
          }
        }
      } else if ($.isPlainObject(language)) {
        languageData = new Translation(language);
      } else {
        languageData = language;
      }

      translations.extend(languageData);
    }

    return translations;
  };

  Defaults.prototype.set = function (key, value) {
    var camelKey = $.camelCase(key);

    var data = {};
    data[camelKey] = value;

    var convertedData = Utils._convertData(data);

    $.extend(true, this.defaults, convertedData);
  };

  var defaults = new Defaults();

  return defaults;
});

S2.define('select2/options',[
  'require',
  'jquery',
  './defaults',
  './utils'
], function (require, $, Defaults, Utils) {
  function Options (options, $element) {
    this.options = options;

    if ($element != null) {
      this.fromElement($element);
    }

    if ($element != null) {
      this.options = Defaults.applyFromElement(this.options, $element);
    }

    this.options = Defaults.apply(this.options);

    if ($element && $element.is('input')) {
      var InputCompat = require(this.get('amdBase') + 'compat/inputData');

      this.options.dataAdapter = Utils.Decorate(
        this.options.dataAdapter,
        InputCompat
      );
    }
  }

  Options.prototype.fromElement = function ($e) {
    var excludedData = ['select2'];

    if (this.options.multiple == null) {
      this.options.multiple = $e.prop('multiple');
    }

    if (this.options.disabled == null) {
      this.options.disabled = $e.prop('disabled');
    }

    if (this.options.dir == null) {
      if ($e.prop('dir')) {
        this.options.dir = $e.prop('dir');
      } else if ($e.closest('[dir]').prop('dir')) {
        this.options.dir = $e.closest('[dir]').prop('dir');
      } else {
        this.options.dir = 'ltr';
      }
    }

    $e.prop('disabled', this.options.disabled);
    $e.prop('multiple', this.options.multiple);

    if (Utils.GetData($e[0], 'select2Tags')) {
      if (this.options.debug && window.console && console.warn) {
        console.warn(
          'Select2: The `data-select2-tags` attribute has been changed to ' +
          'use the `data-data` and `data-tags="true"` attributes and will be ' +
          'removed in future versions of Select2.'
        );
      }

      Utils.StoreData($e[0], 'data', Utils.GetData($e[0], 'select2Tags'));
      Utils.StoreData($e[0], 'tags', true);
    }

    if (Utils.GetData($e[0], 'ajaxUrl')) {
      if (this.options.debug && window.console && console.warn) {
        console.warn(
          'Select2: The `data-ajax-url` attribute has been changed to ' +
          '`data-ajax--url` and support for the old attribute will be removed' +
          ' in future versions of Select2.'
        );
      }

      $e.attr('ajax--url', Utils.GetData($e[0], 'ajaxUrl'));
      Utils.StoreData($e[0], 'ajax-Url', Utils.GetData($e[0], 'ajaxUrl'));
    }

    var dataset = {};

    function upperCaseLetter(_, letter) {
      return letter.toUpperCase();
    }

    // Pre-load all of the attributes which are prefixed with `data-`
    for (var attr = 0; attr < $e[0].attributes.length; attr++) {
      var attributeName = $e[0].attributes[attr].name;
      var prefix = 'data-';

      if (attributeName.substr(0, prefix.length) == prefix) {
        // Get the contents of the attribute after `data-`
        var dataName = attributeName.substring(prefix.length);

        // Get the data contents from the consistent source
        // This is more than likely the jQuery data helper
        var dataValue = Utils.GetData($e[0], dataName);

        // camelCase the attribute name to match the spec
        var camelDataName = dataName.replace(/-([a-z])/g, upperCaseLetter);

        // Store the data attribute contents into the dataset since
        dataset[camelDataName] = dataValue;
      }
    }

    // Prefer the element's `dataset` attribute if it exists
    // jQuery 1.x does not correctly handle data attributes with multiple dashes
    if ($.fn.jquery && $.fn.jquery.substr(0, 2) == '1.' && $e[0].dataset) {
      dataset = $.extend(true, {}, $e[0].dataset, dataset);
    }

    // Prefer our internal data cache if it exists
    var data = $.extend(true, {}, Utils.GetData($e[0]), dataset);

    data = Utils._convertData(data);

    for (var key in data) {
      if ($.inArray(key, excludedData) > -1) {
        continue;
      }

      if ($.isPlainObject(this.options[key])) {
        $.extend(this.options[key], data[key]);
      } else {
        this.options[key] = data[key];
      }
    }

    return this;
  };

  Options.prototype.get = function (key) {
    return this.options[key];
  };

  Options.prototype.set = function (key, val) {
    this.options[key] = val;
  };

  return Options;
});

S2.define('select2/core',[
  'jquery',
  './options',
  './utils',
  './keys'
], function ($, Options, Utils, KEYS) {
  var Select2 = function ($element, options) {
    if (Utils.GetData($element[0], 'select2') != null) {
      Utils.GetData($element[0], 'select2').destroy();
    }

    this.$element = $element;

    this.id = this._generateId($element);

    options = options || {};

    this.options = new Options(options, $element);

    Select2.__super__.constructor.call(this);

    // Set up the tabindex

    var tabindex = $element.attr('tabindex') || 0;
    Utils.StoreData($element[0], 'old-tabindex', tabindex);
    $element.attr('tabindex', '-1');

    // Set up containers and adapters

    var DataAdapter = this.options.get('dataAdapter');
    this.dataAdapter = new DataAdapter($element, this.options);

    var $container = this.render();

    this._placeContainer($container);

    var SelectionAdapter = this.options.get('selectionAdapter');
    this.selection = new SelectionAdapter($element, this.options);
    this.$selection = this.selection.render();

    this.selection.position(this.$selection, $container);

    var DropdownAdapter = this.options.get('dropdownAdapter');
    this.dropdown = new DropdownAdapter($element, this.options);
    this.$dropdown = this.dropdown.render();

    this.dropdown.position(this.$dropdown, $container);

    var ResultsAdapter = this.options.get('resultsAdapter');
    this.results = new ResultsAdapter($element, this.options, this.dataAdapter);
    this.$results = this.results.render();

    this.results.position(this.$results, this.$dropdown);

    // Bind events

    var self = this;

    // Bind the container to all of the adapters
    this._bindAdapters();

    // Register any DOM event handlers
    this._registerDomEvents();

    // Register any internal event handlers
    this._registerDataEvents();
    this._registerSelectionEvents();
    this._registerDropdownEvents();
    this._registerResultsEvents();
    this._registerEvents();

    // Set the initial state
    this.dataAdapter.current(function (initialData) {
      self.trigger('selection:update', {
        data: initialData
      });
    });

    // Hide the original select
    $element.addClass('select2-hidden-accessible');
    $element.attr('aria-hidden', 'true');

    // Synchronize any monitored attributes
    this._syncAttributes();

    Utils.StoreData($element[0], 'select2', this);

    // Ensure backwards compatibility with $element.data('select2').
    $element.data('select2', this);
  };

  Utils.Extend(Select2, Utils.Observable);

  Select2.prototype._generateId = function ($element) {
    var id = '';

    if ($element.attr('id') != null) {
      id = $element.attr('id');
    } else if ($element.attr('name') != null) {
      id = $element.attr('name') + '-' + Utils.generateChars(2);
    } else {
      id = Utils.generateChars(4);
    }

    id = id.replace(/(:|\.|\[|\]|,)/g, '');
    id = 'select2-' + id;

    return id;
  };

  Select2.prototype._placeContainer = function ($container) {
    $container.insertAfter(this.$element);

    var width = this._resolveWidth(this.$element, this.options.get('width'));

    if (width != null) {
      $container.css('width', width);
    }
  };

  Select2.prototype._resolveWidth = function ($element, method) {
    var WIDTH = /^width:(([-+]?([0-9]*\.)?[0-9]+)(px|em|ex|%|in|cm|mm|pt|pc))/i;

    if (method == 'resolve') {
      var styleWidth = this._resolveWidth($element, 'style');

      if (styleWidth != null) {
        return styleWidth;
      }

      return this._resolveWidth($element, 'element');
    }

    if (method == 'element') {
      var elementWidth = $element.outerWidth(false);

      if (elementWidth <= 0) {
        return 'auto';
      }

      return elementWidth + 'px';
    }

    if (method == 'style') {
      var style = $element.attr('style');

      if (typeof(style) !== 'string') {
        return null;
      }

      var attrs = style.split(';');

      for (var i = 0, l = attrs.length; i < l; i = i + 1) {
        var attr = attrs[i].replace(/\s/g, '');
        var matches = attr.match(WIDTH);

        if (matches !== null && matches.length >= 1) {
          return matches[1];
        }
      }

      return null;
    }

    if (method == 'computedstyle') {
      var computedStyle = window.getComputedStyle($element[0]);

      return computedStyle.width;
    }

    return method;
  };

  Select2.prototype._bindAdapters = function () {
    this.dataAdapter.bind(this, this.$container);
    this.selection.bind(this, this.$container);

    this.dropdown.bind(this, this.$container);
    this.results.bind(this, this.$container);
  };

  Select2.prototype._registerDomEvents = function () {
    var self = this;

    this.$element.on('change.select2', function () {
      self.dataAdapter.current(function (data) {
        self.trigger('selection:update', {
          data: data
        });
      });
    });

    this.$element.on('focus.select2', function (evt) {
      self.trigger('focus', evt);
    });

    this._syncA = Utils.bind(this._syncAttributes, this);
    this._syncS = Utils.bind(this._syncSubtree, this);

    if (this.$element[0].attachEvent) {
      this.$element[0].attachEvent('onpropertychange', this._syncA);
    }

    var observer = window.MutationObserver ||
      window.WebKitMutationObserver ||
      window.MozMutationObserver
    ;

    if (observer != null) {
      this._observer = new observer(function (mutations) {
        self._syncA();
        self._syncS(null, mutations);
      });
      this._observer.observe(this.$element[0], {
        attributes: true,
        childList: true,
        subtree: false
      });
    } else if (this.$element[0].addEventListener) {
      this.$element[0].addEventListener(
        'DOMAttrModified',
        self._syncA,
        false
      );
      this.$element[0].addEventListener(
        'DOMNodeInserted',
        self._syncS,
        false
      );
      this.$element[0].addEventListener(
        'DOMNodeRemoved',
        self._syncS,
        false
      );
    }
  };

  Select2.prototype._registerDataEvents = function () {
    var self = this;

    this.dataAdapter.on('*', function (name, params) {
      self.trigger(name, params);
    });
  };

  Select2.prototype._registerSelectionEvents = function () {
    var self = this;
    var nonRelayEvents = ['toggle', 'focus'];

    this.selection.on('toggle', function () {
      self.toggleDropdown();
    });

    this.selection.on('focus', function (params) {
      self.focus(params);
    });

    this.selection.on('*', function (name, params) {
      if ($.inArray(name, nonRelayEvents) !== -1) {
        return;
      }

      self.trigger(name, params);
    });
  };

  Select2.prototype._registerDropdownEvents = function () {
    var self = this;

    this.dropdown.on('*', function (name, params) {
      self.trigger(name, params);
    });
  };

  Select2.prototype._registerResultsEvents = function () {
    var self = this;

    this.results.on('*', function (name, params) {
      self.trigger(name, params);
    });
  };

  Select2.prototype._registerEvents = function () {
    var self = this;

    this.on('open', function () {
      self.$container.addClass('select2-container--open');
    });

    this.on('close', function () {
      self.$container.removeClass('select2-container--open');
    });

    this.on('enable', function () {
      self.$container.removeClass('select2-container--disabled');
    });

    this.on('disable', function () {
      self.$container.addClass('select2-container--disabled');
    });

    this.on('blur', function () {
      self.$container.removeClass('select2-container--focus');
    });

    this.on('query', function (params) {
      if (!self.isOpen()) {
        self.trigger('open', {});
      }

      this.dataAdapter.query(params, function (data) {
        self.trigger('results:all', {
          data: data,
          query: params
        });
      });
    });

    this.on('query:append', function (params) {
      this.dataAdapter.query(params, function (data) {
        self.trigger('results:append', {
          data: data,
          query: params
        });
      });
    });

    this.on('keypress', function (evt) {
      var key = evt.which;

      if (self.isOpen()) {
        if (key === KEYS.ESC || key === KEYS.TAB ||
            (key === KEYS.UP && evt.altKey)) {
          self.close(evt);

          evt.preventDefault();
        } else if (key === KEYS.ENTER) {
          self.trigger('results:select', {});

          evt.preventDefault();
        } else if ((key === KEYS.SPACE && evt.ctrlKey)) {
          self.trigger('results:toggle', {});

          evt.preventDefault();
        } else if (key === KEYS.UP) {
          self.trigger('results:previous', {});

          evt.preventDefault();
        } else if (key === KEYS.DOWN) {
          self.trigger('results:next', {});

          evt.preventDefault();
        }
      } else {
        if (key === KEYS.ENTER || key === KEYS.SPACE ||
            (key === KEYS.DOWN && evt.altKey)) {
          self.open();

          evt.preventDefault();
        }
      }
    });
  };

  Select2.prototype._syncAttributes = function () {
    this.options.set('disabled', this.$element.prop('disabled'));

    if (this.isDisabled()) {
      if (this.isOpen()) {
        this.close();
      }

      this.trigger('disable', {});
    } else {
      this.trigger('enable', {});
    }
  };

  Select2.prototype._isChangeMutation = function (evt, mutations) {
    var changed = false;
    var self = this;

    // Ignore any mutation events raised for elements that aren't options or
    // optgroups. This handles the case when the select element is destroyed
    if (
      evt && evt.target && (
        evt.target.nodeName !== 'OPTION' && evt.target.nodeName !== 'OPTGROUP'
      )
    ) {
      return;
    }

    if (!mutations) {
      // If mutation events aren't supported, then we can only assume that the
      // change affected the selections
      changed = true;
    } else if (mutations.addedNodes && mutations.addedNodes.length > 0) {
      for (var n = 0; n < mutations.addedNodes.length; n++) {
        var node = mutations.addedNodes[n];

        if (node.selected) {
          changed = true;
        }
      }
    } else if (mutations.removedNodes && mutations.removedNodes.length > 0) {
      changed = true;
    } else if ($.isArray(mutations)) {
      $.each(mutations, function(evt, mutation) {
        if (self._isChangeMutation(evt, mutation)) {
          // We've found a change mutation.
          // Let's escape from the loop and continue
          changed = true;
          return false;
        }
      });
    }
    return changed;
  };

  Select2.prototype._syncSubtree = function (evt, mutations) {
    var changed = this._isChangeMutation(evt, mutations);
    var self = this;

    // Only re-pull the data if we think there is a change
    if (changed) {
      this.dataAdapter.current(function (currentData) {
        self.trigger('selection:update', {
          data: currentData
        });
      });
    }
  };

  /**
   * Override the trigger method to automatically trigger pre-events when
   * there are events that can be prevented.
   */
  Select2.prototype.trigger = function (name, args) {
    var actualTrigger = Select2.__super__.trigger;
    var preTriggerMap = {
      'open': 'opening',
      'close': 'closing',
      'select': 'selecting',
      'unselect': 'unselecting',
      'clear': 'clearing'
    };

    if (args === undefined) {
      args = {};
    }

    if (name in preTriggerMap) {
      var preTriggerName = preTriggerMap[name];
      var preTriggerArgs = {
        prevented: false,
        name: name,
        args: args
      };

      actualTrigger.call(this, preTriggerName, preTriggerArgs);

      if (preTriggerArgs.prevented) {
        args.prevented = true;

        return;
      }
    }

    actualTrigger.call(this, name, args);
  };

  Select2.prototype.toggleDropdown = function () {
    if (this.isDisabled()) {
      return;
    }

    if (this.isOpen()) {
      this.close();
    } else {
      this.open();
    }
  };

  Select2.prototype.open = function () {
    if (this.isOpen()) {
      return;
    }

    if (this.isDisabled()) {
      return;
    }

    this.trigger('query', {});
  };

  Select2.prototype.close = function (evt) {
    if (!this.isOpen()) {
      return;
    }

    this.trigger('close', { originalEvent : evt });
  };

  /**
   * Helper method to abstract the "enabled" (not "disabled") state of this
   * object.
   *
   * @return {true} if the instance is not disabled.
   * @return {false} if the instance is disabled.
   */
  Select2.prototype.isEnabled = function () {
    return !this.isDisabled();
  };

  /**
   * Helper method to abstract the "disabled" state of this object.
   *
   * @return {true} if the disabled option is true.
   * @return {false} if the disabled option is false.
   */
  Select2.prototype.isDisabled = function () {
    return this.options.get('disabled');
  };

  Select2.prototype.isOpen = function () {
    return this.$container.hasClass('select2-container--open');
  };

  Select2.prototype.hasFocus = function () {
    return this.$container.hasClass('select2-container--focus');
  };

  Select2.prototype.focus = function (data) {
    // No need to re-trigger focus events if we are already focused
    if (this.hasFocus()) {
      return;
    }

    this.$container.addClass('select2-container--focus');
    this.trigger('focus', {});
  };

  Select2.prototype.enable = function (args) {
    if (this.options.get('debug') && window.console && console.warn) {
      console.warn(
        'Select2: The `select2("enable")` method has been deprecated and will' +
        ' be removed in later Select2 versions. Use $element.prop("disabled")' +
        ' instead.'
      );
    }

    if (args == null || args.length === 0) {
      args = [true];
    }

    var disabled = !args[0];

    this.$element.prop('disabled', disabled);
  };

  Select2.prototype.data = function () {
    if (this.options.get('debug') &&
        arguments.length > 0 && window.console && console.warn) {
      console.warn(
        'Select2: Data can no longer be set using `select2("data")`. You ' +
        'should consider setting the value instead using `$element.val()`.'
      );
    }

    var data = [];

    this.dataAdapter.current(function (currentData) {
      data = currentData;
    });

    return data;
  };

  Select2.prototype.val = function (args) {
    if (this.options.get('debug') && window.console && console.warn) {
      console.warn(
        'Select2: The `select2("val")` method has been deprecated and will be' +
        ' removed in later Select2 versions. Use $element.val() instead.'
      );
    }

    if (args == null || args.length === 0) {
      return this.$element.val();
    }

    var newVal = args[0];

    if ($.isArray(newVal)) {
      newVal = $.map(newVal, function (obj) {
        return obj.toString();
      });
    }

    this.$element.val(newVal).trigger('input').trigger('change');
  };

  Select2.prototype.destroy = function () {
    this.$container.remove();

    if (this.$element[0].detachEvent) {
      this.$element[0].detachEvent('onpropertychange', this._syncA);
    }

    if (this._observer != null) {
      this._observer.disconnect();
      this._observer = null;
    } else if (this.$element[0].removeEventListener) {
      this.$element[0]
        .removeEventListener('DOMAttrModified', this._syncA, false);
      this.$element[0]
        .removeEventListener('DOMNodeInserted', this._syncS, false);
      this.$element[0]
        .removeEventListener('DOMNodeRemoved', this._syncS, false);
    }

    this._syncA = null;
    this._syncS = null;

    this.$element.off('.select2');
    this.$element.attr('tabindex',
    Utils.GetData(this.$element[0], 'old-tabindex'));

    this.$element.removeClass('select2-hidden-accessible');
    this.$element.attr('aria-hidden', 'false');
    Utils.RemoveData(this.$element[0]);
    this.$element.removeData('select2');

    this.dataAdapter.destroy();
    this.selection.destroy();
    this.dropdown.destroy();
    this.results.destroy();

    this.dataAdapter = null;
    this.selection = null;
    this.dropdown = null;
    this.results = null;
  };

  Select2.prototype.render = function () {
    var $container = $(
      '<span class="select2 select2-container">' +
        '<span class="selection"></span>' +
        '<span class="dropdown-wrapper" aria-hidden="true"></span>' +
      '</span>'
    );

    $container.attr('dir', this.options.get('dir'));

    this.$container = $container;

    this.$container.addClass('select2-container--' + this.options.get('theme'));

    Utils.StoreData($container[0], 'element', this.$element);

    return $container;
  };

  return Select2;
});

S2.define('select2/compat/utils',[
  'jquery'
], function ($) {
  function syncCssClasses ($dest, $src, adapter) {
    var classes, replacements = [], adapted;

    classes = $.trim($dest.attr('class'));

    if (classes) {
      classes = '' + classes; // for IE which returns object

      $(classes.split(/\s+/)).each(function () {
        // Save all Select2 classes
        if (this.indexOf('select2-') === 0) {
          replacements.push(this);
        }
      });
    }

    classes = $.trim($src.attr('class'));

    if (classes) {
      classes = '' + classes; // for IE which returns object

      $(classes.split(/\s+/)).each(function () {
        // Only adapt non-Select2 classes
        if (this.indexOf('select2-') !== 0) {
          adapted = adapter(this);

          if (adapted != null) {
            replacements.push(adapted);
          }
        }
      });
    }

    $dest.attr('class', replacements.join(' '));
  }

  return {
    syncCssClasses: syncCssClasses
  };
});

S2.define('select2/compat/containerCss',[
  'jquery',
  './utils'
], function ($, CompatUtils) {
  // No-op CSS adapter that discards all classes by default
  function _containerAdapter (clazz) {
    return null;
  }

  function ContainerCSS () { }

  ContainerCSS.prototype.render = function (decorated) {
    var $container = decorated.call(this);

    var containerCssClass = this.options.get('containerCssClass') || '';

    if ($.isFunction(containerCssClass)) {
      containerCssClass = containerCssClass(this.$element);
    }

    var containerCssAdapter = this.options.get('adaptContainerCssClass');
    containerCssAdapter = containerCssAdapter || _containerAdapter;

    if (containerCssClass.indexOf(':all:') !== -1) {
      containerCssClass = containerCssClass.replace(':all:', '');

      var _cssAdapter = containerCssAdapter;

      containerCssAdapter = function (clazz) {
        var adapted = _cssAdapter(clazz);

        if (adapted != null) {
          // Append the old one along with the adapted one
          return adapted + ' ' + clazz;
        }

        return clazz;
      };
    }

    var containerCss = this.options.get('containerCss') || {};

    if ($.isFunction(containerCss)) {
      containerCss = containerCss(this.$element);
    }

    CompatUtils.syncCssClasses($container, this.$element, containerCssAdapter);

    $container.css(containerCss);
    $container.addClass(containerCssClass);

    return $container;
  };

  return ContainerCSS;
});

S2.define('select2/compat/dropdownCss',[
  'jquery',
  './utils'
], function ($, CompatUtils) {
  // No-op CSS adapter that discards all classes by default
  function _dropdownAdapter (clazz) {
    return null;
  }

  function DropdownCSS () { }

  DropdownCSS.prototype.render = function (decorated) {
    var $dropdown = decorated.call(this);

    var dropdownCssClass = this.options.get('dropdownCssClass') || '';

    if ($.isFunction(dropdownCssClass)) {
      dropdownCssClass = dropdownCssClass(this.$element);
    }

    var dropdownCssAdapter = this.options.get('adaptDropdownCssClass');
    dropdownCssAdapter = dropdownCssAdapter || _dropdownAdapter;

    if (dropdownCssClass.indexOf(':all:') !== -1) {
      dropdownCssClass = dropdownCssClass.replace(':all:', '');

      var _cssAdapter = dropdownCssAdapter;

      dropdownCssAdapter = function (clazz) {
        var adapted = _cssAdapter(clazz);

        if (adapted != null) {
          // Append the old one along with the adapted one
          return adapted + ' ' + clazz;
        }

        return clazz;
      };
    }

    var dropdownCss = this.options.get('dropdownCss') || {};

    if ($.isFunction(dropdownCss)) {
      dropdownCss = dropdownCss(this.$element);
    }

    CompatUtils.syncCssClasses($dropdown, this.$element, dropdownCssAdapter);

    $dropdown.css(dropdownCss);
    $dropdown.addClass(dropdownCssClass);

    return $dropdown;
  };

  return DropdownCSS;
});

S2.define('select2/compat/initSelection',[
  'jquery'
], function ($) {
  function InitSelection (decorated, $element, options) {
    if (options.get('debug') && window.console && console.warn) {
      console.warn(
        'Select2: The `initSelection` option has been deprecated in favor' +
        ' of a custom data adapter that overrides the `current` method. ' +
        'This method is now called multiple times instead of a single ' +
        'time when the instance is initialized. Support will be removed ' +
        'for the `initSelection` option in future versions of Select2'
      );
    }

    this.initSelection = options.get('initSelection');
    this._isInitialized = false;

    decorated.call(this, $element, options);
  }

  InitSelection.prototype.current = function (decorated, callback) {
    var self = this;

    if (this._isInitialized) {
      decorated.call(this, callback);

      return;
    }

    this.initSelection.call(null, this.$element, function (data) {
      self._isInitialized = true;

      if (!$.isArray(data)) {
        data = [data];
      }

      callback(data);
    });
  };

  return InitSelection;
});

S2.define('select2/compat/inputData',[
  'jquery',
  '../utils'
], function ($, Utils) {
  function InputData (decorated, $element, options) {
    this._currentData = [];
    this._valueSeparator = options.get('valueSeparator') || ',';

    if ($element.prop('type') === 'hidden') {
      if (options.get('debug') && console && console.warn) {
        console.warn(
          'Select2: Using a hidden input with Select2 is no longer ' +
          'supported and may stop working in the future. It is recommended ' +
          'to use a `<select>` element instead.'
        );
      }
    }

    decorated.call(this, $element, options);
  }

  InputData.prototype.current = function (_, callback) {
    function getSelected (data, selectedIds) {
      var selected = [];

      if (data.selected || $.inArray(data.id, selectedIds) !== -1) {
        data.selected = true;
        selected.push(data);
      } else {
        data.selected = false;
      }

      if (data.children) {
        selected.push.apply(selected, getSelected(data.children, selectedIds));
      }

      return selected;
    }

    var selected = [];

    for (var d = 0; d < this._currentData.length; d++) {
      var data = this._currentData[d];

      selected.push.apply(
        selected,
        getSelected(
          data,
          this.$element.val().split(
            this._valueSeparator
          )
        )
      );
    }

    callback(selected);
  };

  InputData.prototype.select = function (_, data) {
    if (!this.options.get('multiple')) {
      this.current(function (allData) {
        $.map(allData, function (data) {
          data.selected = false;
        });
      });

      this.$element.val(data.id);
      this.$element.trigger('input').trigger('change');
    } else {
      var value = this.$element.val();
      value += this._valueSeparator + data.id;

      this.$element.val(value);
      this.$element.trigger('input').trigger('change');
    }
  };

  InputData.prototype.unselect = function (_, data) {
    var self = this;

    data.selected = false;

    this.current(function (allData) {
      var values = [];

      for (var d = 0; d < allData.length; d++) {
        var item = allData[d];

        if (data.id == item.id) {
          continue;
        }

        values.push(item.id);
      }

      self.$element.val(values.join(self._valueSeparator));
      self.$element.trigger('input').trigger('change');
    });
  };

  InputData.prototype.query = function (_, params, callback) {
    var results = [];

    for (var d = 0; d < this._currentData.length; d++) {
      var data = this._currentData[d];

      var matches = this.matches(params, data);

      if (matches !== null) {
        results.push(matches);
      }
    }

    callback({
      results: results
    });
  };

  InputData.prototype.addOptions = function (_, $options) {
    var options = $.map($options, function ($option) {
      return Utils.GetData($option[0], 'data');
    });

    this._currentData.push.apply(this._currentData, options);
  };

  return InputData;
});

S2.define('select2/compat/matcher',[
  'jquery'
], function ($) {
  function oldMatcher (matcher) {
    function wrappedMatcher (params, data) {
      var match = $.extend(true, {}, data);

      if (params.term == null || $.trim(params.term) === '') {
        return match;
      }

      if (data.children) {
        for (var c = data.children.length - 1; c >= 0; c--) {
          var child = data.children[c];

          // Check if the child object matches
          // The old matcher returned a boolean true or false
          var doesMatch = matcher(params.term, child.text, child);

          // If the child didn't match, pop it off
          if (!doesMatch) {
            match.children.splice(c, 1);
          }
        }

        if (match.children.length > 0) {
          return match;
        }
      }

      if (matcher(params.term, data.text, data)) {
        return match;
      }

      return null;
    }

    return wrappedMatcher;
  }

  return oldMatcher;
});

S2.define('select2/compat/query',[

], function () {
  function Query (decorated, $element, options) {
    if (options.get('debug') && window.console && console.warn) {
      console.warn(
        'Select2: The `query` option has been deprecated in favor of a ' +
        'custom data adapter that overrides the `query` method. Support ' +
        'will be removed for the `query` option in future versions of ' +
        'Select2.'
      );
    }

    decorated.call(this, $element, options);
  }

  Query.prototype.query = function (_, params, callback) {
    params.callback = callback;

    var query = this.options.get('query');

    query.call(null, params);
  };

  return Query;
});

S2.define('select2/dropdown/attachContainer',[

], function () {
  function AttachContainer (decorated, $element, options) {
    decorated.call(this, $element, options);
  }

  AttachContainer.prototype.position =
    function (decorated, $dropdown, $container) {
    var $dropdownContainer = $container.find('.dropdown-wrapper');
    $dropdownContainer.append($dropdown);

    $dropdown.addClass('select2-dropdown--below');
    $container.addClass('select2-container--below');
  };

  return AttachContainer;
});

S2.define('select2/dropdown/stopPropagation',[

], function () {
  function StopPropagation () { }

  StopPropagation.prototype.bind = function (decorated, container, $container) {
    decorated.call(this, container, $container);

    var stoppedEvents = [
    'blur',
    'change',
    'click',
    'dblclick',
    'focus',
    'focusin',
    'focusout',
    'input',
    'keydown',
    'keyup',
    'keypress',
    'mousedown',
    'mouseenter',
    'mouseleave',
    'mousemove',
    'mouseover',
    'mouseup',
    'search',
    'touchend',
    'touchstart'
    ];

    this.$dropdown.on(stoppedEvents.join(' '), function (evt) {
      evt.stopPropagation();
    });
  };

  return StopPropagation;
});

S2.define('select2/selection/stopPropagation',[

], function () {
  function StopPropagation () { }

  StopPropagation.prototype.bind = function (decorated, container, $container) {
    decorated.call(this, container, $container);

    var stoppedEvents = [
      'blur',
      'change',
      'click',
      'dblclick',
      'focus',
      'focusin',
      'focusout',
      'input',
      'keydown',
      'keyup',
      'keypress',
      'mousedown',
      'mouseenter',
      'mouseleave',
      'mousemove',
      'mouseover',
      'mouseup',
      'search',
      'touchend',
      'touchstart'
    ];

    this.$selection.on(stoppedEvents.join(' '), function (evt) {
      evt.stopPropagation();
    });
  };

  return StopPropagation;
});

/*!
 * jQuery Mousewheel 3.1.13
 *
 * Copyright jQuery Foundation and other contributors
 * Released under the MIT license
 * http://jquery.org/license
 */

(function (factory) {
    if ( typeof S2.define === 'function' && S2.define.amd ) {
        // AMD. Register as an anonymous module.
        S2.define('jquery-mousewheel',['jquery'], factory);
    } else if (typeof exports === 'object') {
        // Node/CommonJS style for Browserify
        module.exports = factory;
    } else {
        // Browser globals
        factory(jQuery);
    }
}(function ($) {

    var toFix  = ['wheel', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll'],
        toBind = ( 'onwheel' in document || document.documentMode >= 9 ) ?
                    ['wheel'] : ['mousewheel', 'DomMouseScroll', 'MozMousePixelScroll'],
        slice  = Array.prototype.slice,
        nullLowestDeltaTimeout, lowestDelta;

    if ( $.event.fixHooks ) {
        for ( var i = toFix.length; i; ) {
            $.event.fixHooks[ toFix[--i] ] = $.event.mouseHooks;
        }
    }

    var special = $.event.special.mousewheel = {
        version: '3.1.12',

        setup: function() {
            if ( this.addEventListener ) {
                for ( var i = toBind.length; i; ) {
                    this.addEventListener( toBind[--i], handler, false );
                }
            } else {
                this.onmousewheel = handler;
            }
            // Store the line height and page height for this particular element
            $.data(this, 'mousewheel-line-height', special.getLineHeight(this));
            $.data(this, 'mousewheel-page-height', special.getPageHeight(this));
        },

        teardown: function() {
            if ( this.removeEventListener ) {
                for ( var i = toBind.length; i; ) {
                    this.removeEventListener( toBind[--i], handler, false );
                }
            } else {
                this.onmousewheel = null;
            }
            // Clean up the data we added to the element
            $.removeData(this, 'mousewheel-line-height');
            $.removeData(this, 'mousewheel-page-height');
        },

        getLineHeight: function(elem) {
            var $elem = $(elem),
                $parent = $elem['offsetParent' in $.fn ? 'offsetParent' : 'parent']();
            if (!$parent.length) {
                $parent = $('body');
            }
            return parseInt($parent.css('fontSize'), 10) || parseInt($elem.css('fontSize'), 10) || 16;
        },

        getPageHeight: function(elem) {
            return $(elem).height();
        },

        settings: {
            adjustOldDeltas: true, // see shouldAdjustOldDeltas() below
            normalizeOffset: true  // calls getBoundingClientRect for each event
        }
    };

    $.fn.extend({
        mousewheel: function(fn) {
            return fn ? this.bind('mousewheel', fn) : this.trigger('mousewheel');
        },

        unmousewheel: function(fn) {
            return this.unbind('mousewheel', fn);
        }
    });


    function handler(event) {
        var orgEvent   = event || window.event,
            args       = slice.call(arguments, 1),
            delta      = 0,
            deltaX     = 0,
            deltaY     = 0,
            absDelta   = 0,
            offsetX    = 0,
            offsetY    = 0;
        event = $.event.fix(orgEvent);
        event.type = 'mousewheel';

        // Old school scrollwheel delta
        if ( 'detail'      in orgEvent ) { deltaY = orgEvent.detail * -1;      }
        if ( 'wheelDelta'  in orgEvent ) { deltaY = orgEvent.wheelDelta;       }
        if ( 'wheelDeltaY' in orgEvent ) { deltaY = orgEvent.wheelDeltaY;      }
        if ( 'wheelDeltaX' in orgEvent ) { deltaX = orgEvent.wheelDeltaX * -1; }

        // Firefox < 17 horizontal scrolling related to DOMMouseScroll event
        if ( 'axis' in orgEvent && orgEvent.axis === orgEvent.HORIZONTAL_AXIS ) {
            deltaX = deltaY * -1;
            deltaY = 0;
        }

        // Set delta to be deltaY or deltaX if deltaY is 0 for backwards compatabilitiy
        delta = deltaY === 0 ? deltaX : deltaY;

        // New school wheel delta (wheel event)
        if ( 'deltaY' in orgEvent ) {
            deltaY = orgEvent.deltaY * -1;
            delta  = deltaY;
        }
        if ( 'deltaX' in orgEvent ) {
            deltaX = orgEvent.deltaX;
            if ( deltaY === 0 ) { delta  = deltaX * -1; }
        }

        // No change actually happened, no reason to go any further
        if ( deltaY === 0 && deltaX === 0 ) { return; }

        // Need to convert lines and pages to pixels if we aren't already in pixels
        // There are three delta modes:
        //   * deltaMode 0 is by pixels, nothing to do
        //   * deltaMode 1 is by lines
        //   * deltaMode 2 is by pages
        if ( orgEvent.deltaMode === 1 ) {
            var lineHeight = $.data(this, 'mousewheel-line-height');
            delta  *= lineHeight;
            deltaY *= lineHeight;
            deltaX *= lineHeight;
        } else if ( orgEvent.deltaMode === 2 ) {
            var pageHeight = $.data(this, 'mousewheel-page-height');
            delta  *= pageHeight;
            deltaY *= pageHeight;
            deltaX *= pageHeight;
        }

        // Store lowest absolute delta to normalize the delta values
        absDelta = Math.max( Math.abs(deltaY), Math.abs(deltaX) );

        if ( !lowestDelta || absDelta < lowestDelta ) {
            lowestDelta = absDelta;

            // Adjust older deltas if necessary
            if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) {
                lowestDelta /= 40;
            }
        }

        // Adjust older deltas if necessary
        if ( shouldAdjustOldDeltas(orgEvent, absDelta) ) {
            // Divide all the things by 40!
            delta  /= 40;
            deltaX /= 40;
            deltaY /= 40;
        }

        // Get a whole, normalized value for the deltas
        delta  = Math[ delta  >= 1 ? 'floor' : 'ceil' ](delta  / lowestDelta);
        deltaX = Math[ deltaX >= 1 ? 'floor' : 'ceil' ](deltaX / lowestDelta);
        deltaY = Math[ deltaY >= 1 ? 'floor' : 'ceil' ](deltaY / lowestDelta);

        // Normalise offsetX and offsetY properties
        if ( special.settings.normalizeOffset && this.getBoundingClientRect ) {
            var boundingRect = this.getBoundingClientRect();
            offsetX = event.clientX - boundingRect.left;
            offsetY = event.clientY - boundingRect.top;
        }

        // Add information to the event object
        event.deltaX = deltaX;
        event.deltaY = deltaY;
        event.deltaFactor = lowestDelta;
        event.offsetX = offsetX;
        event.offsetY = offsetY;
        // Go ahead and set deltaMode to 0 since we converted to pixels
        // Although this is a little odd since we overwrite the deltaX/Y
        // properties with normalized deltas.
        event.deltaMode = 0;

        // Add event and delta to the front of the arguments
        args.unshift(event, delta, deltaX, deltaY);

        // Clearout lowestDelta after sometime to better
        // handle multiple device types that give different
        // a different lowestDelta
        // Ex: trackpad = 3 and mouse wheel = 120
        if (nullLowestDeltaTimeout) { clearTimeout(nullLowestDeltaTimeout); }
        nullLowestDeltaTimeout = setTimeout(nullLowestDelta, 200);

        return ($.event.dispatch || $.event.handle).apply(this, args);
    }

    function nullLowestDelta() {
        lowestDelta = null;
    }

    function shouldAdjustOldDeltas(orgEvent, absDelta) {
        // If this is an older event and the delta is divisable by 120,
        // then we are assuming that the browser is treating this as an
        // older mouse wheel event and that we should divide the deltas
        // by 40 to try and get a more usable deltaFactor.
        // Side note, this actually impacts the reported scroll distance
        // in older browsers and can cause scrolling to be slower than native.
        // Turn this off by setting $.event.special.mousewheel.settings.adjustOldDeltas to false.
        return special.settings.adjustOldDeltas && orgEvent.type === 'mousewheel' && absDelta % 120 === 0;
    }

}));

S2.define('jquery.select2',[
  'jquery',
  'jquery-mousewheel',

  './select2/core',
  './select2/defaults',
  './select2/utils'
], function ($, _, Select2, Defaults, Utils) {
  if ($.fn.select2 == null) {
    // All methods that should return the element
    var thisMethods = ['open', 'close', 'destroy'];

    $.fn.select2 = function (options) {
      options = options || {};

      if (typeof options === 'object') {
        this.each(function () {
          var instanceOptions = $.extend(true, {}, options);

          var instance = new Select2($(this), instanceOptions);
        });

        return this;
      } else if (typeof options === 'string') {
        var ret;
        var args = Array.prototype.slice.call(arguments, 1);

        this.each(function () {
          var instance = Utils.GetData(this, 'select2');

          if (instance == null && window.console && console.error) {
            console.error(
              'The select2(\'' + options + '\') method was called on an ' +
              'element that is not using Select2.'
            );
          }

          ret = instance[options].apply(instance, args);
        });

        // Check if we should be returning `this`
        if ($.inArray(options, thisMethods) > -1) {
          return this;
        }

        return ret;
      } else {
        throw new Error('Invalid arguments for Select2: ' + options);
      }
    };
  }

  if ($.fn.select2.defaults == null) {
    $.fn.select2.defaults = Defaults;
  }

  return Select2;
});

  // Return the AMD loader configuration so it can be used outside of this file
  return {
    define: S2.define,
    require: S2.require
  };
}());

  // Autoload the jQuery bindings
  // We know that all of the modules exist above this, so we're safe
  var select2 = S2.require('jquery.select2');

  // Hold the AMD module references on the jQuery function that was just loaded
  // This allows Select2 to use the internal loader outside of this file, such
  // as in the language files.
  jQuery.fn.select2.amd = S2;

  // Return the Select2 instance for anyone who is importing it.
  return select2;
}));
/*! Select2 4.0.13 | https://github.com/select2/select2/blob/master/LICENSE.md */


!function(){if(jQuery&&jQuery.fn&&jQuery.fn.select2&&jQuery.fn.select2.amd)var e=jQuery.fn.select2.amd;e.define("select2/i18n/es",[],function(){return{errorLoading:function(){return"No se pudieron cargar los resultados"},inputTooLong:function(e){var n=e.input.length-e.maximum,r="Por favor, elimine "+n+" car";return r+=1==n?"ácter":"acteres"},inputTooShort:function(e){var n=e.minimum-e.input.length,r="Por favor, introduzca "+n+" car";return r+=1==n?"ácter":"acteres"},loadingMore:function(){return"Cargando más resultados…"},maximumSelected:function(e){var n="Sólo puede seleccionar "+e.maximum+" elemento";return 1!=e.maximum&&(n+="s"),n},noResults:function(){return"No se encontraron resultados"},searching:function(){return"Buscando…"},removeAllItems:function(){return"Eliminar todos los elementos"}}}),e.define,e.require}();
window.TinyMCERails = {
  configuration: {
    default: {}
  },

  initialize: function(config, options) {
    if (typeof tinyMCE != 'undefined') {
      // Merge the custom options with the given configuration
      var configuration = TinyMCERails.configuration[config || 'default'];
      configuration = TinyMCERails._merge(configuration, options);

      tinymce.init(configuration);
    } else {
      // Wait until TinyMCE is loaded
      setTimeout(function() {
        TinyMCERails.initialize(config, options);
      }, 50);
    }
  },

  setupTurbolinks: function() {
    // Remove all TinyMCE instances before rendering
    document.addEventListener('turbolinks:before-render', function() {
      tinymce.remove();
    });
  },

  _merge: function() {
    var result = {};

    for (var i = 0; i < arguments.length; ++i) {
      var source = arguments[i];

      for (var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
          if (Object.prototype.toString.call(source[key]) === '[object Object]') {
            result[key] = TinyMCERails._merge(result[key], source[key]);
          } else {
            result[key] = source[key];
          }
        }
      }
    }

    return result;
  }
};

if (typeof Turbolinks != 'undefined' && Turbolinks.supported) {
  TinyMCERails.setupTurbolinks();
}
;
window.tinymce = window.tinymce || {
  base:   '/assets/tinymce',
  suffix: ''
};
/**
 * TinyMCE version 6.8.5 (TBD)
 */


(function () {
    'use strict';

    var typeOf$1 = function (x) {
      if (x === null) {
        return 'null';
      }
      if (x === undefined) {
        return 'undefined';
      }
      var t = typeof x;
      if (t === 'object' && (Array.prototype.isPrototypeOf(x) || x.constructor && x.constructor.name === 'Array')) {
        return 'array';
      }
      if (t === 'object' && (String.prototype.isPrototypeOf(x) || x.constructor && x.constructor.name === 'String')) {
        return 'string';
      }
      return t;
    };
    var isEquatableType = function (x) {
      return [
        'undefined',
        'boolean',
        'number',
        'string',
        'function',
        'xml',
        'null'
      ].indexOf(x) !== -1;
    };

    var sort$1 = function (xs, compareFn) {
      var clone = Array.prototype.slice.call(xs);
      return clone.sort(compareFn);
    };

    var contramap = function (eqa, f) {
      return eq$2(function (x, y) {
        return eqa.eq(f(x), f(y));
      });
    };
    var eq$2 = function (f) {
      return { eq: f };
    };
    var tripleEq = eq$2(function (x, y) {
      return x === y;
    });
    var eqString = tripleEq;
    var eqArray = function (eqa) {
      return eq$2(function (x, y) {
        if (x.length !== y.length) {
          return false;
        }
        var len = x.length;
        for (var i = 0; i < len; i++) {
          if (!eqa.eq(x[i], y[i])) {
            return false;
          }
        }
        return true;
      });
    };
    var eqSortedArray = function (eqa, compareFn) {
      return contramap(eqArray(eqa), function (xs) {
        return sort$1(xs, compareFn);
      });
    };
    var eqRecord = function (eqa) {
      return eq$2(function (x, y) {
        var kx = Object.keys(x);
        var ky = Object.keys(y);
        if (!eqSortedArray(eqString).eq(kx, ky)) {
          return false;
        }
        var len = kx.length;
        for (var i = 0; i < len; i++) {
          var q = kx[i];
          if (!eqa.eq(x[q], y[q])) {
            return false;
          }
        }
        return true;
      });
    };
    var eqAny = eq$2(function (x, y) {
      if (x === y) {
        return true;
      }
      var tx = typeOf$1(x);
      var ty = typeOf$1(y);
      if (tx !== ty) {
        return false;
      }
      if (isEquatableType(tx)) {
        return x === y;
      } else if (tx === 'array') {
        return eqArray(eqAny).eq(x, y);
      } else if (tx === 'object') {
        return eqRecord(eqAny).eq(x, y);
      }
      return false;
    });

    const getPrototypeOf$2 = Object.getPrototypeOf;
    const hasProto = (v, constructor, predicate) => {
      var _a;
      if (predicate(v, constructor.prototype)) {
        return true;
      } else {
        return ((_a = v.constructor) === null || _a === void 0 ? void 0 : _a.name) === constructor.name;
      }
    };
    const typeOf = x => {
      const t = typeof x;
      if (x === null) {
        return 'null';
      } else if (t === 'object' && Array.isArray(x)) {
        return 'array';
      } else if (t === 'object' && hasProto(x, String, (o, proto) => proto.isPrototypeOf(o))) {
        return 'string';
      } else {
        return t;
      }
    };
    const isType$1 = type => value => typeOf(value) === type;
    const isSimpleType = type => value => typeof value === type;
    const eq$1 = t => a => t === a;
    const is$4 = (value, constructor) => isObject(value) && hasProto(value, constructor, (o, proto) => getPrototypeOf$2(o) === proto);
    const isString = isType$1('string');
    const isObject = isType$1('object');
    const isPlainObject = value => is$4(value, Object);
    const isArray$1 = isType$1('array');
    const isNull = eq$1(null);
    const isBoolean = isSimpleType('boolean');
    const isUndefined = eq$1(undefined);
    const isNullable = a => a === null || a === undefined;
    const isNonNullable = a => !isNullable(a);
    const isFunction = isSimpleType('function');
    const isNumber = isSimpleType('number');
    const isArrayOf = (value, pred) => {
      if (isArray$1(value)) {
        for (let i = 0, len = value.length; i < len; ++i) {
          if (!pred(value[i])) {
            return false;
          }
        }
        return true;
      }
      return false;
    };

    const noop = () => {
    };
    const compose = (fa, fb) => {
      return (...args) => {
        return fa(fb.apply(null, args));
      };
    };
    const compose1 = (fbc, fab) => a => fbc(fab(a));
    const constant = value => {
      return () => {
        return value;
      };
    };
    const identity = x => {
      return x;
    };
    const tripleEquals = (a, b) => {
      return a === b;
    };
    function curry(fn, ...initialArgs) {
      return (...restArgs) => {
        const all = initialArgs.concat(restArgs);
        return fn.apply(null, all);
      };
    }
    const not = f => t => !f(t);
    const die = msg => {
      return () => {
        throw new Error(msg);
      };
    };
    const apply$1 = f => {
      return f();
    };
    const call = f => {
      f();
    };
    const never = constant(false);
    const always = constant(true);

    class Optional {
      constructor(tag, value) {
        this.tag = tag;
        this.value = value;
      }
      static some(value) {
        return new Optional(true, value);
      }
      static none() {
        return Optional.singletonNone;
      }
      fold(onNone, onSome) {
        if (this.tag) {
          return onSome(this.value);
        } else {
          return onNone();
        }
      }
      isSome() {
        return this.tag;
      }
      isNone() {
        return !this.tag;
      }
      map(mapper) {
        if (this.tag) {
          return Optional.some(mapper(this.value));
        } else {
          return Optional.none();
        }
      }
      bind(binder) {
        if (this.tag) {
          return binder(this.value);
        } else {
          return Optional.none();
        }
      }
      exists(predicate) {
        return this.tag && predicate(this.value);
      }
      forall(predicate) {
        return !this.tag || predicate(this.value);
      }
      filter(predicate) {
        if (!this.tag || predicate(this.value)) {
          return this;
        } else {
          return Optional.none();
        }
      }
      getOr(replacement) {
        return this.tag ? this.value : replacement;
      }
      or(replacement) {
        return this.tag ? this : replacement;
      }
      getOrThunk(thunk) {
        return this.tag ? this.value : thunk();
      }
      orThunk(thunk) {
        return this.tag ? this : thunk();
      }
      getOrDie(message) {
        if (!this.tag) {
          throw new Error(message !== null && message !== void 0 ? message : 'Called getOrDie on None');
        } else {
          return this.value;
        }
      }
      static from(value) {
        return isNonNullable(value) ? Optional.some(value) : Optional.none();
      }
      getOrNull() {
        return this.tag ? this.value : null;
      }
      getOrUndefined() {
        return this.value;
      }
      each(worker) {
        if (this.tag) {
          worker(this.value);
        }
      }
      toArray() {
        return this.tag ? [this.value] : [];
      }
      toString() {
        return this.tag ? `some(${ this.value })` : 'none()';
      }
    }
    Optional.singletonNone = new Optional(false);

    const nativeSlice = Array.prototype.slice;
    const nativeIndexOf = Array.prototype.indexOf;
    const nativePush = Array.prototype.push;
    const rawIndexOf = (ts, t) => nativeIndexOf.call(ts, t);
    const indexOf$1 = (xs, x) => {
      const r = rawIndexOf(xs, x);
      return r === -1 ? Optional.none() : Optional.some(r);
    };
    const contains$2 = (xs, x) => rawIndexOf(xs, x) > -1;
    const exists = (xs, pred) => {
      for (let i = 0, len = xs.length; i < len; i++) {
        const x = xs[i];
        if (pred(x, i)) {
          return true;
        }
      }
      return false;
    };
    const map$3 = (xs, f) => {
      const len = xs.length;
      const r = new Array(len);
      for (let i = 0; i < len; i++) {
        const x = xs[i];
        r[i] = f(x, i);
      }
      return r;
    };
    const each$e = (xs, f) => {
      for (let i = 0, len = xs.length; i < len; i++) {
        const x = xs[i];
        f(x, i);
      }
    };
    const eachr = (xs, f) => {
      for (let i = xs.length - 1; i >= 0; i--) {
        const x = xs[i];
        f(x, i);
      }
    };
    const partition$2 = (xs, pred) => {
      const pass = [];
      const fail = [];
      for (let i = 0, len = xs.length; i < len; i++) {
        const x = xs[i];
        const arr = pred(x, i) ? pass : fail;
        arr.push(x);
      }
      return {
        pass,
        fail
      };
    };
    const filter$5 = (xs, pred) => {
      const r = [];
      for (let i = 0, len = xs.length; i < len; i++) {
        const x = xs[i];
        if (pred(x, i)) {
          r.push(x);
        }
      }
      return r;
    };
    const foldr = (xs, f, acc) => {
      eachr(xs, (x, i) => {
        acc = f(acc, x, i);
      });
      return acc;
    };
    const foldl = (xs, f, acc) => {
      each$e(xs, (x, i) => {
        acc = f(acc, x, i);
      });
      return acc;
    };
    const findUntil$1 = (xs, pred, until) => {
      for (let i = 0, len = xs.length; i < len; i++) {
        const x = xs[i];
        if (pred(x, i)) {
          return Optional.some(x);
        } else if (until(x, i)) {
          break;
        }
      }
      return Optional.none();
    };
    const find$2 = (xs, pred) => {
      return findUntil$1(xs, pred, never);
    };
    const findIndex$2 = (xs, pred) => {
      for (let i = 0, len = xs.length; i < len; i++) {
        const x = xs[i];
        if (pred(x, i)) {
          return Optional.some(i);
        }
      }
      return Optional.none();
    };
    const flatten = xs => {
      const r = [];
      for (let i = 0, len = xs.length; i < len; ++i) {
        if (!isArray$1(xs[i])) {
          throw new Error('Arr.flatten item ' + i + ' was not an array, input: ' + xs);
        }
        nativePush.apply(r, xs[i]);
      }
      return r;
    };
    const bind$3 = (xs, f) => flatten(map$3(xs, f));
    const forall = (xs, pred) => {
      for (let i = 0, len = xs.length; i < len; ++i) {
        const x = xs[i];
        if (pred(x, i) !== true) {
          return false;
        }
      }
      return true;
    };
    const reverse = xs => {
      const r = nativeSlice.call(xs, 0);
      r.reverse();
      return r;
    };
    const difference = (a1, a2) => filter$5(a1, x => !contains$2(a2, x));
    const mapToObject = (xs, f) => {
      const r = {};
      for (let i = 0, len = xs.length; i < len; i++) {
        const x = xs[i];
        r[String(x)] = f(x, i);
      }
      return r;
    };
    const sort = (xs, comparator) => {
      const copy = nativeSlice.call(xs, 0);
      copy.sort(comparator);
      return copy;
    };
    const get$b = (xs, i) => i >= 0 && i < xs.length ? Optional.some(xs[i]) : Optional.none();
    const head = xs => get$b(xs, 0);
    const last$3 = xs => get$b(xs, xs.length - 1);
    const from = isFunction(Array.from) ? Array.from : x => nativeSlice.call(x);
    const findMap = (arr, f) => {
      for (let i = 0; i < arr.length; i++) {
        const r = f(arr[i], i);
        if (r.isSome()) {
          return r;
        }
      }
      return Optional.none();
    };
    const unique$1 = (xs, comparator) => {
      const r = [];
      const isDuplicated = isFunction(comparator) ? x => exists(r, i => comparator(i, x)) : x => contains$2(r, x);
      for (let i = 0, len = xs.length; i < len; i++) {
        const x = xs[i];
        if (!isDuplicated(x)) {
          r.push(x);
        }
      }
      return r;
    };

    const keys = Object.keys;
    const hasOwnProperty$1 = Object.hasOwnProperty;
    const each$d = (obj, f) => {
      const props = keys(obj);
      for (let k = 0, len = props.length; k < len; k++) {
        const i = props[k];
        const x = obj[i];
        f(x, i);
      }
    };
    const map$2 = (obj, f) => {
      return tupleMap(obj, (x, i) => ({
        k: i,
        v: f(x, i)
      }));
    };
    const tupleMap = (obj, f) => {
      const r = {};
      each$d(obj, (x, i) => {
        const tuple = f(x, i);
        r[tuple.k] = tuple.v;
      });
      return r;
    };
    const objAcc = r => (x, i) => {
      r[i] = x;
    };
    const internalFilter = (obj, pred, onTrue, onFalse) => {
      each$d(obj, (x, i) => {
        (pred(x, i) ? onTrue : onFalse)(x, i);
      });
    };
    const bifilter = (obj, pred) => {
      const t = {};
      const f = {};
      internalFilter(obj, pred, objAcc(t), objAcc(f));
      return {
        t,
        f
      };
    };
    const filter$4 = (obj, pred) => {
      const t = {};
      internalFilter(obj, pred, objAcc(t), noop);
      return t;
    };
    const mapToArray = (obj, f) => {
      const r = [];
      each$d(obj, (value, name) => {
        r.push(f(value, name));
      });
      return r;
    };
    const values = obj => {
      return mapToArray(obj, identity);
    };
    const get$a = (obj, key) => {
      return has$2(obj, key) ? Optional.from(obj[key]) : Optional.none();
    };
    const has$2 = (obj, key) => hasOwnProperty$1.call(obj, key);
    const hasNonNullableKey = (obj, key) => has$2(obj, key) && obj[key] !== undefined && obj[key] !== null;
    const equal$1 = (a1, a2, eq = eqAny) => eqRecord(eq).eq(a1, a2);

    const stringArray = a => {
      const all = {};
      each$e(a, key => {
        all[key] = {};
      });
      return keys(all);
    };

    const isArrayLike = o => o.length !== undefined;
    const isArray = Array.isArray;
    const toArray$1 = obj => {
      if (!isArray(obj)) {
        const array = [];
        for (let i = 0, l = obj.length; i < l; i++) {
          array[i] = obj[i];
        }
        return array;
      } else {
        return obj;
      }
    };
    const each$c = (o, cb, s) => {
      if (!o) {
        return false;
      }
      s = s || o;
      if (isArrayLike(o)) {
        for (let n = 0, l = o.length; n < l; n++) {
          if (cb.call(s, o[n], n, o) === false) {
            return false;
          }
        }
      } else {
        for (const n in o) {
          if (has$2(o, n)) {
            if (cb.call(s, o[n], n, o) === false) {
              return false;
            }
          }
        }
      }
      return true;
    };
    const map$1 = (array, callback) => {
      const out = [];
      each$c(array, (item, index) => {
        out.push(callback(item, index, array));
      });
      return out;
    };
    const filter$3 = (a, f) => {
      const o = [];
      each$c(a, (v, index) => {
        if (!f || f(v, index, a)) {
          o.push(v);
        }
      });
      return o;
    };
    const indexOf = (a, v) => {
      if (a) {
        for (let i = 0, l = a.length; i < l; i++) {
          if (a[i] === v) {
            return i;
          }
        }
      }
      return -1;
    };
    const reduce = (collection, iteratee, accumulator, thisArg) => {
      let acc = isUndefined(accumulator) ? collection[0] : accumulator;
      for (let i = 0; i < collection.length; i++) {
        acc = iteratee.call(thisArg, acc, collection[i], i);
      }
      return acc;
    };
    const findIndex$1 = (array, predicate, thisArg) => {
      for (let i = 0, l = array.length; i < l; i++) {
        if (predicate.call(thisArg, array[i], i, array)) {
          return i;
        }
      }
      return -1;
    };
    const last$2 = collection => collection[collection.length - 1];

    const cached = f => {
      let called = false;
      let r;
      return (...args) => {
        if (!called) {
          called = true;
          r = f.apply(null, args);
        }
        return r;
      };
    };

    const DeviceType = (os, browser, userAgent, mediaMatch) => {
      const isiPad = os.isiOS() && /ipad/i.test(userAgent) === true;
      const isiPhone = os.isiOS() && !isiPad;
      const isMobile = os.isiOS() || os.isAndroid();
      const isTouch = isMobile || mediaMatch('(pointer:coarse)');
      const isTablet = isiPad || !isiPhone && isMobile && mediaMatch('(min-device-width:768px)');
      const isPhone = isiPhone || isMobile && !isTablet;
      const iOSwebview = browser.isSafari() && os.isiOS() && /safari/i.test(userAgent) === false;
      const isDesktop = !isPhone && !isTablet && !iOSwebview;
      return {
        isiPad: constant(isiPad),
        isiPhone: constant(isiPhone),
        isTablet: constant(isTablet),
        isPhone: constant(isPhone),
        isTouch: constant(isTouch),
        isAndroid: os.isAndroid,
        isiOS: os.isiOS,
        isWebView: constant(iOSwebview),
        isDesktop: constant(isDesktop)
      };
    };

    const firstMatch = (regexes, s) => {
      for (let i = 0; i < regexes.length; i++) {
        const x = regexes[i];
        if (x.test(s)) {
          return x;
        }
      }
      return undefined;
    };
    const find$1 = (regexes, agent) => {
      const r = firstMatch(regexes, agent);
      if (!r) {
        return {
          major: 0,
          minor: 0
        };
      }
      const group = i => {
        return Number(agent.replace(r, '$' + i));
      };
      return nu$3(group(1), group(2));
    };
    const detect$5 = (versionRegexes, agent) => {
      const cleanedAgent = String(agent).toLowerCase();
      if (versionRegexes.length === 0) {
        return unknown$2();
      }
      return find$1(versionRegexes, cleanedAgent);
    };
    const unknown$2 = () => {
      return nu$3(0, 0);
    };
    const nu$3 = (major, minor) => {
      return {
        major,
        minor
      };
    };
    const Version = {
      nu: nu$3,
      detect: detect$5,
      unknown: unknown$2
    };

    const detectBrowser$1 = (browsers, userAgentData) => {
      return findMap(userAgentData.brands, uaBrand => {
        const lcBrand = uaBrand.brand.toLowerCase();
        return find$2(browsers, browser => {
          var _a;
          return lcBrand === ((_a = browser.brand) === null || _a === void 0 ? void 0 : _a.toLowerCase());
        }).map(info => ({
          current: info.name,
          version: Version.nu(parseInt(uaBrand.version, 10), 0)
        }));
      });
    };

    const detect$4 = (candidates, userAgent) => {
      const agent = String(userAgent).toLowerCase();
      return find$2(candidates, candidate => {
        return candidate.search(agent);
      });
    };
    const detectBrowser = (browsers, userAgent) => {
      return detect$4(browsers, userAgent).map(browser => {
        const version = Version.detect(browser.versionRegexes, userAgent);
        return {
          current: browser.name,
          version
        };
      });
    };
    const detectOs = (oses, userAgent) => {
      return detect$4(oses, userAgent).map(os => {
        const version = Version.detect(os.versionRegexes, userAgent);
        return {
          current: os.name,
          version
        };
      });
    };

    const removeFromStart = (str, numChars) => {
      return str.substring(numChars);
    };

    const checkRange = (str, substr, start) => substr === '' || str.length >= substr.length && str.substr(start, start + substr.length) === substr;
    const removeLeading = (str, prefix) => {
      return startsWith(str, prefix) ? removeFromStart(str, prefix.length) : str;
    };
    const contains$1 = (str, substr, start = 0, end) => {
      const idx = str.indexOf(substr, start);
      if (idx !== -1) {
        return isUndefined(end) ? true : idx + substr.length <= end;
      } else {
        return false;
      }
    };
    const startsWith = (str, prefix) => {
      return checkRange(str, prefix, 0);
    };
    const endsWith = (str, suffix) => {
      return checkRange(str, suffix, str.length - suffix.length);
    };
    const blank = r => s => s.replace(r, '');
    const trim$4 = blank(/^\s+|\s+$/g);
    const lTrim = blank(/^\s+/g);
    const rTrim = blank(/\s+$/g);
    const isNotEmpty = s => s.length > 0;
    const isEmpty$3 = s => !isNotEmpty(s);
    const repeat = (s, count) => count <= 0 ? '' : new Array(count + 1).join(s);
    const toInt = (value, radix = 10) => {
      const num = parseInt(value, radix);
      return isNaN(num) ? Optional.none() : Optional.some(num);
    };

    const normalVersionRegex = /.*?version\/\ ?([0-9]+)\.([0-9]+).*/;
    const checkContains = target => {
      return uastring => {
        return contains$1(uastring, target);
      };
    };
    const browsers = [
      {
        name: 'Edge',
        versionRegexes: [/.*?edge\/ ?([0-9]+)\.([0-9]+)$/],
        search: uastring => {
          return contains$1(uastring, 'edge/') && contains$1(uastring, 'chrome') && contains$1(uastring, 'safari') && contains$1(uastring, 'applewebkit');
        }
      },
      {
        name: 'Chromium',
        brand: 'Chromium',
        versionRegexes: [
          /.*?chrome\/([0-9]+)\.([0-9]+).*/,
          normalVersionRegex
        ],
        search: uastring => {
          return contains$1(uastring, 'chrome') && !contains$1(uastring, 'chromeframe');
        }
      },
      {
        name: 'IE',
        versionRegexes: [
          /.*?msie\ ?([0-9]+)\.([0-9]+).*/,
          /.*?rv:([0-9]+)\.([0-9]+).*/
        ],
        search: uastring => {
          return contains$1(uastring, 'msie') || contains$1(uastring, 'trident');
        }
      },
      {
        name: 'Opera',
        versionRegexes: [
          normalVersionRegex,
          /.*?opera\/([0-9]+)\.([0-9]+).*/
        ],
        search: checkContains('opera')
      },
      {
        name: 'Firefox',
        versionRegexes: [/.*?firefox\/\ ?([0-9]+)\.([0-9]+).*/],
        search: checkContains('firefox')
      },
      {
        name: 'Safari',
        versionRegexes: [
          normalVersionRegex,
          /.*?cpu os ([0-9]+)_([0-9]+).*/
        ],
        search: uastring => {
          return (contains$1(uastring, 'safari') || contains$1(uastring, 'mobile/')) && contains$1(uastring, 'applewebkit');
        }
      }
    ];
    const oses = [
      {
        name: 'Windows',
        search: checkContains('win'),
        versionRegexes: [/.*?windows\ nt\ ?([0-9]+)\.([0-9]+).*/]
      },
      {
        name: 'iOS',
        search: uastring => {
          return contains$1(uastring, 'iphone') || contains$1(uastring, 'ipad');
        },
        versionRegexes: [
          /.*?version\/\ ?([0-9]+)\.([0-9]+).*/,
          /.*cpu os ([0-9]+)_([0-9]+).*/,
          /.*cpu iphone os ([0-9]+)_([0-9]+).*/
        ]
      },
      {
        name: 'Android',
        search: checkContains('android'),
        versionRegexes: [/.*?android\ ?([0-9]+)\.([0-9]+).*/]
      },
      {
        name: 'macOS',
        search: checkContains('mac os x'),
        versionRegexes: [/.*?mac\ os\ x\ ?([0-9]+)_([0-9]+).*/]
      },
      {
        name: 'Linux',
        search: checkContains('linux'),
        versionRegexes: []
      },
      {
        name: 'Solaris',
        search: checkContains('sunos'),
        versionRegexes: []
      },
      {
        name: 'FreeBSD',
        search: checkContains('freebsd'),
        versionRegexes: []
      },
      {
        name: 'ChromeOS',
        search: checkContains('cros'),
        versionRegexes: [/.*?chrome\/([0-9]+)\.([0-9]+).*/]
      }
    ];
    const PlatformInfo = {
      browsers: constant(browsers),
      oses: constant(oses)
    };

    const edge = 'Edge';
    const chromium = 'Chromium';
    const ie = 'IE';
    const opera = 'Opera';
    const firefox = 'Firefox';
    const safari = 'Safari';
    const unknown$1 = () => {
      return nu$2({
        current: undefined,
        version: Version.unknown()
      });
    };
    const nu$2 = info => {
      const current = info.current;
      const version = info.version;
      const isBrowser = name => () => current === name;
      return {
        current,
        version,
        isEdge: isBrowser(edge),
        isChromium: isBrowser(chromium),
        isIE: isBrowser(ie),
        isOpera: isBrowser(opera),
        isFirefox: isBrowser(firefox),
        isSafari: isBrowser(safari)
      };
    };
    const Browser = {
      unknown: unknown$1,
      nu: nu$2,
      edge: constant(edge),
      chromium: constant(chromium),
      ie: constant(ie),
      opera: constant(opera),
      firefox: constant(firefox),
      safari: constant(safari)
    };

    const windows = 'Windows';
    const ios = 'iOS';
    const android = 'Android';
    const linux = 'Linux';
    const macos = 'macOS';
    const solaris = 'Solaris';
    const freebsd = 'FreeBSD';
    const chromeos = 'ChromeOS';
    const unknown = () => {
      return nu$1({
        current: undefined,
        version: Version.unknown()
      });
    };
    const nu$1 = info => {
      const current = info.current;
      const version = info.version;
      const isOS = name => () => current === name;
      return {
        current,
        version,
        isWindows: isOS(windows),
        isiOS: isOS(ios),
        isAndroid: isOS(android),
        isMacOS: isOS(macos),
        isLinux: isOS(linux),
        isSolaris: isOS(solaris),
        isFreeBSD: isOS(freebsd),
        isChromeOS: isOS(chromeos)
      };
    };
    const OperatingSystem = {
      unknown,
      nu: nu$1,
      windows: constant(windows),
      ios: constant(ios),
      android: constant(android),
      linux: constant(linux),
      macos: constant(macos),
      solaris: constant(solaris),
      freebsd: constant(freebsd),
      chromeos: constant(chromeos)
    };

    const detect$3 = (userAgent, userAgentDataOpt, mediaMatch) => {
      const browsers = PlatformInfo.browsers();
      const oses = PlatformInfo.oses();
      const browser = userAgentDataOpt.bind(userAgentData => detectBrowser$1(browsers, userAgentData)).orThunk(() => detectBrowser(browsers, userAgent)).fold(Browser.unknown, Browser.nu);
      const os = detectOs(oses, userAgent).fold(OperatingSystem.unknown, OperatingSystem.nu);
      const deviceType = DeviceType(os, browser, userAgent, mediaMatch);
      return {
        browser,
        os,
        deviceType
      };
    };
    const PlatformDetection = { detect: detect$3 };

    const mediaMatch = query => window.matchMedia(query).matches;
    let platform$4 = cached(() => PlatformDetection.detect(navigator.userAgent, Optional.from(navigator.userAgentData), mediaMatch));
    const detect$2 = () => platform$4();

    const userAgent = navigator.userAgent;
    const platform$3 = detect$2();
    const browser$3 = platform$3.browser;
    const os$1 = platform$3.os;
    const deviceType = platform$3.deviceType;
    const windowsPhone = userAgent.indexOf('Windows Phone') !== -1;
    const Env = {
      transparentSrc: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
      documentMode: browser$3.isIE() ? document.documentMode || 7 : 10,
      cacheSuffix: null,
      container: null,
      canHaveCSP: !browser$3.isIE(),
      windowsPhone,
      browser: {
        current: browser$3.current,
        version: browser$3.version,
        isChromium: browser$3.isChromium,
        isEdge: browser$3.isEdge,
        isFirefox: browser$3.isFirefox,
        isIE: browser$3.isIE,
        isOpera: browser$3.isOpera,
        isSafari: browser$3.isSafari
      },
      os: {
        current: os$1.current,
        version: os$1.version,
        isAndroid: os$1.isAndroid,
        isChromeOS: os$1.isChromeOS,
        isFreeBSD: os$1.isFreeBSD,
        isiOS: os$1.isiOS,
        isLinux: os$1.isLinux,
        isMacOS: os$1.isMacOS,
        isSolaris: os$1.isSolaris,
        isWindows: os$1.isWindows
      },
      deviceType: {
        isDesktop: deviceType.isDesktop,
        isiPad: deviceType.isiPad,
        isiPhone: deviceType.isiPhone,
        isPhone: deviceType.isPhone,
        isTablet: deviceType.isTablet,
        isTouch: deviceType.isTouch,
        isWebView: deviceType.isWebView
      }
    };

    const whiteSpaceRegExp$1 = /^\s*|\s*$/g;
    const trim$3 = str => {
      return isNullable(str) ? '' : ('' + str).replace(whiteSpaceRegExp$1, '');
    };
    const is$3 = (obj, type) => {
      if (!type) {
        return obj !== undefined;
      }
      if (type === 'array' && isArray(obj)) {
        return true;
      }
      return typeof obj === type;
    };
    const makeMap$4 = (items, delim, map = {}) => {
      const resolvedItems = isString(items) ? items.split(delim || ',') : items || [];
      let i = resolvedItems.length;
      while (i--) {
        map[resolvedItems[i]] = {};
      }
      return map;
    };
    const hasOwnProperty = has$2;
    const extend$3 = (obj, ...exts) => {
      for (let i = 0; i < exts.length; i++) {
        const ext = exts[i];
        for (const name in ext) {
          if (has$2(ext, name)) {
            const value = ext[name];
            if (value !== undefined) {
              obj[name] = value;
            }
          }
        }
      }
      return obj;
    };
    const walk$4 = function (o, f, n, s) {
      s = s || this;
      if (o) {
        if (n) {
          o = o[n];
        }
        each$c(o, (o, i) => {
          if (f.call(s, o, i, n) === false) {
            return false;
          } else {
            walk$4(o, f, n, s);
            return true;
          }
        });
      }
    };
    const resolve$3 = (n, o = window) => {
      const path = n.split('.');
      for (let i = 0, l = path.length; i < l; i++) {
        o = o[path[i]];
        if (!o) {
          break;
        }
      }
      return o;
    };
    const explode$3 = (s, d) => {
      if (isArray$1(s)) {
        return s;
      } else if (s === '') {
        return [];
      } else {
        return map$1(s.split(d || ','), trim$3);
      }
    };
    const _addCacheSuffix = url => {
      const cacheSuffix = Env.cacheSuffix;
      if (cacheSuffix) {
        url += (url.indexOf('?') === -1 ? '?' : '&') + cacheSuffix;
      }
      return url;
    };
    const Tools = {
      trim: trim$3,
      isArray: isArray,
      is: is$3,
      toArray: toArray$1,
      makeMap: makeMap$4,
      each: each$c,
      map: map$1,
      grep: filter$3,
      inArray: indexOf,
      hasOwn: hasOwnProperty,
      extend: extend$3,
      walk: walk$4,
      resolve: resolve$3,
      explode: explode$3,
      _addCacheSuffix
    };

    const is$2 = (lhs, rhs, comparator = tripleEquals) => lhs.exists(left => comparator(left, rhs));
    const equals = (lhs, rhs, comparator = tripleEquals) => lift2(lhs, rhs, comparator).getOr(lhs.isNone() && rhs.isNone());
    const cat = arr => {
      const r = [];
      const push = x => {
        r.push(x);
      };
      for (let i = 0; i < arr.length; i++) {
        arr[i].each(push);
      }
      return r;
    };
    const lift2 = (oa, ob, f) => oa.isSome() && ob.isSome() ? Optional.some(f(oa.getOrDie(), ob.getOrDie())) : Optional.none();
    const lift3 = (oa, ob, oc, f) => oa.isSome() && ob.isSome() && oc.isSome() ? Optional.some(f(oa.getOrDie(), ob.getOrDie(), oc.getOrDie())) : Optional.none();
    const someIf = (b, a) => b ? Optional.some(a) : Optional.none();

    const Global = typeof window !== 'undefined' ? window : Function('return this;')();

    const path = (parts, scope) => {
      let o = scope !== undefined && scope !== null ? scope : Global;
      for (let i = 0; i < parts.length && o !== undefined && o !== null; ++i) {
        o = o[parts[i]];
      }
      return o;
    };
    const resolve$2 = (p, scope) => {
      const parts = p.split('.');
      return path(parts, scope);
    };

    const unsafe = (name, scope) => {
      return resolve$2(name, scope);
    };
    const getOrDie = (name, scope) => {
      const actual = unsafe(name, scope);
      if (actual === undefined || actual === null) {
        throw new Error(name + ' not available on this browser');
      }
      return actual;
    };

    const getPrototypeOf$1 = Object.getPrototypeOf;
    const sandHTMLElement = scope => {
      return getOrDie('HTMLElement', scope);
    };
    const isPrototypeOf = x => {
      const scope = resolve$2('ownerDocument.defaultView', x);
      return isObject(x) && (sandHTMLElement(scope).prototype.isPrototypeOf(x) || /^HTML\w*Element$/.test(getPrototypeOf$1(x).constructor.name));
    };

    const COMMENT = 8;
    const DOCUMENT = 9;
    const DOCUMENT_FRAGMENT = 11;
    const ELEMENT = 1;
    const TEXT = 3;

    const name = element => {
      const r = element.dom.nodeName;
      return r.toLowerCase();
    };
    const type$1 = element => element.dom.nodeType;
    const isType = t => element => type$1(element) === t;
    const isComment$1 = element => type$1(element) === COMMENT || name(element) === '#comment';
    const isHTMLElement$1 = element => isElement$7(element) && isPrototypeOf(element.dom);
    const isElement$7 = isType(ELEMENT);
    const isText$b = isType(TEXT);
    const isDocument$2 = isType(DOCUMENT);
    const isDocumentFragment$1 = isType(DOCUMENT_FRAGMENT);
    const isTag = tag => e => isElement$7(e) && name(e) === tag;

    const rawSet = (dom, key, value) => {
      if (isString(value) || isBoolean(value) || isNumber(value)) {
        dom.setAttribute(key, value + '');
      } else {
        console.error('Invalid call to Attribute.set. Key ', key, ':: Value ', value, ':: Element ', dom);
        throw new Error('Attribute value was not simple');
      }
    };
    const set$3 = (element, key, value) => {
      rawSet(element.dom, key, value);
    };
    const setAll$1 = (element, attrs) => {
      const dom = element.dom;
      each$d(attrs, (v, k) => {
        rawSet(dom, k, v);
      });
    };
    const get$9 = (element, key) => {
      const v = element.dom.getAttribute(key);
      return v === null ? undefined : v;
    };
    const getOpt = (element, key) => Optional.from(get$9(element, key));
    const has$1 = (element, key) => {
      const dom = element.dom;
      return dom && dom.hasAttribute ? dom.hasAttribute(key) : false;
    };
    const remove$a = (element, key) => {
      element.dom.removeAttribute(key);
    };
    const hasNone = element => {
      const attrs = element.dom.attributes;
      return attrs === undefined || attrs === null || attrs.length === 0;
    };
    const clone$4 = element => foldl(element.dom.attributes, (acc, attr) => {
      acc[attr.name] = attr.value;
      return acc;
    }, {});

    const read$4 = (element, attr) => {
      const value = get$9(element, attr);
      return value === undefined || value === '' ? [] : value.split(' ');
    };
    const add$4 = (element, attr, id) => {
      const old = read$4(element, attr);
      const nu = old.concat([id]);
      set$3(element, attr, nu.join(' '));
      return true;
    };
    const remove$9 = (element, attr, id) => {
      const nu = filter$5(read$4(element, attr), v => v !== id);
      if (nu.length > 0) {
        set$3(element, attr, nu.join(' '));
      } else {
        remove$a(element, attr);
      }
      return false;
    };

    const supports = element => element.dom.classList !== undefined;
    const get$8 = element => read$4(element, 'class');
    const add$3 = (element, clazz) => add$4(element, 'class', clazz);
    const remove$8 = (element, clazz) => remove$9(element, 'class', clazz);
    const toggle$2 = (element, clazz) => {
      if (contains$2(get$8(element), clazz)) {
        return remove$8(element, clazz);
      } else {
        return add$3(element, clazz);
      }
    };

    const add$2 = (element, clazz) => {
      if (supports(element)) {
        element.dom.classList.add(clazz);
      } else {
        add$3(element, clazz);
      }
    };
    const cleanClass = element => {
      const classList = supports(element) ? element.dom.classList : get$8(element);
      if (classList.length === 0) {
        remove$a(element, 'class');
      }
    };
    const remove$7 = (element, clazz) => {
      if (supports(element)) {
        const classList = element.dom.classList;
        classList.remove(clazz);
      } else {
        remove$8(element, clazz);
      }
      cleanClass(element);
    };
    const toggle$1 = (element, clazz) => {
      const result = supports(element) ? element.dom.classList.toggle(clazz) : toggle$2(element, clazz);
      cleanClass(element);
      return result;
    };
    const has = (element, clazz) => supports(element) && element.dom.classList.contains(clazz);

    const fromHtml$1 = (html, scope) => {
      const doc = scope || document;
      const div = doc.createElement('div');
      div.innerHTML = html;
      if (!div.hasChildNodes() || div.childNodes.length > 1) {
        const message = 'HTML does not have a single root node';
        console.error(message, html);
        throw new Error(message);
      }
      return fromDom$2(div.childNodes[0]);
    };
    const fromTag = (tag, scope) => {
      const doc = scope || document;
      const node = doc.createElement(tag);
      return fromDom$2(node);
    };
    const fromText = (text, scope) => {
      const doc = scope || document;
      const node = doc.createTextNode(text);
      return fromDom$2(node);
    };
    const fromDom$2 = node => {
      if (node === null || node === undefined) {
        throw new Error('Node cannot be null or undefined');
      }
      return { dom: node };
    };
    const fromPoint$2 = (docElm, x, y) => Optional.from(docElm.dom.elementFromPoint(x, y)).map(fromDom$2);
    const SugarElement = {
      fromHtml: fromHtml$1,
      fromTag,
      fromText,
      fromDom: fromDom$2,
      fromPoint: fromPoint$2
    };

    const toArray = (target, f) => {
      const r = [];
      const recurse = e => {
        r.push(e);
        return f(e);
      };
      let cur = f(target);
      do {
        cur = cur.bind(recurse);
      } while (cur.isSome());
      return r;
    };

    const is$1 = (element, selector) => {
      const dom = element.dom;
      if (dom.nodeType !== ELEMENT) {
        return false;
      } else {
        const elem = dom;
        if (elem.matches !== undefined) {
          return elem.matches(selector);
        } else if (elem.msMatchesSelector !== undefined) {
          return elem.msMatchesSelector(selector);
        } else if (elem.webkitMatchesSelector !== undefined) {
          return elem.webkitMatchesSelector(selector);
        } else if (elem.mozMatchesSelector !== undefined) {
          return elem.mozMatchesSelector(selector);
        } else {
          throw new Error('Browser lacks native selectors');
        }
      }
    };
    const bypassSelector = dom => dom.nodeType !== ELEMENT && dom.nodeType !== DOCUMENT && dom.nodeType !== DOCUMENT_FRAGMENT || dom.childElementCount === 0;
    const all = (selector, scope) => {
      const base = scope === undefined ? document : scope.dom;
      return bypassSelector(base) ? [] : map$3(base.querySelectorAll(selector), SugarElement.fromDom);
    };
    const one = (selector, scope) => {
      const base = scope === undefined ? document : scope.dom;
      return bypassSelector(base) ? Optional.none() : Optional.from(base.querySelector(selector)).map(SugarElement.fromDom);
    };

    const eq = (e1, e2) => e1.dom === e2.dom;
    const contains = (e1, e2) => {
      const d1 = e1.dom;
      const d2 = e2.dom;
      return d1 === d2 ? false : d1.contains(d2);
    };

    const owner$1 = element => SugarElement.fromDom(element.dom.ownerDocument);
    const documentOrOwner = dos => isDocument$2(dos) ? dos : owner$1(dos);
    const documentElement = element => SugarElement.fromDom(documentOrOwner(element).dom.documentElement);
    const defaultView = element => SugarElement.fromDom(documentOrOwner(element).dom.defaultView);
    const parent = element => Optional.from(element.dom.parentNode).map(SugarElement.fromDom);
    const parentElement = element => Optional.from(element.dom.parentElement).map(SugarElement.fromDom);
    const parents$1 = (element, isRoot) => {
      const stop = isFunction(isRoot) ? isRoot : never;
      let dom = element.dom;
      const ret = [];
      while (dom.parentNode !== null && dom.parentNode !== undefined) {
        const rawParent = dom.parentNode;
        const p = SugarElement.fromDom(rawParent);
        ret.push(p);
        if (stop(p) === true) {
          break;
        } else {
          dom = rawParent;
        }
      }
      return ret;
    };
    const siblings = element => {
      const filterSelf = elements => filter$5(elements, x => !eq(element, x));
      return parent(element).map(children$1).map(filterSelf).getOr([]);
    };
    const prevSibling = element => Optional.from(element.dom.previousSibling).map(SugarElement.fromDom);
    const nextSibling = element => Optional.from(element.dom.nextSibling).map(SugarElement.fromDom);
    const prevSiblings = element => reverse(toArray(element, prevSibling));
    const nextSiblings = element => toArray(element, nextSibling);
    const children$1 = element => map$3(element.dom.childNodes, SugarElement.fromDom);
    const child$1 = (element, index) => {
      const cs = element.dom.childNodes;
      return Optional.from(cs[index]).map(SugarElement.fromDom);
    };
    const firstChild = element => child$1(element, 0);
    const lastChild = element => child$1(element, element.dom.childNodes.length - 1);
    const childNodesCount = element => element.dom.childNodes.length;
    const hasChildNodes = element => element.dom.hasChildNodes();

    const getHead = doc => {
      const b = doc.dom.head;
      if (b === null || b === undefined) {
        throw new Error('Head is not available yet');
      }
      return SugarElement.fromDom(b);
    };

    const isShadowRoot = dos => isDocumentFragment$1(dos) && isNonNullable(dos.dom.host);
    const supported = isFunction(Element.prototype.attachShadow) && isFunction(Node.prototype.getRootNode);
    const isSupported$1 = constant(supported);
    const getRootNode = supported ? e => SugarElement.fromDom(e.dom.getRootNode()) : documentOrOwner;
    const getStyleContainer = dos => isShadowRoot(dos) ? dos : getHead(documentOrOwner(dos));
    const getContentContainer = dos => isShadowRoot(dos) ? dos : SugarElement.fromDom(documentOrOwner(dos).dom.body);
    const getShadowRoot = e => {
      const r = getRootNode(e);
      return isShadowRoot(r) ? Optional.some(r) : Optional.none();
    };
    const getShadowHost = e => SugarElement.fromDom(e.dom.host);
    const getOriginalEventTarget = event => {
      if (isSupported$1() && isNonNullable(event.target)) {
        const el = SugarElement.fromDom(event.target);
        if (isElement$7(el) && isOpenShadowHost(el)) {
          if (event.composed && event.composedPath) {
            const composedPath = event.composedPath();
            if (composedPath) {
              return head(composedPath);
            }
          }
        }
      }
      return Optional.from(event.target);
    };
    const isOpenShadowHost = element => isNonNullable(element.dom.shadowRoot);

    const inBody = element => {
      const dom = isText$b(element) ? element.dom.parentNode : element.dom;
      if (dom === undefined || dom === null || dom.ownerDocument === null) {
        return false;
      }
      const doc = dom.ownerDocument;
      return getShadowRoot(SugarElement.fromDom(dom)).fold(() => doc.body.contains(dom), compose1(inBody, getShadowHost));
    };

    var ClosestOrAncestor = (is, ancestor, scope, a, isRoot) => {
      if (is(scope, a)) {
        return Optional.some(scope);
      } else if (isFunction(isRoot) && isRoot(scope)) {
        return Optional.none();
      } else {
        return ancestor(scope, a, isRoot);
      }
    };

    const ancestor$4 = (scope, predicate, isRoot) => {
      let element = scope.dom;
      const stop = isFunction(isRoot) ? isRoot : never;
      while (element.parentNode) {
        element = element.parentNode;
        const el = SugarElement.fromDom(element);
        if (predicate(el)) {
          return Optional.some(el);
        } else if (stop(el)) {
          break;
        }
      }
      return Optional.none();
    };
    const closest$4 = (scope, predicate, isRoot) => {
      const is = (s, test) => test(s);
      return ClosestOrAncestor(is, ancestor$4, scope, predicate, isRoot);
    };
    const sibling$1 = (scope, predicate) => {
      const element = scope.dom;
      if (!element.parentNode) {
        return Optional.none();
      }
      return child(SugarElement.fromDom(element.parentNode), x => !eq(scope, x) && predicate(x));
    };
    const child = (scope, predicate) => {
      const pred = node => predicate(SugarElement.fromDom(node));
      const result = find$2(scope.dom.childNodes, pred);
      return result.map(SugarElement.fromDom);
    };
    const descendant$2 = (scope, predicate) => {
      const descend = node => {
        for (let i = 0; i < node.childNodes.length; i++) {
          const child = SugarElement.fromDom(node.childNodes[i]);
          if (predicate(child)) {
            return Optional.some(child);
          }
          const res = descend(node.childNodes[i]);
          if (res.isSome()) {
            return res;
          }
        }
        return Optional.none();
      };
      return descend(scope.dom);
    };

    const ancestor$3 = (scope, selector, isRoot) => ancestor$4(scope, e => is$1(e, selector), isRoot);
    const descendant$1 = (scope, selector) => one(selector, scope);
    const closest$3 = (scope, selector, isRoot) => {
      const is = (element, selector) => is$1(element, selector);
      return ClosestOrAncestor(is, ancestor$3, scope, selector, isRoot);
    };

    const closest$2 = target => closest$3(target, '[contenteditable]');
    const isEditable$2 = (element, assumeEditable = false) => {
      if (inBody(element)) {
        return element.dom.isContentEditable;
      } else {
        return closest$2(element).fold(constant(assumeEditable), editable => getRaw$1(editable) === 'true');
      }
    };
    const getRaw$1 = element => element.dom.contentEditable;

    const isSupported = dom => dom.style !== undefined && isFunction(dom.style.getPropertyValue);

    const internalSet = (dom, property, value) => {
      if (!isString(value)) {
        console.error('Invalid call to CSS.set. Property ', property, ':: Value ', value, ':: Element ', dom);
        throw new Error('CSS value must be a string: ' + value);
      }
      if (isSupported(dom)) {
        dom.style.setProperty(property, value);
      }
    };
    const internalRemove = (dom, property) => {
      if (isSupported(dom)) {
        dom.style.removeProperty(property);
      }
    };
    const set$2 = (element, property, value) => {
      const dom = element.dom;
      internalSet(dom, property, value);
    };
    const setAll = (element, css) => {
      const dom = element.dom;
      each$d(css, (v, k) => {
        internalSet(dom, k, v);
      });
    };
    const get$7 = (element, property) => {
      const dom = element.dom;
      const styles = window.getComputedStyle(dom);
      const r = styles.getPropertyValue(property);
      return r === '' && !inBody(element) ? getUnsafeProperty(dom, property) : r;
    };
    const getUnsafeProperty = (dom, property) => isSupported(dom) ? dom.style.getPropertyValue(property) : '';
    const getRaw = (element, property) => {
      const dom = element.dom;
      const raw = getUnsafeProperty(dom, property);
      return Optional.from(raw).filter(r => r.length > 0);
    };
    const getAllRaw = element => {
      const css = {};
      const dom = element.dom;
      if (isSupported(dom)) {
        for (let i = 0; i < dom.style.length; i++) {
          const ruleName = dom.style.item(i);
          css[ruleName] = dom.style[ruleName];
        }
      }
      return css;
    };
    const remove$6 = (element, property) => {
      const dom = element.dom;
      internalRemove(dom, property);
      if (is$2(getOpt(element, 'style').map(trim$4), '')) {
        remove$a(element, 'style');
      }
    };
    const reflow = e => e.dom.offsetWidth;

    const before$3 = (marker, element) => {
      const parent$1 = parent(marker);
      parent$1.each(v => {
        v.dom.insertBefore(element.dom, marker.dom);
      });
    };
    const after$4 = (marker, element) => {
      const sibling = nextSibling(marker);
      sibling.fold(() => {
        const parent$1 = parent(marker);
        parent$1.each(v => {
          append$1(v, element);
        });
      }, v => {
        before$3(v, element);
      });
    };
    const prepend = (parent, element) => {
      const firstChild$1 = firstChild(parent);
      firstChild$1.fold(() => {
        append$1(parent, element);
      }, v => {
        parent.dom.insertBefore(element.dom, v.dom);
      });
    };
    const append$1 = (parent, element) => {
      parent.dom.appendChild(element.dom);
    };
    const wrap$2 = (element, wrapper) => {
      before$3(element, wrapper);
      append$1(wrapper, element);
    };

    const after$3 = (marker, elements) => {
      each$e(elements, (x, i) => {
        const e = i === 0 ? marker : elements[i - 1];
        after$4(e, x);
      });
    };
    const append = (parent, elements) => {
      each$e(elements, x => {
        append$1(parent, x);
      });
    };

    const empty = element => {
      element.dom.textContent = '';
      each$e(children$1(element), rogue => {
        remove$5(rogue);
      });
    };
    const remove$5 = element => {
      const dom = element.dom;
      if (dom.parentNode !== null) {
        dom.parentNode.removeChild(dom);
      }
    };
    const unwrap = wrapper => {
      const children = children$1(wrapper);
      if (children.length > 0) {
        after$3(wrapper, children);
      }
      remove$5(wrapper);
    };

    const fromHtml = (html, scope) => {
      const doc = scope || document;
      const div = doc.createElement('div');
      div.innerHTML = html;
      return children$1(SugarElement.fromDom(div));
    };
    const fromDom$1 = nodes => map$3(nodes, SugarElement.fromDom);

    const get$6 = element => element.dom.innerHTML;
    const set$1 = (element, content) => {
      const owner = owner$1(element);
      const docDom = owner.dom;
      const fragment = SugarElement.fromDom(docDom.createDocumentFragment());
      const contentElements = fromHtml(content, docDom);
      append(fragment, contentElements);
      empty(element);
      append$1(element, fragment);
    };
    const getOuter = element => {
      const container = SugarElement.fromTag('div');
      const clone = SugarElement.fromDom(element.dom.cloneNode(true));
      append$1(container, clone);
      return get$6(container);
    };

    const mkEvent = (target, x, y, stop, prevent, kill, raw) => ({
      target,
      x,
      y,
      stop,
      prevent,
      kill,
      raw
    });
    const fromRawEvent = rawEvent => {
      const target = SugarElement.fromDom(getOriginalEventTarget(rawEvent).getOr(rawEvent.target));
      const stop = () => rawEvent.stopPropagation();
      const prevent = () => rawEvent.preventDefault();
      const kill = compose(prevent, stop);
      return mkEvent(target, rawEvent.clientX, rawEvent.clientY, stop, prevent, kill, rawEvent);
    };
    const handle$1 = (filter, handler) => rawEvent => {
      if (filter(rawEvent)) {
        handler(fromRawEvent(rawEvent));
      }
    };
    const binder = (element, event, filter, handler, useCapture) => {
      const wrapped = handle$1(filter, handler);
      element.dom.addEventListener(event, wrapped, useCapture);
      return { unbind: curry(unbind, element, event, wrapped, useCapture) };
    };
    const bind$2 = (element, event, filter, handler) => binder(element, event, filter, handler, false);
    const unbind = (element, event, handler, useCapture) => {
      element.dom.removeEventListener(event, handler, useCapture);
    };

    const r = (left, top) => {
      const translate = (x, y) => r(left + x, top + y);
      return {
        left,
        top,
        translate
      };
    };
    const SugarPosition = r;

    const boxPosition = dom => {
      const box = dom.getBoundingClientRect();
      return SugarPosition(box.left, box.top);
    };
    const firstDefinedOrZero = (a, b) => {
      if (a !== undefined) {
        return a;
      } else {
        return b !== undefined ? b : 0;
      }
    };
    const absolute = element => {
      const doc = element.dom.ownerDocument;
      const body = doc.body;
      const win = doc.defaultView;
      const html = doc.documentElement;
      if (body === element.dom) {
        return SugarPosition(body.offsetLeft, body.offsetTop);
      }
      const scrollTop = firstDefinedOrZero(win === null || win === void 0 ? void 0 : win.pageYOffset, html.scrollTop);
      const scrollLeft = firstDefinedOrZero(win === null || win === void 0 ? void 0 : win.pageXOffset, html.scrollLeft);
      const clientTop = firstDefinedOrZero(html.clientTop, body.clientTop);
      const clientLeft = firstDefinedOrZero(html.clientLeft, body.clientLeft);
      return viewport(element).translate(scrollLeft - clientLeft, scrollTop - clientTop);
    };
    const viewport = element => {
      const dom = element.dom;
      const doc = dom.ownerDocument;
      const body = doc.body;
      if (body === dom) {
        return SugarPosition(body.offsetLeft, body.offsetTop);
      }
      if (!inBody(element)) {
        return SugarPosition(0, 0);
      }
      return boxPosition(dom);
    };

    const get$5 = _DOC => {
      const doc = _DOC !== undefined ? _DOC.dom : document;
      const x = doc.body.scrollLeft || doc.documentElement.scrollLeft;
      const y = doc.body.scrollTop || doc.documentElement.scrollTop;
      return SugarPosition(x, y);
    };
    const to = (x, y, _DOC) => {
      const doc = _DOC !== undefined ? _DOC.dom : document;
      const win = doc.defaultView;
      if (win) {
        win.scrollTo(x, y);
      }
    };
    const intoView = (element, alignToTop) => {
      const isSafari = detect$2().browser.isSafari();
      if (isSafari && isFunction(element.dom.scrollIntoViewIfNeeded)) {
        element.dom.scrollIntoViewIfNeeded(false);
      } else {
        element.dom.scrollIntoView(alignToTop);
      }
    };

    const get$4 = _win => {
      const win = _win === undefined ? window : _win;
      if (detect$2().browser.isFirefox()) {
        return Optional.none();
      } else {
        return Optional.from(win.visualViewport);
      }
    };
    const bounds = (x, y, width, height) => ({
      x,
      y,
      width,
      height,
      right: x + width,
      bottom: y + height
    });
    const getBounds = _win => {
      const win = _win === undefined ? window : _win;
      const doc = win.document;
      const scroll = get$5(SugarElement.fromDom(doc));
      return get$4(win).fold(() => {
        const html = win.document.documentElement;
        const width = html.clientWidth;
        const height = html.clientHeight;
        return bounds(scroll.left, scroll.top, width, height);
      }, visualViewport => bounds(Math.max(visualViewport.pageLeft, scroll.left), Math.max(visualViewport.pageTop, scroll.top), visualViewport.width, visualViewport.height));
    };

    const children = (scope, predicate) => filter$5(children$1(scope), predicate);
    const descendants$1 = (scope, predicate) => {
      let result = [];
      each$e(children$1(scope), x => {
        if (predicate(x)) {
          result = result.concat([x]);
        }
        result = result.concat(descendants$1(x, predicate));
      });
      return result;
    };

    const descendants = (scope, selector) => all(selector, scope);

    const ancestor$2 = (scope, selector, isRoot) => ancestor$3(scope, selector, isRoot).isSome();

    class DomTreeWalker {
      constructor(startNode, rootNode) {
        this.node = startNode;
        this.rootNode = rootNode;
        this.current = this.current.bind(this);
        this.next = this.next.bind(this);
        this.prev = this.prev.bind(this);
        this.prev2 = this.prev2.bind(this);
      }
      current() {
        return this.node;
      }
      next(shallow) {
        this.node = this.findSibling(this.node, 'firstChild', 'nextSibling', shallow);
        return this.node;
      }
      prev(shallow) {
        this.node = this.findSibling(this.node, 'lastChild', 'previousSibling', shallow);
        return this.node;
      }
      prev2(shallow) {
        this.node = this.findPreviousNode(this.node, shallow);
        return this.node;
      }
      findSibling(node, startName, siblingName, shallow) {
        if (node) {
          if (!shallow && node[startName]) {
            return node[startName];
          }
          if (node !== this.rootNode) {
            let sibling = node[siblingName];
            if (sibling) {
              return sibling;
            }
            for (let parent = node.parentNode; parent && parent !== this.rootNode; parent = parent.parentNode) {
              sibling = parent[siblingName];
              if (sibling) {
                return sibling;
              }
            }
          }
        }
        return undefined;
      }
      findPreviousNode(node, shallow) {
        if (node) {
          const sibling = node.previousSibling;
          if (this.rootNode && sibling === this.rootNode) {
            return;
          }
          if (sibling) {
            if (!shallow) {
              for (let child = sibling.lastChild; child; child = child.lastChild) {
                if (!child.lastChild) {
                  return child;
                }
              }
            }
            return sibling;
          }
          const parent = node.parentNode;
          if (parent && parent !== this.rootNode) {
            return parent;
          }
        }
        return undefined;
      }
    }

    const isNodeType = type => {
      return node => {
        return !!node && node.nodeType === type;
      };
    };
    const isRestrictedNode = node => !!node && !Object.getPrototypeOf(node);
    const isElement$6 = isNodeType(1);
    const isHTMLElement = node => isElement$6(node) && isHTMLElement$1(SugarElement.fromDom(node));
    const isSVGElement = node => isElement$6(node) && node.namespaceURI === 'http://www.w3.org/2000/svg';
    const matchNodeName = name => {
      const lowerCasedName = name.toLowerCase();
      return node => isNonNullable(node) && node.nodeName.toLowerCase() === lowerCasedName;
    };
    const matchNodeNames = names => {
      const lowerCasedNames = names.map(s => s.toLowerCase());
      return node => {
        if (node && node.nodeName) {
          const nodeName = node.nodeName.toLowerCase();
          return contains$2(lowerCasedNames, nodeName);
        }
        return false;
      };
    };
    const matchStyleValues = (name, values) => {
      const items = values.toLowerCase().split(' ');
      return node => {
        if (isElement$6(node)) {
          const win = node.ownerDocument.defaultView;
          if (win) {
            for (let i = 0; i < items.length; i++) {
              const computed = win.getComputedStyle(node, null);
              const cssValue = computed ? computed.getPropertyValue(name) : null;
              if (cssValue === items[i]) {
                return true;
              }
            }
          }
        }
        return false;
      };
    };
    const hasAttribute = attrName => {
      return node => {
        return isElement$6(node) && node.hasAttribute(attrName);
      };
    };
    const hasAttributeValue = (attrName, attrValue) => {
      return node => {
        return isElement$6(node) && node.getAttribute(attrName) === attrValue;
      };
    };
    const isBogus$2 = node => isElement$6(node) && node.hasAttribute('data-mce-bogus');
    const isBogusAll$1 = node => isElement$6(node) && node.getAttribute('data-mce-bogus') === 'all';
    const isTable$2 = node => isElement$6(node) && node.tagName === 'TABLE';
    const hasContentEditableState = value => {
      return node => {
        if (isHTMLElement(node)) {
          if (node.contentEditable === value) {
            return true;
          }
          if (node.getAttribute('data-mce-contenteditable') === value) {
            return true;
          }
        }
        return false;
      };
    };
    const isTextareaOrInput = matchNodeNames([
      'textarea',
      'input'
    ]);
    const isText$a = isNodeType(3);
    const isCData = isNodeType(4);
    const isPi = isNodeType(7);
    const isComment = isNodeType(8);
    const isDocument$1 = isNodeType(9);
    const isDocumentFragment = isNodeType(11);
    const isBr$6 = matchNodeName('br');
    const isImg = matchNodeName('img');
    const isContentEditableTrue$3 = hasContentEditableState('true');
    const isContentEditableFalse$b = hasContentEditableState('false');
    const isTableCell$3 = matchNodeNames([
      'td',
      'th'
    ]);
    const isTableCellOrCaption = matchNodeNames([
      'td',
      'th',
      'caption'
    ]);
    const isMedia$2 = matchNodeNames([
      'video',
      'audio',
      'object',
      'embed'
    ]);
    const isListItem$2 = matchNodeName('li');
    const isDetails = matchNodeName('details');
    const isSummary$1 = matchNodeName('summary');

    const zeroWidth = '\uFEFF';
    const nbsp = '\xA0';
    const isZwsp$2 = char => char === zeroWidth;
    const removeZwsp = s => s.replace(/\uFEFF/g, '');

    const NodeValue = (is, name) => {
      const get = element => {
        if (!is(element)) {
          throw new Error('Can only get ' + name + ' value of a ' + name + ' node');
        }
        return getOption(element).getOr('');
      };
      const getOption = element => is(element) ? Optional.from(element.dom.nodeValue) : Optional.none();
      const set = (element, value) => {
        if (!is(element)) {
          throw new Error('Can only set raw ' + name + ' value of a ' + name + ' node');
        }
        element.dom.nodeValue = value;
      };
      return {
        get,
        getOption,
        set
      };
    };

    const api$1 = NodeValue(isText$b, 'text');
    const get$3 = element => api$1.get(element);
    const getOption = element => api$1.getOption(element);
    const set = (element, value) => api$1.set(element, value);

    const tableCells = [
      'td',
      'th'
    ];
    const tableSections = [
      'thead',
      'tbody',
      'tfoot'
    ];
    const textBlocks = [
      'h1',
      'h2',
      'h3',
      'h4',
      'h5',
      'h6',
      'p',
      'div',
      'address',
      'pre',
      'form',
      'blockquote',
      'center',
      'dir',
      'fieldset',
      'header',
      'footer',
      'article',
      'section',
      'hgroup',
      'aside',
      'nav',
      'figure'
    ];
    const listItems$1 = [
      'li',
      'dd',
      'dt'
    ];
    const lists = [
      'ul',
      'ol',
      'dl'
    ];
    const wsElements = [
      'pre',
      'script',
      'textarea',
      'style'
    ];
    const lazyLookup = items => {
      let lookup;
      return node => {
        lookup = lookup ? lookup : mapToObject(items, always);
        return has$2(lookup, name(node));
      };
    };
    const isTable$1 = node => name(node) === 'table';
    const isBr$5 = node => isElement$7(node) && name(node) === 'br';
    const isTextBlock$2 = lazyLookup(textBlocks);
    const isList = lazyLookup(lists);
    const isListItem$1 = lazyLookup(listItems$1);
    const isTableSection = lazyLookup(tableSections);
    const isTableCell$2 = lazyLookup(tableCells);
    const isWsPreserveElement = lazyLookup(wsElements);

    const getLastChildren$1 = elm => {
      const children = [];
      let rawNode = elm.dom;
      while (rawNode) {
        children.push(SugarElement.fromDom(rawNode));
        rawNode = rawNode.lastChild;
      }
      return children;
    };
    const removeTrailingBr = elm => {
      const allBrs = descendants(elm, 'br');
      const brs = filter$5(getLastChildren$1(elm).slice(-1), isBr$5);
      if (allBrs.length === brs.length) {
        each$e(brs, remove$5);
      }
    };
    const createPaddingBr = () => {
      const br = SugarElement.fromTag('br');
      set$3(br, 'data-mce-bogus', '1');
      return br;
    };
    const fillWithPaddingBr = elm => {
      empty(elm);
      append$1(elm, createPaddingBr());
    };
    const trimBlockTrailingBr = (elm, schema) => {
      lastChild(elm).each(lastChild => {
        prevSibling(lastChild).each(lastChildPrevSibling => {
          if (schema.isBlock(name(elm)) && isBr$5(lastChild) && schema.isBlock(name(lastChildPrevSibling))) {
            remove$5(lastChild);
          }
        });
      });
    };

    const ZWSP$1 = zeroWidth;
    const isZwsp$1 = isZwsp$2;
    const trim$2 = removeZwsp;
    const insert$5 = editor => editor.insertContent(ZWSP$1, { preserve_zwsp: true });

    const isElement$5 = isElement$6;
    const isText$9 = isText$a;
    const isCaretContainerBlock$1 = node => {
      if (isText$9(node)) {
        node = node.parentNode;
      }
      return isElement$5(node) && node.hasAttribute('data-mce-caret');
    };
    const isCaretContainerInline = node => isText$9(node) && isZwsp$1(node.data);
    const isCaretContainer$2 = node => isCaretContainerBlock$1(node) || isCaretContainerInline(node);
    const hasContent = node => node.firstChild !== node.lastChild || !isBr$6(node.firstChild);
    const insertInline$1 = (node, before) => {
      var _a;
      const doc = (_a = node.ownerDocument) !== null && _a !== void 0 ? _a : document;
      const textNode = doc.createTextNode(ZWSP$1);
      const parentNode = node.parentNode;
      if (!before) {
        const sibling = node.nextSibling;
        if (isText$9(sibling)) {
          if (isCaretContainer$2(sibling)) {
            return sibling;
          }
          if (startsWithCaretContainer$1(sibling)) {
            sibling.splitText(1);
            return sibling;
          }
        }
        if (node.nextSibling) {
          parentNode === null || parentNode === void 0 ? void 0 : parentNode.insertBefore(textNode, node.nextSibling);
        } else {
          parentNode === null || parentNode === void 0 ? void 0 : parentNode.appendChild(textNode);
        }
      } else {
        const sibling = node.previousSibling;
        if (isText$9(sibling)) {
          if (isCaretContainer$2(sibling)) {
            return sibling;
          }
          if (endsWithCaretContainer$1(sibling)) {
            return sibling.splitText(sibling.data.length - 1);
          }
        }
        parentNode === null || parentNode === void 0 ? void 0 : parentNode.insertBefore(textNode, node);
      }
      return textNode;
    };
    const isBeforeInline = pos => {
      const container = pos.container();
      if (!isText$a(container)) {
        return false;
      }
      return container.data.charAt(pos.offset()) === ZWSP$1 || pos.isAtStart() && isCaretContainerInline(container.previousSibling);
    };
    const isAfterInline = pos => {
      const container = pos.container();
      if (!isText$a(container)) {
        return false;
      }
      return container.data.charAt(pos.offset() - 1) === ZWSP$1 || pos.isAtEnd() && isCaretContainerInline(container.nextSibling);
    };
    const insertBlock = (blockName, node, before) => {
      var _a;
      const doc = (_a = node.ownerDocument) !== null && _a !== void 0 ? _a : document;
      const blockNode = doc.createElement(blockName);
      blockNode.setAttribute('data-mce-caret', before ? 'before' : 'after');
      blockNode.setAttribute('data-mce-bogus', 'all');
      blockNode.appendChild(createPaddingBr().dom);
      const parentNode = node.parentNode;
      if (!before) {
        if (node.nextSibling) {
          parentNode === null || parentNode === void 0 ? void 0 : parentNode.insertBefore(blockNode, node.nextSibling);
        } else {
          parentNode === null || parentNode === void 0 ? void 0 : parentNode.appendChild(blockNode);
        }
      } else {
        parentNode === null || parentNode === void 0 ? void 0 : parentNode.insertBefore(blockNode, node);
      }
      return blockNode;
    };
    const startsWithCaretContainer$1 = node => isText$9(node) && node.data[0] === ZWSP$1;
    const endsWithCaretContainer$1 = node => isText$9(node) && node.data[node.data.length - 1] === ZWSP$1;
    const trimBogusBr = elm => {
      var _a;
      const brs = elm.getElementsByTagName('br');
      const lastBr = brs[brs.length - 1];
      if (isBogus$2(lastBr)) {
        (_a = lastBr.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(lastBr);
      }
    };
    const showCaretContainerBlock = caretContainer => {
      if (caretContainer && caretContainer.hasAttribute('data-mce-caret')) {
        trimBogusBr(caretContainer);
        caretContainer.removeAttribute('data-mce-caret');
        caretContainer.removeAttribute('data-mce-bogus');
        caretContainer.removeAttribute('style');
        caretContainer.removeAttribute('data-mce-style');
        caretContainer.removeAttribute('_moz_abspos');
        return caretContainer;
      }
      return null;
    };
    const isRangeInCaretContainerBlock = range => isCaretContainerBlock$1(range.startContainer);

    const isContentEditableTrue$2 = isContentEditableTrue$3;
    const isContentEditableFalse$a = isContentEditableFalse$b;
    const isBr$4 = isBr$6;
    const isText$8 = isText$a;
    const isInvalidTextElement = matchNodeNames([
      'script',
      'style',
      'textarea'
    ]);
    const isAtomicInline = matchNodeNames([
      'img',
      'input',
      'textarea',
      'hr',
      'iframe',
      'video',
      'audio',
      'object',
      'embed'
    ]);
    const isTable = matchNodeNames(['table']);
    const isCaretContainer$1 = isCaretContainer$2;
    const isCaretCandidate$3 = node => {
      if (isCaretContainer$1(node)) {
        return false;
      }
      if (isText$8(node)) {
        return !isInvalidTextElement(node.parentNode);
      }
      return isAtomicInline(node) || isBr$4(node) || isTable(node) || isNonUiContentEditableFalse(node);
    };
    const isUnselectable = node => isElement$6(node) && node.getAttribute('unselectable') === 'true';
    const isNonUiContentEditableFalse = node => !isUnselectable(node) && isContentEditableFalse$a(node);
    const isInEditable = (node, root) => {
      for (let tempNode = node.parentNode; tempNode && tempNode !== root; tempNode = tempNode.parentNode) {
        if (isNonUiContentEditableFalse(tempNode)) {
          return false;
        }
        if (isContentEditableTrue$2(tempNode)) {
          return true;
        }
      }
      return true;
    };
    const isAtomicContentEditableFalse = node => {
      if (!isNonUiContentEditableFalse(node)) {
        return false;
      }
      return !foldl(from(node.getElementsByTagName('*')), (result, elm) => {
        return result || isContentEditableTrue$2(elm);
      }, false);
    };
    const isAtomic$1 = node => isAtomicInline(node) || isAtomicContentEditableFalse(node);
    const isEditableCaretCandidate$1 = (node, root) => isCaretCandidate$3(node) && isInEditable(node, root);

    const whiteSpaceRegExp = /^[ \t\r\n]*$/;
    const isWhitespaceText = text => whiteSpaceRegExp.test(text);
    const isZwsp = text => {
      for (const c of text) {
        if (!isZwsp$2(c)) {
          return false;
        }
      }
      return true;
    };
    const isCollapsibleWhitespace$1 = c => ' \f\t\x0B'.indexOf(c) !== -1;
    const isNewLineChar = c => c === '\n' || c === '\r';
    const isNewline = (text, idx) => idx < text.length && idx >= 0 ? isNewLineChar(text[idx]) : false;
    const normalize$4 = (text, tabSpaces = 4, isStartOfContent = true, isEndOfContent = true) => {
      const tabSpace = repeat(' ', tabSpaces);
      const normalizedText = text.replace(/\t/g, tabSpace);
      const result = foldl(normalizedText, (acc, c) => {
        if (isCollapsibleWhitespace$1(c) || c === nbsp) {
          if (acc.pcIsSpace || acc.str === '' && isStartOfContent || acc.str.length === normalizedText.length - 1 && isEndOfContent || isNewline(normalizedText, acc.str.length + 1)) {
            return {
              pcIsSpace: false,
              str: acc.str + nbsp
            };
          } else {
            return {
              pcIsSpace: true,
              str: acc.str + ' '
            };
          }
        } else {
          return {
            pcIsSpace: isNewLineChar(c),
            str: acc.str + c
          };
        }
      }, {
        pcIsSpace: false,
        str: ''
      });
      return result.str;
    };

    const hasWhitespacePreserveParent = (node, rootNode) => {
      const rootElement = SugarElement.fromDom(rootNode);
      const startNode = SugarElement.fromDom(node);
      return ancestor$2(startNode, 'pre,code', curry(eq, rootElement));
    };
    const isWhitespace$1 = (node, rootNode) => {
      return isText$a(node) && isWhitespaceText(node.data) && !hasWhitespacePreserveParent(node, rootNode);
    };
    const isNamedAnchor = node => {
      return isElement$6(node) && node.nodeName === 'A' && !node.hasAttribute('href') && (node.hasAttribute('name') || node.hasAttribute('id'));
    };
    const isContent$1 = (node, rootNode) => {
      return isCaretCandidate$3(node) && !isWhitespace$1(node, rootNode) || isNamedAnchor(node) || isBookmark(node);
    };
    const isBookmark = hasAttribute('data-mce-bookmark');
    const isBogus$1 = hasAttribute('data-mce-bogus');
    const isBogusAll = hasAttributeValue('data-mce-bogus', 'all');
    const hasNonEditableParent = node => parentElement(SugarElement.fromDom(node)).exists(parent => !isEditable$2(parent));
    const isEmptyNode = (targetNode, skipBogus) => {
      let brCount = 0;
      if (isContent$1(targetNode, targetNode)) {
        return false;
      } else {
        let node = targetNode.firstChild;
        if (!node) {
          return true;
        }
        const walker = new DomTreeWalker(node, targetNode);
        do {
          if (skipBogus) {
            if (isBogusAll(node)) {
              node = walker.next(true);
              continue;
            }
            if (isBogus$1(node)) {
              node = walker.next();
              continue;
            }
          }
          if (isContentEditableTrue$3(node) && hasNonEditableParent(node)) {
            return false;
          }
          if (isBr$6(node)) {
            brCount++;
            node = walker.next();
            continue;
          }
          if (isContent$1(node, targetNode)) {
            return false;
          }
          node = walker.next();
        } while (node);
        return brCount <= 1;
      }
    };
    const isEmpty$2 = (elm, skipBogus = true) => isEmptyNode(elm.dom, skipBogus);

    const isNonHtmlElementRootName = name => name.toLowerCase() === 'svg';
    const isNonHtmlElementRoot = node => isNonHtmlElementRootName(node.nodeName);
    const toScopeType = node => (node === null || node === void 0 ? void 0 : node.nodeName) === 'svg' ? 'svg' : 'html';
    const namespaceElements = ['svg'];
    const createNamespaceTracker = () => {
      let scopes = [];
      const peek = () => scopes[scopes.length - 1];
      const track = node => {
        if (isNonHtmlElementRoot(node)) {
          scopes.push(node);
        }
        let currentScope = peek();
        if (currentScope && !currentScope.contains(node)) {
          scopes.pop();
          currentScope = peek();
        }
        return toScopeType(currentScope);
      };
      const current = () => toScopeType(peek());
      const reset = () => {
        scopes = [];
      };
      return {
        track,
        current,
        reset
      };
    };

    const transparentBlockAttr = 'data-mce-block';
    const elementNames = map => filter$5(keys(map), key => !/[A-Z]/.test(key));
    const makeSelectorFromSchemaMap = map => map$3(elementNames(map), name => {
      return `${ name }:` + map$3(namespaceElements, ns => `not(${ ns } ${ name })`).join(':');
    }).join(',');
    const updateTransparent = (blocksSelector, transparent) => {
      if (isNonNullable(transparent.querySelector(blocksSelector))) {
        transparent.setAttribute(transparentBlockAttr, 'true');
        if (transparent.getAttribute('data-mce-selected') === 'inline-boundary') {
          transparent.removeAttribute('data-mce-selected');
        }
        return true;
      } else {
        transparent.removeAttribute(transparentBlockAttr);
        return false;
      }
    };
    const updateBlockStateOnChildren = (schema, scope) => {
      const transparentSelector = makeSelectorFromSchemaMap(schema.getTransparentElements());
      const blocksSelector = makeSelectorFromSchemaMap(schema.getBlockElements());
      return filter$5(scope.querySelectorAll(transparentSelector), transparent => updateTransparent(blocksSelector, transparent));
    };
    const trimEdge = (el, leftSide) => {
      var _a;
      const childPropertyName = leftSide ? 'lastChild' : 'firstChild';
      for (let child = el[childPropertyName]; child; child = child[childPropertyName]) {
        if (isEmpty$2(SugarElement.fromDom(child))) {
          (_a = child.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(child);
          return;
        }
      }
    };
    const split$2 = (parentElm, splitElm) => {
      const range = document.createRange();
      const parentNode = parentElm.parentNode;
      if (parentNode) {
        range.setStartBefore(parentElm);
        range.setEndBefore(splitElm);
        const beforeFragment = range.extractContents();
        trimEdge(beforeFragment, true);
        range.setStartAfter(splitElm);
        range.setEndAfter(parentElm);
        const afterFragment = range.extractContents();
        trimEdge(afterFragment, false);
        if (!isEmpty$2(SugarElement.fromDom(beforeFragment))) {
          parentNode.insertBefore(beforeFragment, parentElm);
        }
        if (!isEmpty$2(SugarElement.fromDom(splitElm))) {
          parentNode.insertBefore(splitElm, parentElm);
        }
        if (!isEmpty$2(SugarElement.fromDom(afterFragment))) {
          parentNode.insertBefore(afterFragment, parentElm);
        }
        parentNode.removeChild(parentElm);
      }
    };
    const splitInvalidChildren = (schema, scope, transparentBlocks) => {
      const blocksElements = schema.getBlockElements();
      const rootNode = SugarElement.fromDom(scope);
      const isBlock = el => name(el) in blocksElements;
      const isRoot = el => eq(el, rootNode);
      each$e(fromDom$1(transparentBlocks), transparentBlock => {
        ancestor$4(transparentBlock, isBlock, isRoot).each(parentBlock => {
          const invalidChildren = children(transparentBlock, el => isBlock(el) && !schema.isValidChild(name(parentBlock), name(el)));
          if (invalidChildren.length > 0) {
            const stateScope = parentElement(parentBlock);
            each$e(invalidChildren, child => {
              ancestor$4(child, isBlock, isRoot).each(parentBlock => {
                split$2(parentBlock.dom, child.dom);
              });
            });
            stateScope.each(scope => updateBlockStateOnChildren(schema, scope.dom));
          }
        });
      });
    };
    const unwrapInvalidChildren = (schema, scope, transparentBlocks) => {
      each$e([
        ...transparentBlocks,
        ...isTransparentBlock(schema, scope) ? [scope] : []
      ], block => each$e(descendants(SugarElement.fromDom(block), block.nodeName.toLowerCase()), elm => {
        if (isTransparentInline(schema, elm.dom)) {
          unwrap(elm);
        }
      }));
    };
    const updateChildren = (schema, scope) => {
      const transparentBlocks = updateBlockStateOnChildren(schema, scope);
      splitInvalidChildren(schema, scope, transparentBlocks);
      unwrapInvalidChildren(schema, scope, transparentBlocks);
    };
    const updateElement = (schema, target) => {
      if (isTransparentElement(schema, target)) {
        const blocksSelector = makeSelectorFromSchemaMap(schema.getBlockElements());
        updateTransparent(blocksSelector, target);
      }
    };
    const updateCaret = (schema, root, caretParent) => {
      const isRoot = el => eq(el, SugarElement.fromDom(root));
      const parents = parents$1(SugarElement.fromDom(caretParent), isRoot);
      get$b(parents, parents.length - 2).filter(isElement$7).fold(() => updateChildren(schema, root), scope => updateChildren(schema, scope.dom));
    };
    const hasBlockAttr = el => el.hasAttribute(transparentBlockAttr);
    const isTransparentElementName = (schema, name) => has$2(schema.getTransparentElements(), name);
    const isTransparentElement = (schema, node) => isElement$6(node) && isTransparentElementName(schema, node.nodeName);
    const isTransparentBlock = (schema, node) => isTransparentElement(schema, node) && hasBlockAttr(node);
    const isTransparentInline = (schema, node) => isTransparentElement(schema, node) && !hasBlockAttr(node);
    const isTransparentAstBlock = (schema, node) => node.type === 1 && isTransparentElementName(schema, node.name) && isString(node.attr(transparentBlockAttr));

    const browser$2 = detect$2().browser;
    const firstElement = nodes => find$2(nodes, isElement$7);
    const getTableCaptionDeltaY = elm => {
      if (browser$2.isFirefox() && name(elm) === 'table') {
        return firstElement(children$1(elm)).filter(elm => {
          return name(elm) === 'caption';
        }).bind(caption => {
          return firstElement(nextSiblings(caption)).map(body => {
            const bodyTop = body.dom.offsetTop;
            const captionTop = caption.dom.offsetTop;
            const captionHeight = caption.dom.offsetHeight;
            return bodyTop <= captionTop ? -captionHeight : 0;
          });
        }).getOr(0);
      } else {
        return 0;
      }
    };
    const hasChild = (elm, child) => elm.children && contains$2(elm.children, child);
    const getPos = (body, elm, rootElm) => {
      let x = 0, y = 0;
      const doc = body.ownerDocument;
      rootElm = rootElm ? rootElm : body;
      if (elm) {
        if (rootElm === body && elm.getBoundingClientRect && get$7(SugarElement.fromDom(body), 'position') === 'static') {
          const pos = elm.getBoundingClientRect();
          x = pos.left + (doc.documentElement.scrollLeft || body.scrollLeft) - doc.documentElement.clientLeft;
          y = pos.top + (doc.documentElement.scrollTop || body.scrollTop) - doc.documentElement.clientTop;
          return {
            x,
            y
          };
        }
        let offsetParent = elm;
        while (offsetParent && offsetParent !== rootElm && offsetParent.nodeType && !hasChild(offsetParent, rootElm)) {
          const castOffsetParent = offsetParent;
          x += castOffsetParent.offsetLeft || 0;
          y += castOffsetParent.offsetTop || 0;
          offsetParent = castOffsetParent.offsetParent;
        }
        offsetParent = elm.parentNode;
        while (offsetParent && offsetParent !== rootElm && offsetParent.nodeType && !hasChild(offsetParent, rootElm)) {
          x -= offsetParent.scrollLeft || 0;
          y -= offsetParent.scrollTop || 0;
          offsetParent = offsetParent.parentNode;
        }
        y += getTableCaptionDeltaY(SugarElement.fromDom(elm));
      }
      return {
        x,
        y
      };
    };

    const StyleSheetLoader = (documentOrShadowRoot, settings = {}) => {
      let idCount = 0;
      const loadedStates = {};
      const edos = SugarElement.fromDom(documentOrShadowRoot);
      const doc = documentOrOwner(edos);
      const _setReferrerPolicy = referrerPolicy => {
        settings.referrerPolicy = referrerPolicy;
      };
      const _setContentCssCors = contentCssCors => {
        settings.contentCssCors = contentCssCors;
      };
      const addStyle = element => {
        append$1(getStyleContainer(edos), element);
      };
      const removeStyle = id => {
        const styleContainer = getStyleContainer(edos);
        descendant$1(styleContainer, '#' + id).each(remove$5);
      };
      const getOrCreateState = url => get$a(loadedStates, url).getOrThunk(() => ({
        id: 'mce-u' + idCount++,
        passed: [],
        failed: [],
        count: 0
      }));
      const load = url => new Promise((success, failure) => {
        let link;
        const urlWithSuffix = Tools._addCacheSuffix(url);
        const state = getOrCreateState(urlWithSuffix);
        loadedStates[urlWithSuffix] = state;
        state.count++;
        const resolve = (callbacks, status) => {
          each$e(callbacks, call);
          state.status = status;
          state.passed = [];
          state.failed = [];
          if (link) {
            link.onload = null;
            link.onerror = null;
            link = null;
          }
        };
        const passed = () => resolve(state.passed, 2);
        const failed = () => resolve(state.failed, 3);
        if (success) {
          state.passed.push(success);
        }
        if (failure) {
          state.failed.push(failure);
        }
        if (state.status === 1) {
          return;
        }
        if (state.status === 2) {
          passed();
          return;
        }
        if (state.status === 3) {
          failed();
          return;
        }
        state.status = 1;
        const linkElem = SugarElement.fromTag('link', doc.dom);
        setAll$1(linkElem, {
          rel: 'stylesheet',
          type: 'text/css',
          id: state.id
        });
        if (settings.contentCssCors) {
          set$3(linkElem, 'crossOrigin', 'anonymous');
        }
        if (settings.referrerPolicy) {
          set$3(linkElem, 'referrerpolicy', settings.referrerPolicy);
        }
        link = linkElem.dom;
        link.onload = passed;
        link.onerror = failed;
        addStyle(linkElem);
        set$3(linkElem, 'href', urlWithSuffix);
      });
      const loadRawCss = (key, css) => {
        const state = getOrCreateState(key);
        loadedStates[key] = state;
        state.count++;
        const styleElem = SugarElement.fromTag('style', doc.dom);
        setAll$1(styleElem, {
          rel: 'stylesheet',
          type: 'text/css',
          id: state.id
        });
        styleElem.dom.innerHTML = css;
        addStyle(styleElem);
      };
      const loadAll = urls => {
        const loadedUrls = Promise.allSettled(map$3(urls, url => load(url).then(constant(url))));
        return loadedUrls.then(results => {
          const parts = partition$2(results, r => r.status === 'fulfilled');
          if (parts.fail.length > 0) {
            return Promise.reject(map$3(parts.fail, result => result.reason));
          } else {
            return map$3(parts.pass, result => result.value);
          }
        });
      };
      const unload = url => {
        const urlWithSuffix = Tools._addCacheSuffix(url);
        get$a(loadedStates, urlWithSuffix).each(state => {
          const count = --state.count;
          if (count === 0) {
            delete loadedStates[urlWithSuffix];
            removeStyle(state.id);
          }
        });
      };
      const unloadRawCss = key => {
        get$a(loadedStates, key).each(state => {
          const count = --state.count;
          if (count === 0) {
            delete loadedStates[key];
            removeStyle(state.id);
          }
        });
      };
      const unloadAll = urls => {
        each$e(urls, url => {
          unload(url);
        });
      };
      return {
        load,
        loadRawCss,
        loadAll,
        unload,
        unloadRawCss,
        unloadAll,
        _setReferrerPolicy,
        _setContentCssCors
      };
    };

    const create$d = () => {
      const map = new WeakMap();
      const forElement = (referenceElement, settings) => {
        const root = getRootNode(referenceElement);
        const rootDom = root.dom;
        return Optional.from(map.get(rootDom)).getOrThunk(() => {
          const sl = StyleSheetLoader(rootDom, settings);
          map.set(rootDom, sl);
          return sl;
        });
      };
      return { forElement };
    };
    const instance = create$d();

    const isSpan = node => node.nodeName.toLowerCase() === 'span';
    const isInlineContent = (node, root, schema) => isNonNullable(node) && (isContent$1(node, root) || schema.isInline(node.nodeName.toLowerCase()));
    const surroundedByInlineContent = (node, root, schema) => {
      const prev = new DomTreeWalker(node, root).prev(false);
      const next = new DomTreeWalker(node, root).next(false);
      const prevIsInline = isUndefined(prev) || isInlineContent(prev, root, schema);
      const nextIsInline = isUndefined(next) || isInlineContent(next, root, schema);
      return prevIsInline && nextIsInline;
    };
    const isBookmarkNode$2 = node => isSpan(node) && node.getAttribute('data-mce-type') === 'bookmark';
    const isKeepTextNode = (node, root, schema) => isText$a(node) && node.data.length > 0 && surroundedByInlineContent(node, root, schema);
    const isKeepElement = node => isElement$6(node) ? node.childNodes.length > 0 : false;
    const isDocument = node => isDocumentFragment(node) || isDocument$1(node);
    const trimNode = (dom, node, schema, root) => {
      var _a;
      const rootNode = root || node;
      if (isElement$6(node) && isBookmarkNode$2(node)) {
        return node;
      }
      const children = node.childNodes;
      for (let i = children.length - 1; i >= 0; i--) {
        trimNode(dom, children[i], schema, rootNode);
      }
      if (isElement$6(node)) {
        const currentChildren = node.childNodes;
        if (currentChildren.length === 1 && isBookmarkNode$2(currentChildren[0])) {
          (_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(currentChildren[0], node);
        }
      }
      if (!isDocument(node) && !isContent$1(node, rootNode) && !isKeepElement(node) && !isKeepTextNode(node, rootNode, schema)) {
        dom.remove(node);
      }
      return node;
    };

    const makeMap$3 = Tools.makeMap;
    const attrsCharsRegExp = /[&<>\"\u0060\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
    const textCharsRegExp = /[<>&\u007E-\uD7FF\uE000-\uFFEF]|[\uD800-\uDBFF][\uDC00-\uDFFF]/g;
    const rawCharsRegExp = /[<>&\"\']/g;
    const entityRegExp = /&#([a-z0-9]+);?|&([a-z0-9]+);/gi;
    const asciiMap = {
      128: '\u20AC',
      130: '\u201A',
      131: '\u0192',
      132: '\u201E',
      133: '\u2026',
      134: '\u2020',
      135: '\u2021',
      136: '\u02c6',
      137: '\u2030',
      138: '\u0160',
      139: '\u2039',
      140: '\u0152',
      142: '\u017d',
      145: '\u2018',
      146: '\u2019',
      147: '\u201C',
      148: '\u201D',
      149: '\u2022',
      150: '\u2013',
      151: '\u2014',
      152: '\u02DC',
      153: '\u2122',
      154: '\u0161',
      155: '\u203A',
      156: '\u0153',
      158: '\u017e',
      159: '\u0178'
    };
    const baseEntities = {
      '"': '&quot;',
      '\'': '&#39;',
      '<': '&lt;',
      '>': '&gt;',
      '&': '&amp;',
      '`': '&#96;'
    };
    const reverseEntities = {
      '&lt;': '<',
      '&gt;': '>',
      '&amp;': '&',
      '&quot;': '"',
      '&apos;': `'`
    };
    const nativeDecode = text => {
      const elm = SugarElement.fromTag('div').dom;
      elm.innerHTML = text;
      return elm.textContent || elm.innerText || text;
    };
    const buildEntitiesLookup = (items, radix) => {
      const lookup = {};
      if (items) {
        const itemList = items.split(',');
        radix = radix || 10;
        for (let i = 0; i < itemList.length; i += 2) {
          const chr = String.fromCharCode(parseInt(itemList[i], radix));
          if (!baseEntities[chr]) {
            const entity = '&' + itemList[i + 1] + ';';
            lookup[chr] = entity;
            lookup[entity] = chr;
          }
        }
        return lookup;
      } else {
        return undefined;
      }
    };
    const namedEntities = buildEntitiesLookup('50,nbsp,51,iexcl,52,cent,53,pound,54,curren,55,yen,56,brvbar,57,sect,58,uml,59,copy,' + '5a,ordf,5b,laquo,5c,not,5d,shy,5e,reg,5f,macr,5g,deg,5h,plusmn,5i,sup2,5j,sup3,5k,acute,' + '5l,micro,5m,para,5n,middot,5o,cedil,5p,sup1,5q,ordm,5r,raquo,5s,frac14,5t,frac12,5u,frac34,' + '5v,iquest,60,Agrave,61,Aacute,62,Acirc,63,Atilde,64,Auml,65,Aring,66,AElig,67,Ccedil,' + '68,Egrave,69,Eacute,6a,Ecirc,6b,Euml,6c,Igrave,6d,Iacute,6e,Icirc,6f,Iuml,6g,ETH,6h,Ntilde,' + '6i,Ograve,6j,Oacute,6k,Ocirc,6l,Otilde,6m,Ouml,6n,times,6o,Oslash,6p,Ugrave,6q,Uacute,' + '6r,Ucirc,6s,Uuml,6t,Yacute,6u,THORN,6v,szlig,70,agrave,71,aacute,72,acirc,73,atilde,74,auml,' + '75,aring,76,aelig,77,ccedil,78,egrave,79,eacute,7a,ecirc,7b,euml,7c,igrave,7d,iacute,7e,icirc,' + '7f,iuml,7g,eth,7h,ntilde,7i,ograve,7j,oacute,7k,ocirc,7l,otilde,7m,ouml,7n,divide,7o,oslash,' + '7p,ugrave,7q,uacute,7r,ucirc,7s,uuml,7t,yacute,7u,thorn,7v,yuml,ci,fnof,sh,Alpha,si,Beta,' + 'sj,Gamma,sk,Delta,sl,Epsilon,sm,Zeta,sn,Eta,so,Theta,sp,Iota,sq,Kappa,sr,Lambda,ss,Mu,' + 'st,Nu,su,Xi,sv,Omicron,t0,Pi,t1,Rho,t3,Sigma,t4,Tau,t5,Upsilon,t6,Phi,t7,Chi,t8,Psi,' + 't9,Omega,th,alpha,ti,beta,tj,gamma,tk,delta,tl,epsilon,tm,zeta,tn,eta,to,theta,tp,iota,' + 'tq,kappa,tr,lambda,ts,mu,tt,nu,tu,xi,tv,omicron,u0,pi,u1,rho,u2,sigmaf,u3,sigma,u4,tau,' + 'u5,upsilon,u6,phi,u7,chi,u8,psi,u9,omega,uh,thetasym,ui,upsih,um,piv,812,bull,816,hellip,' + '81i,prime,81j,Prime,81u,oline,824,frasl,88o,weierp,88h,image,88s,real,892,trade,89l,alefsym,' + '8cg,larr,8ch,uarr,8ci,rarr,8cj,darr,8ck,harr,8dl,crarr,8eg,lArr,8eh,uArr,8ei,rArr,8ej,dArr,' + '8ek,hArr,8g0,forall,8g2,part,8g3,exist,8g5,empty,8g7,nabla,8g8,isin,8g9,notin,8gb,ni,8gf,prod,' + '8gh,sum,8gi,minus,8gn,lowast,8gq,radic,8gt,prop,8gu,infin,8h0,ang,8h7,and,8h8,or,8h9,cap,8ha,cup,' + '8hb,int,8hk,there4,8hs,sim,8i5,cong,8i8,asymp,8j0,ne,8j1,equiv,8j4,le,8j5,ge,8k2,sub,8k3,sup,8k4,' + 'nsub,8k6,sube,8k7,supe,8kl,oplus,8kn,otimes,8l5,perp,8m5,sdot,8o8,lceil,8o9,rceil,8oa,lfloor,8ob,' + 'rfloor,8p9,lang,8pa,rang,9ea,loz,9j0,spades,9j3,clubs,9j5,hearts,9j6,diams,ai,OElig,aj,oelig,b0,' + 'Scaron,b1,scaron,bo,Yuml,m6,circ,ms,tilde,802,ensp,803,emsp,809,thinsp,80c,zwnj,80d,zwj,80e,lrm,' + '80f,rlm,80j,ndash,80k,mdash,80o,lsquo,80p,rsquo,80q,sbquo,80s,ldquo,80t,rdquo,80u,bdquo,810,dagger,' + '811,Dagger,81g,permil,81p,lsaquo,81q,rsaquo,85c,euro', 32);
    const encodeRaw = (text, attr) => text.replace(attr ? attrsCharsRegExp : textCharsRegExp, chr => {
      return baseEntities[chr] || chr;
    });
    const encodeAllRaw = text => ('' + text).replace(rawCharsRegExp, chr => {
      return baseEntities[chr] || chr;
    });
    const encodeNumeric = (text, attr) => text.replace(attr ? attrsCharsRegExp : textCharsRegExp, chr => {
      if (chr.length > 1) {
        return '&#' + ((chr.charCodeAt(0) - 55296) * 1024 + (chr.charCodeAt(1) - 56320) + 65536) + ';';
      }
      return baseEntities[chr] || '&#' + chr.charCodeAt(0) + ';';
    });
    const encodeNamed = (text, attr, entities) => {
      const resolveEntities = entities || namedEntities;
      return text.replace(attr ? attrsCharsRegExp : textCharsRegExp, chr => {
        return baseEntities[chr] || resolveEntities[chr] || chr;
      });
    };
    const getEncodeFunc = (name, entities) => {
      const entitiesMap = buildEntitiesLookup(entities) || namedEntities;
      const encodeNamedAndNumeric = (text, attr) => text.replace(attr ? attrsCharsRegExp : textCharsRegExp, chr => {
        if (baseEntities[chr] !== undefined) {
          return baseEntities[chr];
        }
        if (entitiesMap[chr] !== undefined) {
          return entitiesMap[chr];
        }
        if (chr.length > 1) {
          return '&#' + ((chr.charCodeAt(0) - 55296) * 1024 + (chr.charCodeAt(1) - 56320) + 65536) + ';';
        }
        return '&#' + chr.charCodeAt(0) + ';';
      });
      const encodeCustomNamed = (text, attr) => {
        return encodeNamed(text, attr, entitiesMap);
      };
      const nameMap = makeMap$3(name.replace(/\+/g, ','));
      if (nameMap.named && nameMap.numeric) {
        return encodeNamedAndNumeric;
      }
      if (nameMap.named) {
        if (entities) {
          return encodeCustomNamed;
        }
        return encodeNamed;
      }
      if (nameMap.numeric) {
        return encodeNumeric;
      }
      return encodeRaw;
    };
    const decode = text => text.replace(entityRegExp, (all, numeric) => {
      if (numeric) {
        if (numeric.charAt(0).toLowerCase() === 'x') {
          numeric = parseInt(numeric.substr(1), 16);
        } else {
          numeric = parseInt(numeric, 10);
        }
        if (numeric > 65535) {
          numeric -= 65536;
          return String.fromCharCode(55296 + (numeric >> 10), 56320 + (numeric & 1023));
        }
        return asciiMap[numeric] || String.fromCharCode(numeric);
      }
      return reverseEntities[all] || namedEntities[all] || nativeDecode(all);
    });
    const Entities = {
      encodeRaw,
      encodeAllRaw,
      encodeNumeric,
      encodeNamed,
      getEncodeFunc,
      decode
    };

    const split$1 = (items, delim) => {
      items = Tools.trim(items);
      return items ? items.split(delim || ' ') : [];
    };
    const patternToRegExp = str => new RegExp('^' + str.replace(/([?+*])/g, '.$1') + '$');

    const parseCustomElementsRules = value => {
      const customElementRegExp = /^(~)?(.+)$/;
      return bind$3(split$1(value, ','), rule => {
        const matches = customElementRegExp.exec(rule);
        if (matches) {
          const inline = matches[1] === '~';
          const cloneName = inline ? 'span' : 'div';
          const name = matches[2];
          return [{
              inline,
              cloneName,
              name
            }];
        } else {
          return [];
        }
      });
    };

    const getElementSetsAsStrings = type => {
      let globalAttributes, blockContent;
      let phrasingContent;
      globalAttributes = 'id accesskey class dir lang style tabindex title role';
      blockContent = 'address blockquote div dl fieldset form h1 h2 h3 h4 h5 h6 hr menu ol p pre table ul';
      phrasingContent = 'a abbr b bdo br button cite code del dfn em embed i iframe img input ins kbd ' + 'label map noscript object q s samp script select small span strong sub sup ' + 'textarea u var #text #comment';
      if (type !== 'html4') {
        const transparentContent = 'a ins del canvas map';
        globalAttributes += ' contenteditable contextmenu draggable dropzone ' + 'hidden spellcheck translate';
        blockContent += ' article aside details dialog figure main header footer hgroup section nav ' + transparentContent;
        phrasingContent += ' audio canvas command datalist mark meter output picture ' + 'progress time wbr video ruby bdi keygen svg';
      }
      if (type !== 'html5-strict') {
        globalAttributes += ' xml:lang';
        const html4PhrasingContent = 'acronym applet basefont big font strike tt';
        phrasingContent = [
          phrasingContent,
          html4PhrasingContent
        ].join(' ');
        const html4BlockContent = 'center dir isindex noframes';
        blockContent = [
          blockContent,
          html4BlockContent
        ].join(' ');
      }
      const flowContent = [
        blockContent,
        phrasingContent
      ].join(' ');
      return {
        globalAttributes,
        blockContent,
        phrasingContent,
        flowContent
      };
    };

    const makeSchema = type => {
      const {globalAttributes, phrasingContent, flowContent} = getElementSetsAsStrings(type);
      const schema = {};
      const addElement = (name, attributes, children) => {
        schema[name] = {
          attributes: mapToObject(attributes, constant({})),
          attributesOrder: attributes,
          children: mapToObject(children, constant({}))
        };
      };
      const add = (name, attributes = '', children = '') => {
        const childNames = split$1(children);
        const names = split$1(name);
        let ni = names.length;
        const allAttributes = split$1([
          globalAttributes,
          attributes
        ].join(' '));
        while (ni--) {
          addElement(names[ni], allAttributes.slice(), childNames);
        }
      };
      const addAttrs = (name, attributes) => {
        const names = split$1(name);
        const attrs = split$1(attributes);
        let ni = names.length;
        while (ni--) {
          const schemaItem = schema[names[ni]];
          for (let i = 0, l = attrs.length; i < l; i++) {
            schemaItem.attributes[attrs[i]] = {};
            schemaItem.attributesOrder.push(attrs[i]);
          }
        }
      };
      if (type !== 'html5-strict') {
        const html4PhrasingContent = 'acronym applet basefont big font strike tt';
        each$e(split$1(html4PhrasingContent), name => {
          add(name, '', phrasingContent);
        });
        const html4BlockContent = 'center dir isindex noframes';
        each$e(split$1(html4BlockContent), name => {
          add(name, '', flowContent);
        });
      }
      add('html', 'manifest', 'head body');
      add('head', '', 'base command link meta noscript script style title');
      add('title hr noscript br');
      add('base', 'href target');
      add('link', 'href rel media hreflang type sizes hreflang');
      add('meta', 'name http-equiv content charset');
      add('style', 'media type scoped');
      add('script', 'src async defer type charset');
      add('body', 'onafterprint onbeforeprint onbeforeunload onblur onerror onfocus ' + 'onhashchange onload onmessage onoffline ononline onpagehide onpageshow ' + 'onpopstate onresize onscroll onstorage onunload', flowContent);
      add('dd div', '', flowContent);
      add('address dt caption', '', type === 'html4' ? phrasingContent : flowContent);
      add('h1 h2 h3 h4 h5 h6 pre p abbr code var samp kbd sub sup i b u bdo span legend em strong small s cite dfn', '', phrasingContent);
      add('blockquote', 'cite', flowContent);
      add('ol', 'reversed start type', 'li');
      add('ul', '', 'li');
      add('li', 'value', flowContent);
      add('dl', '', 'dt dd');
      add('a', 'href target rel media hreflang type', type === 'html4' ? phrasingContent : flowContent);
      add('q', 'cite', phrasingContent);
      add('ins del', 'cite datetime', flowContent);
      add('img', 'src sizes srcset alt usemap ismap width height');
      add('iframe', 'src name width height', flowContent);
      add('embed', 'src type width height');
      add('object', 'data type typemustmatch name usemap form width height', [
        flowContent,
        'param'
      ].join(' '));
      add('param', 'name value');
      add('map', 'name', [
        flowContent,
        'area'
      ].join(' '));
      add('area', 'alt coords shape href target rel media hreflang type');
      add('table', 'border', 'caption colgroup thead tfoot tbody tr' + (type === 'html4' ? ' col' : ''));
      add('colgroup', 'span', 'col');
      add('col', 'span');
      add('tbody thead tfoot', '', 'tr');
      add('tr', '', 'td th');
      add('td', 'colspan rowspan headers', flowContent);
      add('th', 'colspan rowspan headers scope abbr', flowContent);
      add('form', 'accept-charset action autocomplete enctype method name novalidate target', flowContent);
      add('fieldset', 'disabled form name', [
        flowContent,
        'legend'
      ].join(' '));
      add('label', 'form for', phrasingContent);
      add('input', 'accept alt autocomplete checked dirname disabled form formaction formenctype formmethod formnovalidate ' + 'formtarget height list max maxlength min multiple name pattern readonly required size src step type value width');
      add('button', 'disabled form formaction formenctype formmethod formnovalidate formtarget name type value', type === 'html4' ? flowContent : phrasingContent);
      add('select', 'disabled form multiple name required size', 'option optgroup');
      add('optgroup', 'disabled label', 'option');
      add('option', 'disabled label selected value');
      add('textarea', 'cols dirname disabled form maxlength name readonly required rows wrap');
      add('menu', 'type label', [
        flowContent,
        'li'
      ].join(' '));
      add('noscript', '', flowContent);
      if (type !== 'html4') {
        add('wbr');
        add('ruby', '', [
          phrasingContent,
          'rt rp'
        ].join(' '));
        add('figcaption', '', flowContent);
        add('mark rt rp bdi', '', phrasingContent);
        add('summary', '', [
          phrasingContent,
          'h1 h2 h3 h4 h5 h6'
        ].join(' '));
        add('canvas', 'width height', flowContent);
        add('video', 'src crossorigin poster preload autoplay mediagroup loop ' + 'muted controls width height buffered', [
          flowContent,
          'track source'
        ].join(' '));
        add('audio', 'src crossorigin preload autoplay mediagroup loop muted controls ' + 'buffered volume', [
          flowContent,
          'track source'
        ].join(' '));
        add('picture', '', 'img source');
        add('source', 'src srcset type media sizes');
        add('track', 'kind src srclang label default');
        add('datalist', '', [
          phrasingContent,
          'option'
        ].join(' '));
        add('article section nav aside main header footer', '', flowContent);
        add('hgroup', '', 'h1 h2 h3 h4 h5 h6');
        add('figure', '', [
          flowContent,
          'figcaption'
        ].join(' '));
        add('time', 'datetime', phrasingContent);
        add('dialog', 'open', flowContent);
        add('command', 'type label icon disabled checked radiogroup command');
        add('output', 'for form name', phrasingContent);
        add('progress', 'value max', phrasingContent);
        add('meter', 'value min max low high optimum', phrasingContent);
        add('details', 'open', [
          flowContent,
          'summary'
        ].join(' '));
        add('keygen', 'autofocus challenge disabled form keytype name');
        addElement('svg', 'id tabindex lang xml:space class style x y width height viewBox preserveAspectRatio zoomAndPan transform'.split(' '), []);
      }
      if (type !== 'html5-strict') {
        addAttrs('script', 'language xml:space');
        addAttrs('style', 'xml:space');
        addAttrs('object', 'declare classid code codebase codetype archive standby align border hspace vspace');
        addAttrs('embed', 'align name hspace vspace');
        addAttrs('param', 'valuetype type');
        addAttrs('a', 'charset name rev shape coords');
        addAttrs('br', 'clear');
        addAttrs('applet', 'codebase archive code object alt name width height align hspace vspace');
        addAttrs('img', 'name longdesc align border hspace vspace');
        addAttrs('iframe', 'longdesc frameborder marginwidth marginheight scrolling align');
        addAttrs('font basefont', 'size color face');
        addAttrs('input', 'usemap align');
        addAttrs('select');
        addAttrs('textarea');
        addAttrs('h1 h2 h3 h4 h5 h6 div p legend caption', 'align');
        addAttrs('ul', 'type compact');
        addAttrs('li', 'type');
        addAttrs('ol dl menu dir', 'compact');
        addAttrs('pre', 'width xml:space');
        addAttrs('hr', 'align noshade size width');
        addAttrs('isindex', 'prompt');
        addAttrs('table', 'summary width frame rules cellspacing cellpadding align bgcolor');
        addAttrs('col', 'width align char charoff valign');
        addAttrs('colgroup', 'width align char charoff valign');
        addAttrs('thead', 'align char charoff valign');
        addAttrs('tr', 'align char charoff valign bgcolor');
        addAttrs('th', 'axis align char charoff valign nowrap bgcolor width height');
        addAttrs('form', 'accept');
        addAttrs('td', 'abbr axis scope align char charoff valign nowrap bgcolor width height');
        addAttrs('tfoot', 'align char charoff valign');
        addAttrs('tbody', 'align char charoff valign');
        addAttrs('area', 'nohref');
        addAttrs('body', 'background bgcolor text link vlink alink');
      }
      if (type !== 'html4') {
        addAttrs('input button select textarea', 'autofocus');
        addAttrs('input textarea', 'placeholder');
        addAttrs('a', 'download');
        addAttrs('link script img', 'crossorigin');
        addAttrs('img', 'loading');
        addAttrs('iframe', 'sandbox seamless allow allowfullscreen loading');
      }
      if (type !== 'html4') {
        each$e([
          schema.video,
          schema.audio
        ], item => {
          delete item.children.audio;
          delete item.children.video;
        });
      }
      each$e(split$1('a form meter progress dfn'), name => {
        if (schema[name]) {
          delete schema[name].children[name];
        }
      });
      delete schema.caption.children.table;
      delete schema.script;
      return schema;
    };

    const prefixToOperation = prefix => prefix === '-' ? 'remove' : 'add';
    const parseValidChildrenRules = value => {
      const childRuleRegExp = /^([+\-]?)([A-Za-z0-9_\-.\u00b7\u00c0-\u00d6\u00d8-\u00f6\u00f8-\u037d\u037f-\u1fff\u200c-\u200d\u203f-\u2040\u2070-\u218f\u2c00-\u2fef\u3001-\ud7ff\uf900-\ufdcf\ufdf0-\ufffd]+)\[([^\]]+)]$/;
      return bind$3(split$1(value, ','), rule => {
        const matches = childRuleRegExp.exec(rule);
        if (matches) {
          const prefix = matches[1];
          const operation = prefix ? prefixToOperation(prefix) : 'replace';
          const name = matches[2];
          const validChildren = split$1(matches[3], '|');
          return [{
              operation,
              name,
              validChildren
            }];
        } else {
          return [];
        }
      });
    };

    const parseValidElementsAttrDataIntoElement = (attrData, targetElement) => {
      const attrRuleRegExp = /^([!\-])?(\w+[\\:]:\w+|[^=~<]+)?(?:([=~<])(.*))?$/;
      const hasPatternsRegExp = /[*?+]/;
      const {attributes, attributesOrder} = targetElement;
      return each$e(split$1(attrData, '|'), rule => {
        const matches = attrRuleRegExp.exec(rule);
        if (matches) {
          const attr = {};
          const attrType = matches[1];
          const attrName = matches[2].replace(/[\\:]:/g, ':');
          const attrPrefix = matches[3];
          const value = matches[4];
          if (attrType === '!') {
            targetElement.attributesRequired = targetElement.attributesRequired || [];
            targetElement.attributesRequired.push(attrName);
            attr.required = true;
          }
          if (attrType === '-') {
            delete attributes[attrName];
            attributesOrder.splice(Tools.inArray(attributesOrder, attrName), 1);
            return;
          }
          if (attrPrefix) {
            if (attrPrefix === '=') {
              targetElement.attributesDefault = targetElement.attributesDefault || [];
              targetElement.attributesDefault.push({
                name: attrName,
                value
              });
              attr.defaultValue = value;
            } else if (attrPrefix === '~') {
              targetElement.attributesForced = targetElement.attributesForced || [];
              targetElement.attributesForced.push({
                name: attrName,
                value
              });
              attr.forcedValue = value;
            } else if (attrPrefix === '<') {
              attr.validValues = Tools.makeMap(value, '?');
            }
          }
          if (hasPatternsRegExp.test(attrName)) {
            const attrPattern = attr;
            targetElement.attributePatterns = targetElement.attributePatterns || [];
            attrPattern.pattern = patternToRegExp(attrName);
            targetElement.attributePatterns.push(attrPattern);
          } else {
            if (!attributes[attrName]) {
              attributesOrder.push(attrName);
            }
            attributes[attrName] = attr;
          }
        }
      });
    };
    const cloneAttributesInto = (from, to) => {
      each$d(from.attributes, (value, key) => {
        to.attributes[key] = value;
      });
      to.attributesOrder.push(...from.attributesOrder);
    };
    const parseValidElementsRules = (globalElement, validElements) => {
      const elementRuleRegExp = /^([#+\-])?([^\[!\/]+)(?:\/([^\[!]+))?(?:(!?)\[([^\]]+)])?$/;
      return bind$3(split$1(validElements, ','), rule => {
        const matches = elementRuleRegExp.exec(rule);
        if (matches) {
          const prefix = matches[1];
          const elementName = matches[2];
          const outputName = matches[3];
          const attrsPrefix = matches[4];
          const attrData = matches[5];
          const element = {
            attributes: {},
            attributesOrder: []
          };
          globalElement.each(el => cloneAttributesInto(el, element));
          if (prefix === '#') {
            element.paddEmpty = true;
          } else if (prefix === '-') {
            element.removeEmpty = true;
          }
          if (attrsPrefix === '!') {
            element.removeEmptyAttrs = true;
          }
          if (attrData) {
            parseValidElementsAttrDataIntoElement(attrData, element);
          }
          if (outputName) {
            element.outputName = elementName;
          }
          if (elementName === '@') {
            if (globalElement.isNone()) {
              globalElement = Optional.some(element);
            } else {
              return [];
            }
          }
          return [outputName ? {
              name: elementName,
              element,
              aliasName: outputName
            } : {
              name: elementName,
              element
            }];
        } else {
          return [];
        }
      });
    };

    const mapCache = {};
    const makeMap$2 = Tools.makeMap, each$b = Tools.each, extend$2 = Tools.extend, explode$2 = Tools.explode;
    const createMap = (defaultValue, extendWith = {}) => {
      const value = makeMap$2(defaultValue, ' ', makeMap$2(defaultValue.toUpperCase(), ' '));
      return extend$2(value, extendWith);
    };
    const getTextRootBlockElements = schema => createMap('td th li dt dd figcaption caption details summary', schema.getTextBlockElements());
    const compileElementMap = (value, mode) => {
      if (value) {
        const styles = {};
        if (isString(value)) {
          value = { '*': value };
        }
        each$b(value, (value, key) => {
          styles[key] = styles[key.toUpperCase()] = mode === 'map' ? makeMap$2(value, /[, ]/) : explode$2(value, /[, ]/);
        });
        return styles;
      } else {
        return undefined;
      }
    };
    const Schema = (settings = {}) => {
      var _a;
      const elements = {};
      const children = {};
      let patternElements = [];
      const customElementsMap = {};
      const specialElements = {};
      const createLookupTable = (option, defaultValue, extendWith) => {
        const value = settings[option];
        if (!value) {
          let newValue = mapCache[option];
          if (!newValue) {
            newValue = createMap(defaultValue, extendWith);
            mapCache[option] = newValue;
          }
          return newValue;
        } else {
          return makeMap$2(value, /[, ]/, makeMap$2(value.toUpperCase(), /[, ]/));
        }
      };
      const schemaType = (_a = settings.schema) !== null && _a !== void 0 ? _a : 'html5';
      const schemaItems = makeSchema(schemaType);
      if (settings.verify_html === false) {
        settings.valid_elements = '*[*]';
      }
      const validStyles = compileElementMap(settings.valid_styles);
      const invalidStyles = compileElementMap(settings.invalid_styles, 'map');
      const validClasses = compileElementMap(settings.valid_classes, 'map');
      const whitespaceElementsMap = createLookupTable('whitespace_elements', 'pre script noscript style textarea video audio iframe object code');
      const selfClosingElementsMap = createLookupTable('self_closing_elements', 'colgroup dd dt li option p td tfoot th thead tr');
      const voidElementsMap = createLookupTable('void_elements', 'area base basefont br col frame hr img input isindex link ' + 'meta param embed source wbr track');
      const boolAttrMap = createLookupTable('boolean_attributes', 'checked compact declare defer disabled ismap multiple nohref noresize ' + 'noshade nowrap readonly selected autoplay loop controls allowfullscreen');
      const nonEmptyOrMoveCaretBeforeOnEnter = 'td th iframe video audio object script code';
      const nonEmptyElementsMap = createLookupTable('non_empty_elements', nonEmptyOrMoveCaretBeforeOnEnter + ' pre svg', voidElementsMap);
      const moveCaretBeforeOnEnterElementsMap = createLookupTable('move_caret_before_on_enter_elements', nonEmptyOrMoveCaretBeforeOnEnter + ' table', voidElementsMap);
      const headings = 'h1 h2 h3 h4 h5 h6';
      const textBlockElementsMap = createLookupTable('text_block_elements', headings + ' p div address pre form ' + 'blockquote center dir fieldset header footer article section hgroup aside main nav figure');
      const blockElementsMap = createLookupTable('block_elements', 'hr table tbody thead tfoot ' + 'th tr td li ol ul caption dl dt dd noscript menu isindex option ' + 'datalist select optgroup figcaption details summary html body multicol listing', textBlockElementsMap);
      const textInlineElementsMap = createLookupTable('text_inline_elements', 'span strong b em i font s strike u var cite ' + 'dfn code mark q sup sub samp');
      const transparentElementsMap = createLookupTable('transparent_elements', 'a ins del canvas map');
      const wrapBlockElementsMap = createLookupTable('wrap_block_elements', 'pre ' + headings);
      each$b('script noscript iframe noframes noembed title style textarea xmp plaintext'.split(' '), name => {
        specialElements[name] = new RegExp('</' + name + '[^>]*>', 'gi');
      });
      const addValidElements = validElements => {
        const globalElement = Optional.from(elements['@']);
        const hasPatternsRegExp = /[*?+]/;
        each$e(parseValidElementsRules(globalElement, validElements !== null && validElements !== void 0 ? validElements : ''), ({name, element, aliasName}) => {
          if (aliasName) {
            elements[aliasName] = element;
          }
          if (hasPatternsRegExp.test(name)) {
            const patternElement = element;
            patternElement.pattern = patternToRegExp(name);
            patternElements.push(patternElement);
          } else {
            elements[name] = element;
          }
        });
      };
      const setValidElements = validElements => {
        patternElements = [];
        each$e(keys(elements), name => {
          delete elements[name];
        });
        addValidElements(validElements);
      };
      const addCustomElements = customElements => {
        delete mapCache.text_block_elements;
        delete mapCache.block_elements;
        each$e(parseCustomElementsRules(customElements !== null && customElements !== void 0 ? customElements : ''), ({inline, name, cloneName}) => {
          children[name] = children[cloneName];
          customElementsMap[name] = cloneName;
          nonEmptyElementsMap[name.toUpperCase()] = {};
          nonEmptyElementsMap[name] = {};
          if (!inline) {
            blockElementsMap[name.toUpperCase()] = {};
            blockElementsMap[name] = {};
          }
          if (!elements[name]) {
            let customRule = elements[cloneName];
            customRule = extend$2({}, customRule);
            delete customRule.removeEmptyAttrs;
            delete customRule.removeEmpty;
            elements[name] = customRule;
          }
          each$d(children, (element, elmName) => {
            if (element[cloneName]) {
              children[elmName] = element = extend$2({}, children[elmName]);
              element[name] = element[cloneName];
            }
          });
        });
      };
      const addValidChildren = validChildren => {
        each$e(parseValidChildrenRules(validChildren !== null && validChildren !== void 0 ? validChildren : ''), ({operation, name, validChildren}) => {
          const parent = operation === 'replace' ? { '#comment': {} } : children[name];
          each$e(validChildren, child => {
            if (operation === 'remove') {
              delete parent[child];
            } else {
              parent[child] = {};
            }
          });
          children[name] = parent;
        });
      };
      const getElementRule = name => {
        const element = elements[name];
        if (element) {
          return element;
        }
        let i = patternElements.length;
        while (i--) {
          const patternElement = patternElements[i];
          if (patternElement.pattern.test(name)) {
            return patternElement;
          }
        }
        return undefined;
      };
      if (!settings.valid_elements) {
        each$b(schemaItems, (element, name) => {
          elements[name] = {
            attributes: element.attributes,
            attributesOrder: element.attributesOrder
          };
          children[name] = element.children;
        });
        each$b(split$1('strong/b em/i'), item => {
          const items = split$1(item, '/');
          elements[items[1]].outputName = items[0];
        });
        each$b(textInlineElementsMap, (_val, name) => {
          if (elements[name]) {
            if (settings.padd_empty_block_inline_children) {
              elements[name].paddInEmptyBlock = true;
            }
            elements[name].removeEmpty = true;
          }
        });
        each$b(split$1('ol ul blockquote a table tbody'), name => {
          if (elements[name]) {
            elements[name].removeEmpty = true;
          }
        });
        each$b(split$1('p h1 h2 h3 h4 h5 h6 th td pre div address caption li summary'), name => {
          if (elements[name]) {
            elements[name].paddEmpty = true;
          }
        });
        each$b(split$1('span'), name => {
          elements[name].removeEmptyAttrs = true;
        });
      } else {
        setValidElements(settings.valid_elements);
        each$b(schemaItems, (element, name) => {
          children[name] = element.children;
        });
      }
      delete elements.svg;
      addCustomElements(settings.custom_elements);
      addValidChildren(settings.valid_children);
      addValidElements(settings.extended_valid_elements);
      addValidChildren('+ol[ul|ol],+ul[ul|ol]');
      each$b({
        dd: 'dl',
        dt: 'dl',
        li: 'ul ol',
        td: 'tr',
        th: 'tr',
        tr: 'tbody thead tfoot',
        tbody: 'table',
        thead: 'table',
        tfoot: 'table',
        legend: 'fieldset',
        area: 'map',
        param: 'video audio object'
      }, (parents, item) => {
        if (elements[item]) {
          elements[item].parentsRequired = split$1(parents);
        }
      });
      if (settings.invalid_elements) {
        each$b(explode$2(settings.invalid_elements), item => {
          if (elements[item]) {
            delete elements[item];
          }
        });
      }
      if (!getElementRule('span')) {
        addValidElements('span[!data-mce-type|*]');
      }
      const getValidStyles = constant(validStyles);
      const getInvalidStyles = constant(invalidStyles);
      const getValidClasses = constant(validClasses);
      const getBoolAttrs = constant(boolAttrMap);
      const getBlockElements = constant(blockElementsMap);
      const getTextBlockElements = constant(textBlockElementsMap);
      const getTextInlineElements = constant(textInlineElementsMap);
      const getVoidElements = constant(Object.seal(voidElementsMap));
      const getSelfClosingElements = constant(selfClosingElementsMap);
      const getNonEmptyElements = constant(nonEmptyElementsMap);
      const getMoveCaretBeforeOnEnterElements = constant(moveCaretBeforeOnEnterElementsMap);
      const getWhitespaceElements = constant(whitespaceElementsMap);
      const getTransparentElements = constant(transparentElementsMap);
      const getWrapBlockElements = constant(wrapBlockElementsMap);
      const getSpecialElements = constant(Object.seal(specialElements));
      const isValidChild = (name, child) => {
        const parent = children[name.toLowerCase()];
        return !!(parent && parent[child.toLowerCase()]);
      };
      const isValid = (name, attr) => {
        const rule = getElementRule(name);
        if (rule) {
          if (attr) {
            if (rule.attributes[attr]) {
              return true;
            }
            const attrPatterns = rule.attributePatterns;
            if (attrPatterns) {
              let i = attrPatterns.length;
              while (i--) {
                if (attrPatterns[i].pattern.test(attr)) {
                  return true;
                }
              }
            }
          } else {
            return true;
          }
        }
        return false;
      };
      const isBlock = name => has$2(getBlockElements(), name);
      const isInline = name => !startsWith(name, '#') && isValid(name) && !isBlock(name);
      const isWrapper = name => has$2(getWrapBlockElements(), name) || isInline(name);
      const getCustomElements = constant(customElementsMap);
      return {
        type: schemaType,
        children,
        elements,
        getValidStyles,
        getValidClasses,
        getBlockElements,
        getInvalidStyles,
        getVoidElements,
        getTextBlockElements,
        getTextInlineElements,
        getBoolAttrs,
        getElementRule,
        getSelfClosingElements,
        getNonEmptyElements,
        getMoveCaretBeforeOnEnterElements,
        getWhitespaceElements,
        getTransparentElements,
        getSpecialElements,
        isValidChild,
        isValid,
        isBlock,
        isInline,
        isWrapper,
        getCustomElements,
        addValidElements,
        setValidElements,
        addCustomElements,
        addValidChildren
      };
    };

    const hexColour = value => ({ value: normalizeHex(value) });
    const normalizeHex = hex => removeLeading(hex, '#').toUpperCase();
    const toHex = component => {
      const hex = component.toString(16);
      return (hex.length === 1 ? '0' + hex : hex).toUpperCase();
    };
    const fromRgba = rgbaColour => {
      const value = toHex(rgbaColour.red) + toHex(rgbaColour.green) + toHex(rgbaColour.blue);
      return hexColour(value);
    };

    const rgbRegex = /^\s*rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)\s*$/i;
    const rgbaRegex = /^\s*rgba\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d?(?:\.\d+)?)\s*\)\s*$/i;
    const rgbaColour = (red, green, blue, alpha) => ({
      red,
      green,
      blue,
      alpha
    });
    const fromStringValues = (red, green, blue, alpha) => {
      const r = parseInt(red, 10);
      const g = parseInt(green, 10);
      const b = parseInt(blue, 10);
      const a = parseFloat(alpha);
      return rgbaColour(r, g, b, a);
    };
    const fromString = rgbaString => {
      if (rgbaString === 'transparent') {
        return Optional.some(rgbaColour(0, 0, 0, 0));
      }
      const rgbMatch = rgbRegex.exec(rgbaString);
      if (rgbMatch !== null) {
        return Optional.some(fromStringValues(rgbMatch[1], rgbMatch[2], rgbMatch[3], '1'));
      }
      const rgbaMatch = rgbaRegex.exec(rgbaString);
      if (rgbaMatch !== null) {
        return Optional.some(fromStringValues(rgbaMatch[1], rgbaMatch[2], rgbaMatch[3], rgbaMatch[4]));
      }
      return Optional.none();
    };
    const toString = rgba => `rgba(${ rgba.red },${ rgba.green },${ rgba.blue },${ rgba.alpha })`;

    const rgbaToHexString = color => fromString(color).map(fromRgba).map(h => '#' + h.value).getOr(color);

    const Styles = (settings = {}, schema) => {
      const urlOrStrRegExp = /(?:url(?:(?:\(\s*\"([^\"]+)\"\s*\))|(?:\(\s*\'([^\']+)\'\s*\))|(?:\(\s*([^)\s]+)\s*\))))|(?:\'([^\']+)\')|(?:\"([^\"]+)\")/gi;
      const styleRegExp = /\s*([^:]+):\s*([^;]+);?/g;
      const trimRightRegExp = /\s+$/;
      const encodingLookup = {};
      let validStyles;
      let invalidStyles;
      const invisibleChar = zeroWidth;
      if (schema) {
        validStyles = schema.getValidStyles();
        invalidStyles = schema.getInvalidStyles();
      }
      const encodingItems = (`\\" \\' \\; \\: ; : ` + invisibleChar).split(' ');
      for (let i = 0; i < encodingItems.length; i++) {
        encodingLookup[encodingItems[i]] = invisibleChar + i;
        encodingLookup[invisibleChar + i] = encodingItems[i];
      }
      const self = {
        parse: css => {
          const styles = {};
          let isEncoded = false;
          const urlConverter = settings.url_converter;
          const urlConverterScope = settings.url_converter_scope || self;
          const compress = (prefix, suffix, noJoin) => {
            const top = styles[prefix + '-top' + suffix];
            if (!top) {
              return;
            }
            const right = styles[prefix + '-right' + suffix];
            if (!right) {
              return;
            }
            const bottom = styles[prefix + '-bottom' + suffix];
            if (!bottom) {
              return;
            }
            const left = styles[prefix + '-left' + suffix];
            if (!left) {
              return;
            }
            const box = [
              top,
              right,
              bottom,
              left
            ];
            let i = box.length - 1;
            while (i--) {
              if (box[i] !== box[i + 1]) {
                break;
              }
            }
            if (i > -1 && noJoin) {
              return;
            }
            styles[prefix + suffix] = i === -1 ? box[0] : box.join(' ');
            delete styles[prefix + '-top' + suffix];
            delete styles[prefix + '-right' + suffix];
            delete styles[prefix + '-bottom' + suffix];
            delete styles[prefix + '-left' + suffix];
          };
          const canCompress = key => {
            const value = styles[key];
            if (!value) {
              return;
            }
            const values = value.indexOf(',') > -1 ? [value] : value.split(' ');
            let i = values.length;
            while (i--) {
              if (values[i] !== values[0]) {
                return false;
              }
            }
            styles[key] = values[0];
            return true;
          };
          const compress2 = (target, a, b, c) => {
            if (!canCompress(a)) {
              return;
            }
            if (!canCompress(b)) {
              return;
            }
            if (!canCompress(c)) {
              return;
            }
            styles[target] = styles[a] + ' ' + styles[b] + ' ' + styles[c];
            delete styles[a];
            delete styles[b];
            delete styles[c];
          };
          const encode = str => {
            isEncoded = true;
            return encodingLookup[str];
          };
          const decode = (str, keepSlashes) => {
            if (isEncoded) {
              str = str.replace(/\uFEFF[0-9]/g, str => {
                return encodingLookup[str];
              });
            }
            if (!keepSlashes) {
              str = str.replace(/\\([\'\";:])/g, '$1');
            }
            return str;
          };
          const decodeSingleHexSequence = escSeq => {
            return String.fromCharCode(parseInt(escSeq.slice(1), 16));
          };
          const decodeHexSequences = value => {
            return value.replace(/\\[0-9a-f]+/gi, decodeSingleHexSequence);
          };
          const processUrl = (match, url, url2, url3, str, str2) => {
            str = str || str2;
            if (str) {
              str = decode(str);
              return `'` + str.replace(/\'/g, `\\'`) + `'`;
            }
            url = decode(url || url2 || url3 || '');
            if (!settings.allow_script_urls) {
              const scriptUrl = url.replace(/[\s\r\n]+/g, '');
              if (/(java|vb)script:/i.test(scriptUrl)) {
                return '';
              }
              if (!settings.allow_svg_data_urls && /^data:image\/svg/i.test(scriptUrl)) {
                return '';
              }
            }
            if (urlConverter) {
              url = urlConverter.call(urlConverterScope, url, 'style');
            }
            return `url('` + url.replace(/\'/g, `\\'`) + `')`;
          };
          if (css) {
            css = css.replace(/[\u0000-\u001F]/g, '');
            css = css.replace(/\\[\"\';:\uFEFF]/g, encode).replace(/\"[^\"]+\"|\'[^\']+\'/g, str => {
              return str.replace(/[;:]/g, encode);
            });
            let matches;
            while (matches = styleRegExp.exec(css)) {
              styleRegExp.lastIndex = matches.index + matches[0].length;
              let name = matches[1].replace(trimRightRegExp, '').toLowerCase();
              let value = matches[2].replace(trimRightRegExp, '');
              if (name && value) {
                name = decodeHexSequences(name);
                value = decodeHexSequences(value);
                if (name.indexOf(invisibleChar) !== -1 || name.indexOf('"') !== -1) {
                  continue;
                }
                if (!settings.allow_script_urls && (name === 'behavior' || /expression\s*\(|\/\*|\*\//.test(value))) {
                  continue;
                }
                if (name === 'font-weight' && value === '700') {
                  value = 'bold';
                } else if (name === 'color' || name === 'background-color') {
                  value = value.toLowerCase();
                }
                if (isString(settings.force_hex_color) && settings.force_hex_color !== 'off') {
                  fromString(value).each(rgba => {
                    if (settings.force_hex_color === 'always' || rgba.alpha === 1) {
                      value = rgbaToHexString(toString(rgba));
                    }
                  });
                }
                value = value.replace(urlOrStrRegExp, processUrl);
                styles[name] = isEncoded ? decode(value, true) : value;
              }
            }
            compress('border', '', true);
            compress('border', '-width');
            compress('border', '-color');
            compress('border', '-style');
            compress('padding', '');
            compress('margin', '');
            compress2('border', 'border-width', 'border-style', 'border-color');
            if (styles.border === 'medium none') {
              delete styles.border;
            }
            if (styles['border-image'] === 'none') {
              delete styles['border-image'];
            }
          }
          return styles;
        },
        serialize: (styles, elementName) => {
          let css = '';
          const serializeStyles = (elemName, validStyleList) => {
            const styleList = validStyleList[elemName];
            if (styleList) {
              for (let i = 0, l = styleList.length; i < l; i++) {
                const name = styleList[i];
                const value = styles[name];
                if (value) {
                  css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
                }
              }
            }
          };
          const isValid = (name, elemName) => {
            if (!invalidStyles || !elemName) {
              return true;
            }
            let styleMap = invalidStyles['*'];
            if (styleMap && styleMap[name]) {
              return false;
            }
            styleMap = invalidStyles[elemName];
            return !(styleMap && styleMap[name]);
          };
          if (elementName && validStyles) {
            serializeStyles('*', validStyles);
            serializeStyles(elementName, validStyles);
          } else {
            each$d(styles, (value, name) => {
              if (value && isValid(name, elementName)) {
                css += (css.length > 0 ? ' ' : '') + name + ': ' + value + ';';
              }
            });
          }
          return css;
        }
      };
      return self;
    };

    const deprecated = {
      keyLocation: true,
      layerX: true,
      layerY: true,
      returnValue: true,
      webkitMovementX: true,
      webkitMovementY: true,
      keyIdentifier: true,
      mozPressure: true
    };
    const isNativeEvent = event => event instanceof Event || isFunction(event.initEvent);
    const hasIsDefaultPrevented = event => event.isDefaultPrevented === always || event.isDefaultPrevented === never;
    const needsNormalizing = event => isNullable(event.preventDefault) || isNativeEvent(event);
    const clone$3 = (originalEvent, data) => {
      const event = data !== null && data !== void 0 ? data : {};
      for (const name in originalEvent) {
        if (!has$2(deprecated, name)) {
          event[name] = originalEvent[name];
        }
      }
      if (isNonNullable(originalEvent.composedPath)) {
        event.composedPath = () => originalEvent.composedPath();
      }
      if (isNonNullable(originalEvent.getModifierState)) {
        event.getModifierState = keyArg => originalEvent.getModifierState(keyArg);
      }
      if (isNonNullable(originalEvent.getTargetRanges)) {
        event.getTargetRanges = () => originalEvent.getTargetRanges();
      }
      return event;
    };
    const normalize$3 = (type, originalEvent, fallbackTarget, data) => {
      var _a;
      const event = clone$3(originalEvent, data);
      event.type = type;
      if (isNullable(event.target)) {
        event.target = (_a = event.srcElement) !== null && _a !== void 0 ? _a : fallbackTarget;
      }
      if (needsNormalizing(originalEvent)) {
        event.preventDefault = () => {
          event.defaultPrevented = true;
          event.isDefaultPrevented = always;
          if (isFunction(originalEvent.preventDefault)) {
            originalEvent.preventDefault();
          }
        };
        event.stopPropagation = () => {
          event.cancelBubble = true;
          event.isPropagationStopped = always;
          if (isFunction(originalEvent.stopPropagation)) {
            originalEvent.stopPropagation();
          }
        };
        event.stopImmediatePropagation = () => {
          event.isImmediatePropagationStopped = always;
          event.stopPropagation();
        };
        if (!hasIsDefaultPrevented(event)) {
          event.isDefaultPrevented = event.defaultPrevented === true ? always : never;
          event.isPropagationStopped = event.cancelBubble === true ? always : never;
          event.isImmediatePropagationStopped = never;
        }
      }
      return event;
    };

    const eventExpandoPrefix = 'mce-data-';
    const mouseEventRe = /^(?:mouse|contextmenu)|click/;
    const addEvent = (target, name, callback, capture) => {
      target.addEventListener(name, callback, capture || false);
    };
    const removeEvent = (target, name, callback, capture) => {
      target.removeEventListener(name, callback, capture || false);
    };
    const isMouseEvent = event => isNonNullable(event) && mouseEventRe.test(event.type);
    const fix = (originalEvent, data) => {
      const event = normalize$3(originalEvent.type, originalEvent, document, data);
      if (isMouseEvent(originalEvent) && isUndefined(originalEvent.pageX) && !isUndefined(originalEvent.clientX)) {
        const eventDoc = event.target.ownerDocument || document;
        const doc = eventDoc.documentElement;
        const body = eventDoc.body;
        const mouseEvent = event;
        mouseEvent.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0);
        mouseEvent.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0);
      }
      return event;
    };
    const bindOnReady = (win, callback, eventUtils) => {
      const doc = win.document, event = { type: 'ready' };
      if (eventUtils.domLoaded) {
        callback(event);
        return;
      }
      const isDocReady = () => {
        return doc.readyState === 'complete' || doc.readyState === 'interactive' && doc.body;
      };
      const readyHandler = () => {
        removeEvent(win, 'DOMContentLoaded', readyHandler);
        removeEvent(win, 'load', readyHandler);
        if (!eventUtils.domLoaded) {
          eventUtils.domLoaded = true;
          callback(event);
        }
        win = null;
      };
      if (isDocReady()) {
        readyHandler();
      } else {
        addEvent(win, 'DOMContentLoaded', readyHandler);
      }
      if (!eventUtils.domLoaded) {
        addEvent(win, 'load', readyHandler);
      }
    };
    class EventUtils {
      constructor() {
        this.domLoaded = false;
        this.events = {};
        this.count = 1;
        this.expando = eventExpandoPrefix + (+new Date()).toString(32);
        this.hasFocusIn = 'onfocusin' in document.documentElement;
        this.count = 1;
      }
      bind(target, names, callback, scope) {
        const self = this;
        let callbackList;
        const win = window;
        const defaultNativeHandler = evt => {
          self.executeHandlers(fix(evt || win.event), id);
        };
        if (!target || isText$a(target) || isComment(target)) {
          return callback;
        }
        let id;
        if (!target[self.expando]) {
          id = self.count++;
          target[self.expando] = id;
          self.events[id] = {};
        } else {
          id = target[self.expando];
        }
        scope = scope || target;
        const namesList = names.split(' ');
        let i = namesList.length;
        while (i--) {
          let name = namesList[i];
          let nativeHandler = defaultNativeHandler;
          let capture = false;
          let fakeName = false;
          if (name === 'DOMContentLoaded') {
            name = 'ready';
          }
          if (self.domLoaded && name === 'ready' && target.readyState === 'complete') {
            callback.call(scope, fix({ type: name }));
            continue;
          }
          if (!self.hasFocusIn && (name === 'focusin' || name === 'focusout')) {
            capture = true;
            fakeName = name === 'focusin' ? 'focus' : 'blur';
            nativeHandler = evt => {
              const event = fix(evt || win.event);
              event.type = event.type === 'focus' ? 'focusin' : 'focusout';
              self.executeHandlers(event, id);
            };
          }
          callbackList = self.events[id][name];
          if (!callbackList) {
            self.events[id][name] = callbackList = [{
                func: callback,
                scope
              }];
            callbackList.fakeName = fakeName;
            callbackList.capture = capture;
            callbackList.nativeHandler = nativeHandler;
            if (name === 'ready') {
              bindOnReady(target, nativeHandler, self);
            } else {
              addEvent(target, fakeName || name, nativeHandler, capture);
            }
          } else {
            if (name === 'ready' && self.domLoaded) {
              callback(fix({ type: name }));
            } else {
              callbackList.push({
                func: callback,
                scope
              });
            }
          }
        }
        target = callbackList = null;
        return callback;
      }
      unbind(target, names, callback) {
        if (!target || isText$a(target) || isComment(target)) {
          return this;
        }
        const id = target[this.expando];
        if (id) {
          let eventMap = this.events[id];
          if (names) {
            const namesList = names.split(' ');
            let i = namesList.length;
            while (i--) {
              const name = namesList[i];
              const callbackList = eventMap[name];
              if (callbackList) {
                if (callback) {
                  let ci = callbackList.length;
                  while (ci--) {
                    if (callbackList[ci].func === callback) {
                      const nativeHandler = callbackList.nativeHandler;
                      const fakeName = callbackList.fakeName, capture = callbackList.capture;
                      const newCallbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1));
                      newCallbackList.nativeHandler = nativeHandler;
                      newCallbackList.fakeName = fakeName;
                      newCallbackList.capture = capture;
                      eventMap[name] = newCallbackList;
                    }
                  }
                }
                if (!callback || callbackList.length === 0) {
                  delete eventMap[name];
                  removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
                }
              }
            }
          } else {
            each$d(eventMap, (callbackList, name) => {
              removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
            });
            eventMap = {};
          }
          for (const name in eventMap) {
            if (has$2(eventMap, name)) {
              return this;
            }
          }
          delete this.events[id];
          try {
            delete target[this.expando];
          } catch (ex) {
            target[this.expando] = null;
          }
        }
        return this;
      }
      fire(target, name, args) {
        return this.dispatch(target, name, args);
      }
      dispatch(target, name, args) {
        if (!target || isText$a(target) || isComment(target)) {
          return this;
        }
        const event = fix({
          type: name,
          target
        }, args);
        do {
          const id = target[this.expando];
          if (id) {
            this.executeHandlers(event, id);
          }
          target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
        } while (target && !event.isPropagationStopped());
        return this;
      }
      clean(target) {
        if (!target || isText$a(target) || isComment(target)) {
          return this;
        }
        if (target[this.expando]) {
          this.unbind(target);
        }
        if (!target.getElementsByTagName) {
          target = target.document;
        }
        if (target && target.getElementsByTagName) {
          this.unbind(target);
          const children = target.getElementsByTagName('*');
          let i = children.length;
          while (i--) {
            target = children[i];
            if (target[this.expando]) {
              this.unbind(target);
            }
          }
        }
        return this;
      }
      destroy() {
        this.events = {};
      }
      cancel(e) {
        if (e) {
          e.preventDefault();
          e.stopImmediatePropagation();
        }
        return false;
      }
      executeHandlers(evt, id) {
        const container = this.events[id];
        const callbackList = container && container[evt.type];
        if (callbackList) {
          for (let i = 0, l = callbackList.length; i < l; i++) {
            const callback = callbackList[i];
            if (callback && callback.func.call(callback.scope, evt) === false) {
              evt.preventDefault();
            }
            if (evt.isImmediatePropagationStopped()) {
              return;
            }
          }
        }
      }
    }
    EventUtils.Event = new EventUtils();

    const each$a = Tools.each;
    const grep = Tools.grep;
    const internalStyleName = 'data-mce-style';
    const numericalCssMap = Tools.makeMap('fill-opacity font-weight line-height opacity orphans widows z-index zoom', ' ');
    const legacySetAttribute = (elm, name, value) => {
      if (isNullable(value) || value === '') {
        remove$a(elm, name);
      } else {
        set$3(elm, name, value);
      }
    };
    const camelCaseToHyphens = name => name.replace(/[A-Z]/g, v => '-' + v.toLowerCase());
    const findNodeIndex = (node, normalized) => {
      let idx = 0;
      if (node) {
        for (let lastNodeType = node.nodeType, tempNode = node.previousSibling; tempNode; tempNode = tempNode.previousSibling) {
          const nodeType = tempNode.nodeType;
          if (normalized && isText$a(tempNode)) {
            if (nodeType === lastNodeType || !tempNode.data.length) {
              continue;
            }
          }
          idx++;
          lastNodeType = nodeType;
        }
      }
      return idx;
    };
    const updateInternalStyleAttr = (styles, elm) => {
      const rawValue = get$9(elm, 'style');
      const value = styles.serialize(styles.parse(rawValue), name(elm));
      legacySetAttribute(elm, internalStyleName, value);
    };
    const convertStyleToString = (cssValue, cssName) => {
      if (isNumber(cssValue)) {
        return has$2(numericalCssMap, cssName) ? cssValue + '' : cssValue + 'px';
      } else {
        return cssValue;
      }
    };
    const applyStyle$1 = ($elm, cssName, cssValue) => {
      const normalizedName = camelCaseToHyphens(cssName);
      if (isNullable(cssValue) || cssValue === '') {
        remove$6($elm, normalizedName);
      } else {
        set$2($elm, normalizedName, convertStyleToString(cssValue, normalizedName));
      }
    };
    const setupAttrHooks = (styles, settings, getContext) => {
      const keepValues = settings.keep_values;
      const keepUrlHook = {
        set: (elm, value, name) => {
          const sugarElm = SugarElement.fromDom(elm);
          if (isFunction(settings.url_converter) && isNonNullable(value)) {
            value = settings.url_converter.call(settings.url_converter_scope || getContext(), String(value), name, elm);
          }
          const internalName = 'data-mce-' + name;
          legacySetAttribute(sugarElm, internalName, value);
          legacySetAttribute(sugarElm, name, value);
        },
        get: (elm, name) => {
          const sugarElm = SugarElement.fromDom(elm);
          return get$9(sugarElm, 'data-mce-' + name) || get$9(sugarElm, name);
        }
      };
      const attrHooks = {
        style: {
          set: (elm, value) => {
            const sugarElm = SugarElement.fromDom(elm);
            if (keepValues) {
              legacySetAttribute(sugarElm, internalStyleName, value);
            }
            remove$a(sugarElm, 'style');
            if (isString(value)) {
              setAll(sugarElm, styles.parse(value));
            }
          },
          get: elm => {
            const sugarElm = SugarElement.fromDom(elm);
            const value = get$9(sugarElm, internalStyleName) || get$9(sugarElm, 'style');
            return styles.serialize(styles.parse(value), name(sugarElm));
          }
        }
      };
      if (keepValues) {
        attrHooks.href = attrHooks.src = keepUrlHook;
      }
      return attrHooks;
    };
    const DOMUtils = (doc, settings = {}) => {
      const addedStyles = {};
      const win = window;
      const files = {};
      let counter = 0;
      const stdMode = true;
      const boxModel = true;
      const styleSheetLoader = instance.forElement(SugarElement.fromDom(doc), {
        contentCssCors: settings.contentCssCors,
        referrerPolicy: settings.referrerPolicy
      });
      const boundEvents = [];
      const schema = settings.schema ? settings.schema : Schema({});
      const styles = Styles({
        url_converter: settings.url_converter,
        url_converter_scope: settings.url_converter_scope,
        force_hex_color: settings.force_hex_color
      }, settings.schema);
      const events = settings.ownEvents ? new EventUtils() : EventUtils.Event;
      const blockElementsMap = schema.getBlockElements();
      const isBlock = node => {
        if (isString(node)) {
          return has$2(blockElementsMap, node);
        } else {
          return isElement$6(node) && (has$2(blockElementsMap, node.nodeName) || isTransparentBlock(schema, node));
        }
      };
      const get = elm => elm && doc && isString(elm) ? doc.getElementById(elm) : elm;
      const _get = elm => {
        const value = get(elm);
        return isNonNullable(value) ? SugarElement.fromDom(value) : null;
      };
      const getAttrib = (elm, name, defaultVal = '') => {
        let value;
        const $elm = _get(elm);
        if (isNonNullable($elm) && isElement$7($elm)) {
          const hook = attrHooks[name];
          if (hook && hook.get) {
            value = hook.get($elm.dom, name);
          } else {
            value = get$9($elm, name);
          }
        }
        return isNonNullable(value) ? value : defaultVal;
      };
      const getAttribs = elm => {
        const node = get(elm);
        return isNullable(node) ? [] : node.attributes;
      };
      const setAttrib = (elm, name, value) => {
        run(elm, e => {
          if (isElement$6(e)) {
            const $elm = SugarElement.fromDom(e);
            const val = value === '' ? null : value;
            const originalValue = get$9($elm, name);
            const hook = attrHooks[name];
            if (hook && hook.set) {
              hook.set($elm.dom, val, name);
            } else {
              legacySetAttribute($elm, name, val);
            }
            if (originalValue !== val && settings.onSetAttrib) {
              settings.onSetAttrib({
                attrElm: $elm.dom,
                attrName: name,
                attrValue: val
              });
            }
          }
        });
      };
      const clone = (node, deep) => {
        return node.cloneNode(deep);
      };
      const getRoot = () => settings.root_element || doc.body;
      const getViewPort = argWin => {
        const vp = getBounds(argWin);
        return {
          x: vp.x,
          y: vp.y,
          w: vp.width,
          h: vp.height
        };
      };
      const getPos$1 = (elm, rootElm) => getPos(doc.body, get(elm), rootElm);
      const setStyle = (elm, name, value) => {
        run(elm, e => {
          const $elm = SugarElement.fromDom(e);
          applyStyle$1($elm, name, value);
          if (settings.update_styles) {
            updateInternalStyleAttr(styles, $elm);
          }
        });
      };
      const setStyles = (elm, stylesArg) => {
        run(elm, e => {
          const $elm = SugarElement.fromDom(e);
          each$d(stylesArg, (v, n) => {
            applyStyle$1($elm, n, v);
          });
          if (settings.update_styles) {
            updateInternalStyleAttr(styles, $elm);
          }
        });
      };
      const getStyle = (elm, name, computed) => {
        const $elm = get(elm);
        if (isNullable($elm) || !isHTMLElement($elm) && !isSVGElement($elm)) {
          return undefined;
        }
        if (computed) {
          return get$7(SugarElement.fromDom($elm), camelCaseToHyphens(name));
        } else {
          name = name.replace(/-(\D)/g, (a, b) => b.toUpperCase());
          if (name === 'float') {
            name = 'cssFloat';
          }
          return $elm.style ? $elm.style[name] : undefined;
        }
      };
      const getSize = elm => {
        const $elm = get(elm);
        if (!$elm) {
          return {
            w: 0,
            h: 0
          };
        }
        let w = getStyle($elm, 'width');
        let h = getStyle($elm, 'height');
        if (!w || w.indexOf('px') === -1) {
          w = '0';
        }
        if (!h || h.indexOf('px') === -1) {
          h = '0';
        }
        return {
          w: parseInt(w, 10) || $elm.offsetWidth || $elm.clientWidth,
          h: parseInt(h, 10) || $elm.offsetHeight || $elm.clientHeight
        };
      };
      const getRect = elm => {
        const $elm = get(elm);
        const pos = getPos$1($elm);
        const size = getSize($elm);
        return {
          x: pos.x,
          y: pos.y,
          w: size.w,
          h: size.h
        };
      };
      const is = (elm, selector) => {
        if (!elm) {
          return false;
        }
        const elms = isArray$1(elm) ? elm : [elm];
        return exists(elms, e => {
          return is$1(SugarElement.fromDom(e), selector);
        });
      };
      const getParents = (elm, selector, root, collect) => {
        const result = [];
        let node = get(elm);
        collect = collect === undefined;
        const resolvedRoot = root || (getRoot().nodeName !== 'BODY' ? getRoot().parentNode : null);
        if (isString(selector)) {
          if (selector === '*') {
            selector = isElement$6;
          } else {
            const selectorVal = selector;
            selector = node => is(node, selectorVal);
          }
        }
        while (node) {
          if (node === resolvedRoot || isNullable(node.nodeType) || isDocument$1(node) || isDocumentFragment(node)) {
            break;
          }
          if (!selector || selector(node)) {
            if (collect) {
              result.push(node);
            } else {
              return [node];
            }
          }
          node = node.parentNode;
        }
        return collect ? result : null;
      };
      const getParent = (node, selector, root) => {
        const parents = getParents(node, selector, root, false);
        return parents && parents.length > 0 ? parents[0] : null;
      };
      const _findSib = (node, selector, name) => {
        let func = selector;
        if (node) {
          if (isString(selector)) {
            func = node => {
              return is(node, selector);
            };
          }
          for (let tempNode = node[name]; tempNode; tempNode = tempNode[name]) {
            if (isFunction(func) && func(tempNode)) {
              return tempNode;
            }
          }
        }
        return null;
      };
      const getNext = (node, selector) => _findSib(node, selector, 'nextSibling');
      const getPrev = (node, selector) => _findSib(node, selector, 'previousSibling');
      const isParentNode = node => isFunction(node.querySelectorAll);
      const select = (selector, scope) => {
        var _a, _b;
        const elm = (_b = (_a = get(scope)) !== null && _a !== void 0 ? _a : settings.root_element) !== null && _b !== void 0 ? _b : doc;
        return isParentNode(elm) ? from(elm.querySelectorAll(selector)) : [];
      };
      const run = function (elm, func, scope) {
        const context = scope !== null && scope !== void 0 ? scope : this;
        if (isArray$1(elm)) {
          const result = [];
          each$a(elm, (e, i) => {
            const node = get(e);
            if (node) {
              result.push(func.call(context, node, i));
            }
          });
          return result;
        } else {
          const node = get(elm);
          return !node ? false : func.call(context, node);
        }
      };
      const setAttribs = (elm, attrs) => {
        run(elm, $elm => {
          each$d(attrs, (value, name) => {
            setAttrib($elm, name, value);
          });
        });
      };
      const setHTML = (elm, html) => {
        run(elm, e => {
          const $elm = SugarElement.fromDom(e);
          set$1($elm, html);
        });
      };
      const add = (parentElm, name, attrs, html, create) => run(parentElm, parentElm => {
        const newElm = isString(name) ? doc.createElement(name) : name;
        if (isNonNullable(attrs)) {
          setAttribs(newElm, attrs);
        }
        if (html) {
          if (!isString(html) && html.nodeType) {
            newElm.appendChild(html);
          } else if (isString(html)) {
            setHTML(newElm, html);
          }
        }
        return !create ? parentElm.appendChild(newElm) : newElm;
      });
      const create = (name, attrs, html) => add(doc.createElement(name), name, attrs, html, true);
      const decode = Entities.decode;
      const encode = Entities.encodeAllRaw;
      const createHTML = (name, attrs, html = '') => {
        let outHtml = '<' + name;
        for (const key in attrs) {
          if (hasNonNullableKey(attrs, key)) {
            outHtml += ' ' + key + '="' + encode(attrs[key]) + '"';
          }
        }
        if (isEmpty$3(html) && has$2(schema.getVoidElements(), name)) {
          return outHtml + ' />';
        } else {
          return outHtml + '>' + html + '</' + name + '>';
        }
      };
      const createFragment = html => {
        const container = doc.createElement('div');
        const frag = doc.createDocumentFragment();
        frag.appendChild(container);
        if (html) {
          container.innerHTML = html;
        }
        let node;
        while (node = container.firstChild) {
          frag.appendChild(node);
        }
        frag.removeChild(container);
        return frag;
      };
      const remove = (node, keepChildren) => {
        return run(node, n => {
          const $node = SugarElement.fromDom(n);
          if (keepChildren) {
            each$e(children$1($node), child => {
              if (isText$b(child) && child.dom.length === 0) {
                remove$5(child);
              } else {
                before$3($node, child);
              }
            });
          }
          remove$5($node);
          return $node.dom;
        });
      };
      const removeAllAttribs = e => run(e, e => {
        const attrs = e.attributes;
        for (let i = attrs.length - 1; i >= 0; i--) {
          e.removeAttributeNode(attrs.item(i));
        }
      });
      const parseStyle = cssText => styles.parse(cssText);
      const serializeStyle = (stylesArg, name) => styles.serialize(stylesArg, name);
      const addStyle = cssText => {
        if (self !== DOMUtils.DOM && doc === document) {
          if (addedStyles[cssText]) {
            return;
          }
          addedStyles[cssText] = true;
        }
        let styleElm = doc.getElementById('mceDefaultStyles');
        if (!styleElm) {
          styleElm = doc.createElement('style');
          styleElm.id = 'mceDefaultStyles';
          styleElm.type = 'text/css';
          const head = doc.head;
          if (head.firstChild) {
            head.insertBefore(styleElm, head.firstChild);
          } else {
            head.appendChild(styleElm);
          }
        }
        if (styleElm.styleSheet) {
          styleElm.styleSheet.cssText += cssText;
        } else {
          styleElm.appendChild(doc.createTextNode(cssText));
        }
      };
      const loadCSS = urls => {
        if (!urls) {
          urls = '';
        }
        each$e(urls.split(','), url => {
          files[url] = true;
          styleSheetLoader.load(url).catch(noop);
        });
      };
      const toggleClass = (elm, cls, state) => {
        run(elm, e => {
          if (isElement$6(e)) {
            const $elm = SugarElement.fromDom(e);
            const classes = cls.split(' ');
            each$e(classes, c => {
              if (isNonNullable(state)) {
                const fn = state ? add$2 : remove$7;
                fn($elm, c);
              } else {
                toggle$1($elm, c);
              }
            });
          }
        });
      };
      const addClass = (elm, cls) => {
        toggleClass(elm, cls, true);
      };
      const removeClass = (elm, cls) => {
        toggleClass(elm, cls, false);
      };
      const hasClass = (elm, cls) => {
        const $elm = _get(elm);
        const classes = cls.split(' ');
        return isNonNullable($elm) && forall(classes, c => has($elm, c));
      };
      const show = elm => {
        run(elm, e => remove$6(SugarElement.fromDom(e), 'display'));
      };
      const hide = elm => {
        run(elm, e => set$2(SugarElement.fromDom(e), 'display', 'none'));
      };
      const isHidden = elm => {
        const $elm = _get(elm);
        return isNonNullable($elm) && is$2(getRaw($elm, 'display'), 'none');
      };
      const uniqueId = prefix => (!prefix ? 'mce_' : prefix) + counter++;
      const getOuterHTML = elm => {
        const $elm = _get(elm);
        if (isNonNullable($elm)) {
          return isElement$6($elm.dom) ? $elm.dom.outerHTML : getOuter($elm);
        } else {
          return '';
        }
      };
      const setOuterHTML = (elm, html) => {
        run(elm, $elm => {
          if (isElement$6($elm)) {
            $elm.outerHTML = html;
          }
        });
      };
      const insertAfter = (node, reference) => {
        const referenceNode = get(reference);
        return run(node, node => {
          const parent = referenceNode === null || referenceNode === void 0 ? void 0 : referenceNode.parentNode;
          const nextSibling = referenceNode === null || referenceNode === void 0 ? void 0 : referenceNode.nextSibling;
          if (parent) {
            if (nextSibling) {
              parent.insertBefore(node, nextSibling);
            } else {
              parent.appendChild(node);
            }
          }
          return node;
        });
      };
      const replace = (newElm, oldElm, keepChildren) => run(oldElm, elm => {
        var _a;
        const replacee = isArray$1(oldElm) ? newElm.cloneNode(true) : newElm;
        if (keepChildren) {
          each$a(grep(elm.childNodes), node => {
            replacee.appendChild(node);
          });
        }
        (_a = elm.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(replacee, elm);
        return elm;
      });
      const rename = (elm, name) => {
        if (elm.nodeName !== name.toUpperCase()) {
          const newElm = create(name);
          each$a(getAttribs(elm), attrNode => {
            setAttrib(newElm, attrNode.nodeName, getAttrib(elm, attrNode.nodeName));
          });
          replace(newElm, elm, true);
          return newElm;
        } else {
          return elm;
        }
      };
      const findCommonAncestor = (a, b) => {
        let ps = a;
        while (ps) {
          let pe = b;
          while (pe && ps !== pe) {
            pe = pe.parentNode;
          }
          if (ps === pe) {
            break;
          }
          ps = ps.parentNode;
        }
        if (!ps && a.ownerDocument) {
          return a.ownerDocument.documentElement;
        } else {
          return ps;
        }
      };
      const isNonEmptyElement = node => {
        if (isElement$6(node)) {
          const isNamedAnchor = node.nodeName.toLowerCase() === 'a' && !getAttrib(node, 'href') && getAttrib(node, 'id');
          if (getAttrib(node, 'name') || getAttrib(node, 'data-mce-bookmark') || isNamedAnchor) {
            return true;
          }
        }
        return false;
      };
      const isEmpty = (node, elements, options) => {
        let brCount = 0;
        if (isNonEmptyElement(node)) {
          return false;
        }
        const firstChild = node.firstChild;
        if (firstChild) {
          const walker = new DomTreeWalker(firstChild, node);
          const whitespaceElements = schema ? schema.getWhitespaceElements() : {};
          const nonEmptyElements = elements || (schema ? schema.getNonEmptyElements() : null);
          let tempNode = firstChild;
          do {
            if (isElement$6(tempNode)) {
              const bogusVal = tempNode.getAttribute('data-mce-bogus');
              if (bogusVal) {
                tempNode = walker.next(bogusVal === 'all');
                continue;
              }
              const name = tempNode.nodeName.toLowerCase();
              if (nonEmptyElements && nonEmptyElements[name]) {
                if (name === 'br') {
                  brCount++;
                  tempNode = walker.next();
                  continue;
                }
                return false;
              }
              if (isNonEmptyElement(tempNode)) {
                return false;
              }
            }
            if (isComment(tempNode)) {
              return false;
            }
            if (isText$a(tempNode) && !isWhitespaceText(tempNode.data) && (!(options === null || options === void 0 ? void 0 : options.includeZwsp) || !isZwsp(tempNode.data))) {
              return false;
            }
            if (isText$a(tempNode) && tempNode.parentNode && whitespaceElements[tempNode.parentNode.nodeName] && isWhitespaceText(tempNode.data)) {
              return false;
            }
            tempNode = walker.next();
          } while (tempNode);
        }
        return brCount <= 1;
      };
      const createRng = () => doc.createRange();
      const split = (parentElm, splitElm, replacementElm) => {
        let range = createRng();
        let beforeFragment;
        let afterFragment;
        if (parentElm && splitElm && parentElm.parentNode && splitElm.parentNode) {
          const parentNode = parentElm.parentNode;
          range.setStart(parentNode, findNodeIndex(parentElm));
          range.setEnd(splitElm.parentNode, findNodeIndex(splitElm));
          beforeFragment = range.extractContents();
          range = createRng();
          range.setStart(splitElm.parentNode, findNodeIndex(splitElm) + 1);
          range.setEnd(parentNode, findNodeIndex(parentElm) + 1);
          afterFragment = range.extractContents();
          parentNode.insertBefore(trimNode(self, beforeFragment, schema), parentElm);
          if (replacementElm) {
            parentNode.insertBefore(replacementElm, parentElm);
          } else {
            parentNode.insertBefore(splitElm, parentElm);
          }
          parentNode.insertBefore(trimNode(self, afterFragment, schema), parentElm);
          remove(parentElm);
          return replacementElm || splitElm;
        } else {
          return undefined;
        }
      };
      const bind = (target, name, func, scope) => {
        if (isArray$1(target)) {
          let i = target.length;
          const rv = [];
          while (i--) {
            rv[i] = bind(target[i], name, func, scope);
          }
          return rv;
        } else {
          if (settings.collect && (target === doc || target === win)) {
            boundEvents.push([
              target,
              name,
              func,
              scope
            ]);
          }
          return events.bind(target, name, func, scope || self);
        }
      };
      const unbind = (target, name, func) => {
        if (isArray$1(target)) {
          let i = target.length;
          const rv = [];
          while (i--) {
            rv[i] = unbind(target[i], name, func);
          }
          return rv;
        } else {
          if (boundEvents.length > 0 && (target === doc || target === win)) {
            let i = boundEvents.length;
            while (i--) {
              const [boundTarget, boundName, boundFunc] = boundEvents[i];
              if (target === boundTarget && (!name || name === boundName) && (!func || func === boundFunc)) {
                events.unbind(boundTarget, boundName, boundFunc);
              }
            }
          }
          return events.unbind(target, name, func);
        }
      };
      const dispatch = (target, name, evt) => events.dispatch(target, name, evt);
      const fire = (target, name, evt) => events.dispatch(target, name, evt);
      const getContentEditable = node => {
        if (node && isHTMLElement(node)) {
          const contentEditable = node.getAttribute('data-mce-contenteditable');
          if (contentEditable && contentEditable !== 'inherit') {
            return contentEditable;
          }
          return node.contentEditable !== 'inherit' ? node.contentEditable : null;
        } else {
          return null;
        }
      };
      const getContentEditableParent = node => {
        const root = getRoot();
        let state = null;
        for (let tempNode = node; tempNode && tempNode !== root; tempNode = tempNode.parentNode) {
          state = getContentEditable(tempNode);
          if (state !== null) {
            break;
          }
        }
        return state;
      };
      const isEditable = node => {
        if (isNonNullable(node)) {
          const scope = isElement$6(node) ? node : node.parentElement;
          return isNonNullable(scope) && isHTMLElement(scope) && isEditable$2(SugarElement.fromDom(scope));
        } else {
          return false;
        }
      };
      const destroy = () => {
        if (boundEvents.length > 0) {
          let i = boundEvents.length;
          while (i--) {
            const [boundTarget, boundName, boundFunc] = boundEvents[i];
            events.unbind(boundTarget, boundName, boundFunc);
          }
        }
        each$d(files, (_, url) => {
          styleSheetLoader.unload(url);
          delete files[url];
        });
      };
      const isChildOf = (node, parent) => {
        return node === parent || parent.contains(node);
      };
      const dumpRng = r => 'startContainer: ' + r.startContainer.nodeName + ', startOffset: ' + r.startOffset + ', endContainer: ' + r.endContainer.nodeName + ', endOffset: ' + r.endOffset;
      const self = {
        doc,
        settings,
        win,
        files,
        stdMode,
        boxModel,
        styleSheetLoader,
        boundEvents,
        styles,
        schema,
        events,
        isBlock: isBlock,
        root: null,
        clone,
        getRoot,
        getViewPort,
        getRect,
        getSize,
        getParent,
        getParents: getParents,
        get,
        getNext,
        getPrev,
        select,
        is,
        add,
        create,
        createHTML,
        createFragment,
        remove,
        setStyle,
        getStyle: getStyle,
        setStyles,
        removeAllAttribs,
        setAttrib,
        setAttribs,
        getAttrib,
        getPos: getPos$1,
        parseStyle,
        serializeStyle,
        addStyle,
        loadCSS,
        addClass,
        removeClass,
        hasClass,
        toggleClass,
        show,
        hide,
        isHidden,
        uniqueId,
        setHTML,
        getOuterHTML,
        setOuterHTML,
        decode,
        encode,
        insertAfter,
        replace,
        rename,
        findCommonAncestor,
        run,
        getAttribs,
        isEmpty,
        createRng,
        nodeIndex: findNodeIndex,
        split,
        bind: bind,
        unbind: unbind,
        fire,
        dispatch,
        getContentEditable,
        getContentEditableParent,
        isEditable,
        destroy,
        isChildOf,
        dumpRng
      };
      const attrHooks = setupAttrHooks(styles, settings, constant(self));
      return self;
    };
    DOMUtils.DOM = DOMUtils(document);
    DOMUtils.nodeIndex = findNodeIndex;

    const DOM$b = DOMUtils.DOM;
    const QUEUED = 0;
    const LOADING = 1;
    const LOADED = 2;
    const FAILED = 3;
    class ScriptLoader {
      constructor(settings = {}) {
        this.states = {};
        this.queue = [];
        this.scriptLoadedCallbacks = {};
        this.queueLoadedCallbacks = [];
        this.loading = false;
        this.settings = settings;
      }
      _setReferrerPolicy(referrerPolicy) {
        this.settings.referrerPolicy = referrerPolicy;
      }
      loadScript(url) {
        return new Promise((resolve, reject) => {
          const dom = DOM$b;
          let elm;
          const cleanup = () => {
            dom.remove(id);
            if (elm) {
              elm.onerror = elm.onload = elm = null;
            }
          };
          const done = () => {
            cleanup();
            resolve();
          };
          const error = () => {
            cleanup();
            reject('Failed to load script: ' + url);
          };
          const id = dom.uniqueId();
          elm = document.createElement('script');
          elm.id = id;
          elm.type = 'text/javascript';
          elm.src = Tools._addCacheSuffix(url);
          if (this.settings.referrerPolicy) {
            dom.setAttrib(elm, 'referrerpolicy', this.settings.referrerPolicy);
          }
          elm.onload = done;
          elm.onerror = error;
          (document.getElementsByTagName('head')[0] || document.body).appendChild(elm);
        });
      }
      isDone(url) {
        return this.states[url] === LOADED;
      }
      markDone(url) {
        this.states[url] = LOADED;
      }
      add(url) {
        const self = this;
        self.queue.push(url);
        const state = self.states[url];
        if (state === undefined) {
          self.states[url] = QUEUED;
        }
        return new Promise((resolve, reject) => {
          if (!self.scriptLoadedCallbacks[url]) {
            self.scriptLoadedCallbacks[url] = [];
          }
          self.scriptLoadedCallbacks[url].push({
            resolve,
            reject
          });
        });
      }
      load(url) {
        return this.add(url);
      }
      remove(url) {
        delete this.states[url];
        delete this.scriptLoadedCallbacks[url];
      }
      loadQueue() {
        const queue = this.queue;
        this.queue = [];
        return this.loadScripts(queue);
      }
      loadScripts(scripts) {
        const self = this;
        const execCallbacks = (name, url) => {
          get$a(self.scriptLoadedCallbacks, url).each(callbacks => {
            each$e(callbacks, callback => callback[name](url));
          });
          delete self.scriptLoadedCallbacks[url];
        };
        const processResults = results => {
          const failures = filter$5(results, result => result.status === 'rejected');
          if (failures.length > 0) {
            return Promise.reject(bind$3(failures, ({reason}) => isArray$1(reason) ? reason : [reason]));
          } else {
            return Promise.resolve();
          }
        };
        const load = urls => Promise.allSettled(map$3(urls, url => {
          if (self.states[url] === LOADED) {
            execCallbacks('resolve', url);
            return Promise.resolve();
          } else if (self.states[url] === FAILED) {
            execCallbacks('reject', url);
            return Promise.reject(url);
          } else {
            self.states[url] = LOADING;
            return self.loadScript(url).then(() => {
              self.states[url] = LOADED;
              execCallbacks('resolve', url);
              const queue = self.queue;
              if (queue.length > 0) {
                self.queue = [];
                return load(queue).then(processResults);
              } else {
                return Promise.resolve();
              }
            }, () => {
              self.states[url] = FAILED;
              execCallbacks('reject', url);
              return Promise.reject(url);
            });
          }
        }));
        const processQueue = urls => {
          self.loading = true;
          return load(urls).then(results => {
            self.loading = false;
            const nextQueuedItem = self.queueLoadedCallbacks.shift();
            Optional.from(nextQueuedItem).each(call);
            return processResults(results);
          });
        };
        const uniqueScripts = stringArray(scripts);
        if (self.loading) {
          return new Promise((resolve, reject) => {
            self.queueLoadedCallbacks.push(() => {
              processQueue(uniqueScripts).then(resolve, reject);
            });
          });
        } else {
          return processQueue(uniqueScripts);
        }
      }
    }
    ScriptLoader.ScriptLoader = new ScriptLoader();

    const Cell = initial => {
      let value = initial;
      const get = () => {
        return value;
      };
      const set = v => {
        value = v;
      };
      return {
        get,
        set
      };
    };

    const isDuplicated = (items, item) => {
      const firstIndex = items.indexOf(item);
      return firstIndex !== -1 && items.indexOf(item, firstIndex + 1) > firstIndex;
    };
    const isRaw = str => isObject(str) && has$2(str, 'raw');
    const isTokenised = str => isArray$1(str) && str.length > 1;
    const data = {};
    const currentCode = Cell('en');
    const getLanguageData = () => get$a(data, currentCode.get());
    const getData$1 = () => map$2(data, value => ({ ...value }));
    const setCode = newCode => {
      if (newCode) {
        currentCode.set(newCode);
      }
    };
    const getCode = () => currentCode.get();
    const add$1 = (code, items) => {
      let langData = data[code];
      if (!langData) {
        data[code] = langData = {};
      }
      const lcNames = map$3(keys(items), name => name.toLowerCase());
      each$d(items, (translation, name) => {
        const lcName = name.toLowerCase();
        if (lcName !== name && isDuplicated(lcNames, lcName)) {
          if (!has$2(items, lcName)) {
            langData[lcName] = translation;
          }
          langData[name] = translation;
        } else {
          langData[lcName] = translation;
        }
      });
    };
    const translate = text => {
      const langData = getLanguageData().getOr({});
      const toString = obj => {
        if (isFunction(obj)) {
          return Object.prototype.toString.call(obj);
        }
        return !isEmpty(obj) ? '' + obj : '';
      };
      const isEmpty = text => text === '' || text === null || text === undefined;
      const getLangData = text => {
        const textStr = toString(text);
        return has$2(langData, textStr) ? toString(langData[textStr]) : get$a(langData, textStr.toLowerCase()).map(toString).getOr(textStr);
      };
      const removeContext = str => str.replace(/{context:\w+}$/, '');
      if (isEmpty(text)) {
        return '';
      }
      if (isRaw(text)) {
        return toString(text.raw);
      }
      if (isTokenised(text)) {
        const values = text.slice(1);
        const substitued = getLangData(text[0]).replace(/\{([0-9]+)\}/g, ($1, $2) => has$2(values, $2) ? toString(values[$2]) : $1);
        return removeContext(substitued);
      }
      return removeContext(getLangData(text));
    };
    const isRtl$1 = () => getLanguageData().bind(items => get$a(items, '_dir')).exists(dir => dir === 'rtl');
    const hasCode = code => has$2(data, code);
    const I18n = {
      getData: getData$1,
      setCode,
      getCode,
      add: add$1,
      translate,
      isRtl: isRtl$1,
      hasCode
    };

    const AddOnManager = () => {
      const items = [];
      const urls = {};
      const lookup = {};
      const _listeners = [];
      const runListeners = (name, state) => {
        const matchedListeners = filter$5(_listeners, listener => listener.name === name && listener.state === state);
        each$e(matchedListeners, listener => listener.resolve());
      };
      const isLoaded = name => has$2(urls, name);
      const isAdded = name => has$2(lookup, name);
      const get = name => {
        if (lookup[name]) {
          return lookup[name].instance;
        }
        return undefined;
      };
      const loadLanguagePack = (name, languages) => {
        const language = I18n.getCode();
        const wrappedLanguages = ',' + (languages || '') + ',';
        if (!language || languages && wrappedLanguages.indexOf(',' + language + ',') === -1) {
          return;
        }
        ScriptLoader.ScriptLoader.add(urls[name] + '/langs/' + language + '.js');
      };
      const requireLangPack = (name, languages) => {
        if (AddOnManager.languageLoad !== false) {
          if (isLoaded(name)) {
            loadLanguagePack(name, languages);
          } else {
            waitFor(name, 'loaded').then(() => loadLanguagePack(name, languages));
          }
        }
      };
      const add = (id, addOn) => {
        items.push(addOn);
        lookup[id] = { instance: addOn };
        runListeners(id, 'added');
        return addOn;
      };
      const remove = name => {
        delete urls[name];
        delete lookup[name];
      };
      const createUrl = (baseUrl, dep) => {
        if (isString(dep)) {
          return isString(baseUrl) ? {
            prefix: '',
            resource: dep,
            suffix: ''
          } : {
            prefix: baseUrl.prefix,
            resource: dep,
            suffix: baseUrl.suffix
          };
        } else {
          return dep;
        }
      };
      const load = (name, addOnUrl) => {
        if (urls[name]) {
          return Promise.resolve();
        }
        let urlString = isString(addOnUrl) ? addOnUrl : addOnUrl.prefix + addOnUrl.resource + addOnUrl.suffix;
        if (urlString.indexOf('/') !== 0 && urlString.indexOf('://') === -1) {
          urlString = AddOnManager.baseURL + '/' + urlString;
        }
        urls[name] = urlString.substring(0, urlString.lastIndexOf('/'));
        const done = () => {
          runListeners(name, 'loaded');
          return Promise.resolve();
        };
        if (lookup[name]) {
          return done();
        } else {
          return ScriptLoader.ScriptLoader.add(urlString).then(done);
        }
      };
      const waitFor = (name, state = 'added') => {
        if (state === 'added' && isAdded(name)) {
          return Promise.resolve();
        } else if (state === 'loaded' && isLoaded(name)) {
          return Promise.resolve();
        } else {
          return new Promise(resolve => {
            _listeners.push({
              name,
              state,
              resolve
            });
          });
        }
      };
      return {
        items,
        urls,
        lookup,
        get,
        requireLangPack,
        add,
        remove,
        createUrl,
        load,
        waitFor
      };
    };
    AddOnManager.languageLoad = true;
    AddOnManager.baseURL = '';
    AddOnManager.PluginManager = AddOnManager();
    AddOnManager.ThemeManager = AddOnManager();
    AddOnManager.ModelManager = AddOnManager();

    const singleton = doRevoke => {
      const subject = Cell(Optional.none());
      const revoke = () => subject.get().each(doRevoke);
      const clear = () => {
        revoke();
        subject.set(Optional.none());
      };
      const isSet = () => subject.get().isSome();
      const get = () => subject.get();
      const set = s => {
        revoke();
        subject.set(Optional.some(s));
      };
      return {
        clear,
        isSet,
        get,
        set
      };
    };
    const repeatable = delay => {
      const intervalId = Cell(Optional.none());
      const revoke = () => intervalId.get().each(id => clearInterval(id));
      const clear = () => {
        revoke();
        intervalId.set(Optional.none());
      };
      const isSet = () => intervalId.get().isSome();
      const get = () => intervalId.get();
      const set = functionToRepeat => {
        revoke();
        intervalId.set(Optional.some(setInterval(functionToRepeat, delay)));
      };
      return {
        clear,
        isSet,
        get,
        set
      };
    };
    const value$2 = () => {
      const subject = singleton(noop);
      const on = f => subject.get().each(f);
      return {
        ...subject,
        on
      };
    };

    const first$1 = (fn, rate) => {
      let timer = null;
      const cancel = () => {
        if (!isNull(timer)) {
          clearTimeout(timer);
          timer = null;
        }
      };
      const throttle = (...args) => {
        if (isNull(timer)) {
          timer = setTimeout(() => {
            timer = null;
            fn.apply(null, args);
          }, rate);
        }
      };
      return {
        cancel,
        throttle
      };
    };
    const last$1 = (fn, rate) => {
      let timer = null;
      const cancel = () => {
        if (!isNull(timer)) {
          clearTimeout(timer);
          timer = null;
        }
      };
      const throttle = (...args) => {
        cancel();
        timer = setTimeout(() => {
          timer = null;
          fn.apply(null, args);
        }, rate);
      };
      return {
        cancel,
        throttle
      };
    };

    const annotation = constant('mce-annotation');
    const dataAnnotation = constant('data-mce-annotation');
    const dataAnnotationId = constant('data-mce-annotation-uid');
    const dataAnnotationActive = constant('data-mce-annotation-active');
    const dataAnnotationClasses = constant('data-mce-annotation-classes');
    const dataAnnotationAttributes = constant('data-mce-annotation-attrs');

    const isRoot$1 = root => node => eq(node, root);
    const identify = (editor, annotationName) => {
      const rng = editor.selection.getRng();
      const start = SugarElement.fromDom(rng.startContainer);
      const root = SugarElement.fromDom(editor.getBody());
      const selector = annotationName.fold(() => '.' + annotation(), an => `[${ dataAnnotation() }="${ an }"]`);
      const newStart = child$1(start, rng.startOffset).getOr(start);
      const closest = closest$3(newStart, selector, isRoot$1(root));
      return closest.bind(c => getOpt(c, `${ dataAnnotationId() }`).bind(uid => getOpt(c, `${ dataAnnotation() }`).map(name => {
        const elements = findMarkers(editor, uid);
        return {
          uid,
          name,
          elements
        };
      })));
    };
    const isAnnotation = elem => isElement$7(elem) && has(elem, annotation());
    const isBogusElement = (elem, root) => has$1(elem, 'data-mce-bogus') || ancestor$2(elem, '[data-mce-bogus="all"]', isRoot$1(root));
    const findMarkers = (editor, uid) => {
      const body = SugarElement.fromDom(editor.getBody());
      const descendants$1 = descendants(body, `[${ dataAnnotationId() }="${ uid }"]`);
      return filter$5(descendants$1, descendant => !isBogusElement(descendant, body));
    };
    const findAll = (editor, name) => {
      const body = SugarElement.fromDom(editor.getBody());
      const markers = descendants(body, `[${ dataAnnotation() }="${ name }"]`);
      const directory = {};
      each$e(markers, m => {
        if (!isBogusElement(m, body)) {
          const uid = get$9(m, dataAnnotationId());
          const nodesAlready = get$a(directory, uid).getOr([]);
          directory[uid] = nodesAlready.concat([m]);
        }
      });
      return directory;
    };

    const setup$y = (editor, registry) => {
      const changeCallbacks = Cell({});
      const initData = () => ({
        listeners: [],
        previous: value$2()
      });
      const withCallbacks = (name, f) => {
        updateCallbacks(name, data => {
          f(data);
          return data;
        });
      };
      const updateCallbacks = (name, f) => {
        const callbackMap = changeCallbacks.get();
        const data = get$a(callbackMap, name).getOrThunk(initData);
        const outputData = f(data);
        callbackMap[name] = outputData;
        changeCallbacks.set(callbackMap);
      };
      const fireCallbacks = (name, uid, elements) => {
        withCallbacks(name, data => {
          each$e(data.listeners, f => f(true, name, {
            uid,
            nodes: map$3(elements, elem => elem.dom)
          }));
        });
      };
      const fireNoAnnotation = name => {
        withCallbacks(name, data => {
          each$e(data.listeners, f => f(false, name));
        });
      };
      const toggleActiveAttr = (uid, state) => {
        each$e(findMarkers(editor, uid), elem => {
          if (state) {
            set$3(elem, dataAnnotationActive(), 'true');
          } else {
            remove$a(elem, dataAnnotationActive());
          }
        });
      };
      const onNodeChange = last$1(() => {
        const annotations = sort(registry.getNames());
        each$e(annotations, name => {
          updateCallbacks(name, data => {
            const prev = data.previous.get();
            identify(editor, Optional.some(name)).fold(() => {
              prev.each(uid => {
                fireNoAnnotation(name);
                data.previous.clear();
                toggleActiveAttr(uid, false);
              });
            }, ({uid, name, elements}) => {
              if (!is$2(prev, uid)) {
                prev.each(uid => toggleActiveAttr(uid, false));
                fireCallbacks(name, uid, elements);
                data.previous.set(uid);
                toggleActiveAttr(uid, true);
              }
            });
            return {
              previous: data.previous,
              listeners: data.listeners
            };
          });
        });
      }, 30);
      editor.on('remove', () => {
        onNodeChange.cancel();
      });
      editor.on('NodeChange', () => {
        onNodeChange.throttle();
      });
      const addListener = (name, f) => {
        updateCallbacks(name, data => ({
          previous: data.previous,
          listeners: data.listeners.concat([f])
        }));
      };
      return { addListener };
    };

    const setup$x = (editor, registry) => {
      const dataAnnotation$1 = dataAnnotation();
      const identifyParserNode = node => Optional.from(node.attr(dataAnnotation$1)).bind(registry.lookup);
      const removeDirectAnnotation = node => {
        var _a, _b;
        node.attr(dataAnnotationId(), null);
        node.attr(dataAnnotation(), null);
        node.attr(dataAnnotationActive(), null);
        const customAttrNames = Optional.from(node.attr(dataAnnotationAttributes())).map(names => names.split(',')).getOr([]);
        const customClasses = Optional.from(node.attr(dataAnnotationClasses())).map(names => names.split(',')).getOr([]);
        each$e(customAttrNames, name => node.attr(name, null));
        const classList = (_b = (_a = node.attr('class')) === null || _a === void 0 ? void 0 : _a.split(' ')) !== null && _b !== void 0 ? _b : [];
        const newClassList = difference(classList, [annotation()].concat(customClasses));
        node.attr('class', newClassList.length > 0 ? newClassList.join(' ') : null);
        node.attr(dataAnnotationClasses(), null);
        node.attr(dataAnnotationAttributes(), null);
      };
      editor.serializer.addTempAttr(dataAnnotationActive());
      editor.serializer.addAttributeFilter(dataAnnotation$1, nodes => {
        for (const node of nodes) {
          identifyParserNode(node).each(settings => {
            if (settings.persistent === false) {
              if (node.name === 'span') {
                node.unwrap();
              } else {
                removeDirectAnnotation(node);
              }
            }
          });
        }
      });
    };

    const create$c = () => {
      const annotations = {};
      const register = (name, settings) => {
        annotations[name] = {
          name,
          settings
        };
      };
      const lookup = name => get$a(annotations, name).map(a => a.settings);
      const getNames = () => keys(annotations);
      return {
        register,
        lookup,
        getNames
      };
    };

    let unique = 0;
    const generate$1 = prefix => {
      const date = new Date();
      const time = date.getTime();
      const random = Math.floor(Math.random() * 1000000000);
      unique++;
      return prefix + '_' + random + unique + String(time);
    };

    const add = (element, classes) => {
      each$e(classes, x => {
        add$2(element, x);
      });
    };
    const remove$4 = (element, classes) => {
      each$e(classes, x => {
        remove$7(element, x);
      });
    };

    const clone$2 = (original, isDeep) => SugarElement.fromDom(original.dom.cloneNode(isDeep));
    const shallow$1 = original => clone$2(original, false);
    const deep$1 = original => clone$2(original, true);
    const shallowAs = (original, tag) => {
      const nu = SugarElement.fromTag(tag);
      const attributes = clone$4(original);
      setAll$1(nu, attributes);
      return nu;
    };
    const mutate = (original, tag) => {
      const nu = shallowAs(original, tag);
      after$4(original, nu);
      const children = children$1(original);
      append(nu, children);
      remove$5(original);
      return nu;
    };

    const TextWalker = (startNode, rootNode, isBoundary = never) => {
      const walker = new DomTreeWalker(startNode, rootNode);
      const walk = direction => {
        let next;
        do {
          next = walker[direction]();
        } while (next && !isText$a(next) && !isBoundary(next));
        return Optional.from(next).filter(isText$a);
      };
      return {
        current: () => Optional.from(walker.current()).filter(isText$a),
        next: () => walk('next'),
        prev: () => walk('prev'),
        prev2: () => walk('prev2')
      };
    };

    const TextSeeker = (dom, isBoundary) => {
      const isBlockBoundary = isBoundary ? isBoundary : node => dom.isBlock(node) || isBr$6(node) || isContentEditableFalse$b(node);
      const walk = (node, offset, walker, process) => {
        if (isText$a(node)) {
          const newOffset = process(node, offset, node.data);
          if (newOffset !== -1) {
            return Optional.some({
              container: node,
              offset: newOffset
            });
          }
        }
        return walker().bind(next => walk(next.container, next.offset, walker, process));
      };
      const backwards = (node, offset, process, root) => {
        const walker = TextWalker(node, root !== null && root !== void 0 ? root : dom.getRoot(), isBlockBoundary);
        return walk(node, offset, () => walker.prev().map(prev => ({
          container: prev,
          offset: prev.length
        })), process).getOrNull();
      };
      const forwards = (node, offset, process, root) => {
        const walker = TextWalker(node, root !== null && root !== void 0 ? root : dom.getRoot(), isBlockBoundary);
        return walk(node, offset, () => walker.next().map(next => ({
          container: next,
          offset: 0
        })), process).getOrNull();
      };
      return {
        backwards,
        forwards
      };
    };

    const round$2 = Math.round;
    const clone$1 = rect => {
      if (!rect) {
        return {
          left: 0,
          top: 0,
          bottom: 0,
          right: 0,
          width: 0,
          height: 0
        };
      }
      return {
        left: round$2(rect.left),
        top: round$2(rect.top),
        bottom: round$2(rect.bottom),
        right: round$2(rect.right),
        width: round$2(rect.width),
        height: round$2(rect.height)
      };
    };
    const collapse = (rect, toStart) => {
      rect = clone$1(rect);
      if (toStart) {
        rect.right = rect.left;
      } else {
        rect.left = rect.left + rect.width;
        rect.right = rect.left;
      }
      rect.width = 0;
      return rect;
    };
    const isEqual = (rect1, rect2) => rect1.left === rect2.left && rect1.top === rect2.top && rect1.bottom === rect2.bottom && rect1.right === rect2.right;
    const isValidOverflow = (overflowY, rect1, rect2) => overflowY >= 0 && overflowY <= Math.min(rect1.height, rect2.height) / 2;
    const isAbove$1 = (rect1, rect2) => {
      const halfHeight = Math.min(rect2.height / 2, rect1.height / 2);
      if (rect1.bottom - halfHeight < rect2.top) {
        return true;
      }
      if (rect1.top > rect2.bottom) {
        return false;
      }
      return isValidOverflow(rect2.top - rect1.bottom, rect1, rect2);
    };
    const isBelow$1 = (rect1, rect2) => {
      if (rect1.top > rect2.bottom) {
        return true;
      }
      if (rect1.bottom < rect2.top) {
        return false;
      }
      return isValidOverflow(rect2.bottom - rect1.top, rect1, rect2);
    };
    const containsXY = (rect, clientX, clientY) => clientX >= rect.left && clientX <= rect.right && clientY >= rect.top && clientY <= rect.bottom;
    const boundingClientRectFromRects = rects => {
      return foldl(rects, (acc, rect) => {
        return acc.fold(() => Optional.some(rect), prevRect => {
          const left = Math.min(rect.left, prevRect.left);
          const top = Math.min(rect.top, prevRect.top);
          const right = Math.max(rect.right, prevRect.right);
          const bottom = Math.max(rect.bottom, prevRect.bottom);
          return Optional.some({
            top,
            right,
            bottom,
            left,
            width: right - left,
            height: bottom - top
          });
        });
      }, Optional.none());
    };
    const distanceToRectEdgeFromXY = (rect, x, y) => {
      const cx = Math.max(Math.min(x, rect.left + rect.width), rect.left);
      const cy = Math.max(Math.min(y, rect.top + rect.height), rect.top);
      return Math.sqrt((x - cx) * (x - cx) + (y - cy) * (y - cy));
    };
    const overlapY = (r1, r2) => Math.max(0, Math.min(r1.bottom, r2.bottom) - Math.max(r1.top, r2.top));

    const clamp$2 = (value, min, max) => Math.min(Math.max(value, min), max);

    const getSelectedNode = range => {
      const startContainer = range.startContainer, startOffset = range.startOffset;
      if (startContainer === range.endContainer && startContainer.hasChildNodes() && range.endOffset === startOffset + 1) {
        return startContainer.childNodes[startOffset];
      }
      return null;
    };
    const getNode$1 = (container, offset) => {
      if (isElement$6(container) && container.hasChildNodes()) {
        const childNodes = container.childNodes;
        const safeOffset = clamp$2(offset, 0, childNodes.length - 1);
        return childNodes[safeOffset];
      } else {
        return container;
      }
    };
    const getNodeUnsafe = (container, offset) => {
      if (offset < 0 && isElement$6(container) && container.hasChildNodes()) {
        return undefined;
      } else {
        return getNode$1(container, offset);
      }
    };

    const extendingChars = new RegExp('[\u0300-\u036f\u0483-\u0487\u0488-\u0489\u0591-\u05bd\u05bf\u05c1-\u05c2\u05c4-\u05c5\u05c7\u0610-\u061a' + '\u064b-\u065f\u0670\u06d6-\u06dc\u06df-\u06e4\u06e7-\u06e8\u06ea-\u06ed\u0711\u0730-\u074a\u07a6-\u07b0' + '\u07eb-\u07f3\u0816-\u0819\u081b-\u0823\u0825-\u0827\u0829-\u082d\u0859-\u085b\u08e3-\u0902\u093a\u093c' + '\u0941-\u0948\u094d\u0951-\u0957\u0962-\u0963\u0981\u09bc\u09be\u09c1-\u09c4\u09cd\u09d7\u09e2-\u09e3' + '\u0a01-\u0a02\u0a3c\u0a41-\u0a42\u0a47-\u0a48\u0a4b-\u0a4d\u0a51\u0a70-\u0a71\u0a75\u0a81-\u0a82\u0abc' + '\u0ac1-\u0ac5\u0ac7-\u0ac8\u0acd\u0ae2-\u0ae3\u0b01\u0b3c\u0b3e\u0b3f\u0b41-\u0b44\u0b4d\u0b56\u0b57' + '\u0b62-\u0b63\u0b82\u0bbe\u0bc0\u0bcd\u0bd7\u0c00\u0c3e-\u0c40\u0c46-\u0c48\u0c4a-\u0c4d\u0c55-\u0c56' + '\u0c62-\u0c63\u0c81\u0cbc\u0cbf\u0cc2\u0cc6\u0ccc-\u0ccd\u0cd5-\u0cd6\u0ce2-\u0ce3\u0d01\u0d3e\u0d41-\u0d44' + '\u0d4d\u0d57\u0d62-\u0d63\u0dca\u0dcf\u0dd2-\u0dd4\u0dd6\u0ddf\u0e31\u0e34-\u0e3a\u0e47-\u0e4e\u0eb1\u0eb4-\u0eb9' + '\u0ebb-\u0ebc\u0ec8-\u0ecd\u0f18-\u0f19\u0f35\u0f37\u0f39\u0f71-\u0f7e\u0f80-\u0f84\u0f86-\u0f87\u0f8d-\u0f97' + '\u0f99-\u0fbc\u0fc6\u102d-\u1030\u1032-\u1037\u1039-\u103a\u103d-\u103e\u1058-\u1059\u105e-\u1060\u1071-\u1074' + '\u1082\u1085-\u1086\u108d\u109d\u135d-\u135f\u1712-\u1714\u1732-\u1734\u1752-\u1753\u1772-\u1773\u17b4-\u17b5' + '\u17b7-\u17bd\u17c6\u17c9-\u17d3\u17dd\u180b-\u180d\u18a9\u1920-\u1922\u1927-\u1928\u1932\u1939-\u193b\u1a17-\u1a18' + '\u1a1b\u1a56\u1a58-\u1a5e\u1a60\u1a62\u1a65-\u1a6c\u1a73-\u1a7c\u1a7f\u1ab0-\u1abd\u1ABE\u1b00-\u1b03\u1b34' + '\u1b36-\u1b3a\u1b3c\u1b42\u1b6b-\u1b73\u1b80-\u1b81\u1ba2-\u1ba5\u1ba8-\u1ba9\u1bab-\u1bad\u1be6\u1be8-\u1be9' + '\u1bed\u1bef-\u1bf1\u1c2c-\u1c33\u1c36-\u1c37\u1cd0-\u1cd2\u1cd4-\u1ce0\u1ce2-\u1ce8\u1ced\u1cf4\u1cf8-\u1cf9' + '\u1dc0-\u1df5\u1dfc-\u1dff\u200c-\u200d\u20d0-\u20dc\u20DD-\u20E0\u20e1\u20E2-\u20E4\u20e5-\u20f0\u2cef-\u2cf1' + '\u2d7f\u2de0-\u2dff\u302a-\u302d\u302e-\u302f\u3099-\u309a\ua66f\uA670-\uA672\ua674-\ua67d\ua69e-\ua69f\ua6f0-\ua6f1' + '\ua802\ua806\ua80b\ua825-\ua826\ua8c4\ua8e0-\ua8f1\ua926-\ua92d\ua947-\ua951\ua980-\ua982\ua9b3\ua9b6-\ua9b9\ua9bc' + '\ua9e5\uaa29-\uaa2e\uaa31-\uaa32\uaa35-\uaa36\uaa43\uaa4c\uaa7c\uaab0\uaab2-\uaab4\uaab7-\uaab8\uaabe-\uaabf\uaac1' + '\uaaec-\uaaed\uaaf6\uabe5\uabe8\uabed\ufb1e\ufe00-\ufe0f\ufe20-\ufe2f\uff9e-\uff9f]');
    const isExtendingChar = ch => isString(ch) && ch.charCodeAt(0) >= 768 && extendingChars.test(ch);

    const or = (...args) => {
      return x => {
        for (let i = 0; i < args.length; i++) {
          if (args[i](x)) {
            return true;
          }
        }
        return false;
      };
    };
    const and = (...args) => {
      return x => {
        for (let i = 0; i < args.length; i++) {
          if (!args[i](x)) {
            return false;
          }
        }
        return true;
      };
    };

    const isElement$4 = isElement$6;
    const isCaretCandidate$2 = isCaretCandidate$3;
    const isBlock$2 = matchStyleValues('display', 'block table');
    const isFloated = matchStyleValues('float', 'left right');
    const isValidElementCaretCandidate = and(isElement$4, isCaretCandidate$2, not(isFloated));
    const isNotPre = not(matchStyleValues('white-space', 'pre pre-line pre-wrap'));
    const isText$7 = isText$a;
    const isBr$3 = isBr$6;
    const nodeIndex$1 = DOMUtils.nodeIndex;
    const resolveIndex$1 = getNodeUnsafe;
    const createRange$1 = doc => doc ? doc.createRange() : DOMUtils.DOM.createRng();
    const isWhiteSpace$1 = chr => isString(chr) && /[\r\n\t ]/.test(chr);
    const isRange = rng => !!rng.setStart && !!rng.setEnd;
    const isHiddenWhiteSpaceRange = range => {
      const container = range.startContainer;
      const offset = range.startOffset;
      if (isWhiteSpace$1(range.toString()) && isNotPre(container.parentNode) && isText$a(container)) {
        const text = container.data;
        if (isWhiteSpace$1(text[offset - 1]) || isWhiteSpace$1(text[offset + 1])) {
          return true;
        }
      }
      return false;
    };
    const getBrClientRect = brNode => {
      const doc = brNode.ownerDocument;
      const rng = createRange$1(doc);
      const nbsp$1 = doc.createTextNode(nbsp);
      const parentNode = brNode.parentNode;
      parentNode.insertBefore(nbsp$1, brNode);
      rng.setStart(nbsp$1, 0);
      rng.setEnd(nbsp$1, 1);
      const clientRect = clone$1(rng.getBoundingClientRect());
      parentNode.removeChild(nbsp$1);
      return clientRect;
    };
    const getBoundingClientRectWebKitText = rng => {
      const sc = rng.startContainer;
      const ec = rng.endContainer;
      const so = rng.startOffset;
      const eo = rng.endOffset;
      if (sc === ec && isText$a(ec) && so === 0 && eo === 1) {
        const newRng = rng.cloneRange();
        newRng.setEndAfter(ec);
        return getBoundingClientRect$1(newRng);
      } else {
        return null;
      }
    };
    const isZeroRect = r => r.left === 0 && r.right === 0 && r.top === 0 && r.bottom === 0;
    const getBoundingClientRect$1 = item => {
      var _a;
      let clientRect;
      const clientRects = item.getClientRects();
      if (clientRects.length > 0) {
        clientRect = clone$1(clientRects[0]);
      } else {
        clientRect = clone$1(item.getBoundingClientRect());
      }
      if (!isRange(item) && isBr$3(item) && isZeroRect(clientRect)) {
        return getBrClientRect(item);
      }
      if (isZeroRect(clientRect) && isRange(item)) {
        return (_a = getBoundingClientRectWebKitText(item)) !== null && _a !== void 0 ? _a : clientRect;
      }
      return clientRect;
    };
    const collapseAndInflateWidth = (clientRect, toStart) => {
      const newClientRect = collapse(clientRect, toStart);
      newClientRect.width = 1;
      newClientRect.right = newClientRect.left + 1;
      return newClientRect;
    };
    const getCaretPositionClientRects = caretPosition => {
      const clientRects = [];
      const addUniqueAndValidRect = clientRect => {
        if (clientRect.height === 0) {
          return;
        }
        if (clientRects.length > 0) {
          if (isEqual(clientRect, clientRects[clientRects.length - 1])) {
            return;
          }
        }
        clientRects.push(clientRect);
      };
      const addCharacterOffset = (container, offset) => {
        const range = createRange$1(container.ownerDocument);
        if (offset < container.data.length) {
          if (isExtendingChar(container.data[offset])) {
            return;
          }
          if (isExtendingChar(container.data[offset - 1])) {
            range.setStart(container, offset);
            range.setEnd(container, offset + 1);
            if (!isHiddenWhiteSpaceRange(range)) {
              addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(range), false));
              return;
            }
          }
        }
        if (offset > 0) {
          range.setStart(container, offset - 1);
          range.setEnd(container, offset);
          if (!isHiddenWhiteSpaceRange(range)) {
            addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(range), false));
          }
        }
        if (offset < container.data.length) {
          range.setStart(container, offset);
          range.setEnd(container, offset + 1);
          if (!isHiddenWhiteSpaceRange(range)) {
            addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(range), true));
          }
        }
      };
      const container = caretPosition.container();
      const offset = caretPosition.offset();
      if (isText$7(container)) {
        addCharacterOffset(container, offset);
        return clientRects;
      }
      if (isElement$4(container)) {
        if (caretPosition.isAtEnd()) {
          const node = resolveIndex$1(container, offset);
          if (isText$7(node)) {
            addCharacterOffset(node, node.data.length);
          }
          if (isValidElementCaretCandidate(node) && !isBr$3(node)) {
            addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(node), false));
          }
        } else {
          const node = resolveIndex$1(container, offset);
          if (isText$7(node)) {
            addCharacterOffset(node, 0);
          }
          if (isValidElementCaretCandidate(node) && caretPosition.isAtEnd()) {
            addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(node), false));
            return clientRects;
          }
          const beforeNode = resolveIndex$1(caretPosition.container(), caretPosition.offset() - 1);
          if (isValidElementCaretCandidate(beforeNode) && !isBr$3(beforeNode)) {
            if (isBlock$2(beforeNode) || isBlock$2(node) || !isValidElementCaretCandidate(node)) {
              addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(beforeNode), false));
            }
          }
          if (isValidElementCaretCandidate(node)) {
            addUniqueAndValidRect(collapseAndInflateWidth(getBoundingClientRect$1(node), true));
          }
        }
      }
      return clientRects;
    };
    const CaretPosition = (container, offset, clientRects) => {
      const isAtStart = () => {
        if (isText$7(container)) {
          return offset === 0;
        }
        return offset === 0;
      };
      const isAtEnd = () => {
        if (isText$7(container)) {
          return offset >= container.data.length;
        }
        return offset >= container.childNodes.length;
      };
      const toRange = () => {
        const range = createRange$1(container.ownerDocument);
        range.setStart(container, offset);
        range.setEnd(container, offset);
        return range;
      };
      const getClientRects = () => {
        if (!clientRects) {
          clientRects = getCaretPositionClientRects(CaretPosition(container, offset));
        }
        return clientRects;
      };
      const isVisible = () => getClientRects().length > 0;
      const isEqual = caretPosition => caretPosition && container === caretPosition.container() && offset === caretPosition.offset();
      const getNode = before => resolveIndex$1(container, before ? offset - 1 : offset);
      return {
        container: constant(container),
        offset: constant(offset),
        toRange,
        getClientRects,
        isVisible,
        isAtStart,
        isAtEnd,
        isEqual,
        getNode
      };
    };
    CaretPosition.fromRangeStart = range => CaretPosition(range.startContainer, range.startOffset);
    CaretPosition.fromRangeEnd = range => CaretPosition(range.endContainer, range.endOffset);
    CaretPosition.after = node => CaretPosition(node.parentNode, nodeIndex$1(node) + 1);
    CaretPosition.before = node => CaretPosition(node.parentNode, nodeIndex$1(node));
    CaretPosition.isAbove = (pos1, pos2) => lift2(head(pos2.getClientRects()), last$3(pos1.getClientRects()), isAbove$1).getOr(false);
    CaretPosition.isBelow = (pos1, pos2) => lift2(last$3(pos2.getClientRects()), head(pos1.getClientRects()), isBelow$1).getOr(false);
    CaretPosition.isAtStart = pos => pos ? pos.isAtStart() : false;
    CaretPosition.isAtEnd = pos => pos ? pos.isAtEnd() : false;
    CaretPosition.isTextPosition = pos => pos ? isText$a(pos.container()) : false;
    CaretPosition.isElementPosition = pos => !CaretPosition.isTextPosition(pos);

    const trimEmptyTextNode$1 = (dom, node) => {
      if (isText$a(node) && node.data.length === 0) {
        dom.remove(node);
      }
    };
    const insertNode = (dom, rng, node) => {
      rng.insertNode(node);
      trimEmptyTextNode$1(dom, node.previousSibling);
      trimEmptyTextNode$1(dom, node.nextSibling);
    };
    const insertFragment = (dom, rng, frag) => {
      const firstChild = Optional.from(frag.firstChild);
      const lastChild = Optional.from(frag.lastChild);
      rng.insertNode(frag);
      firstChild.each(child => trimEmptyTextNode$1(dom, child.previousSibling));
      lastChild.each(child => trimEmptyTextNode$1(dom, child.nextSibling));
    };
    const rangeInsertNode = (dom, rng, node) => {
      if (isDocumentFragment(node)) {
        insertFragment(dom, rng, node);
      } else {
        insertNode(dom, rng, node);
      }
    };

    const isText$6 = isText$a;
    const isBogus = isBogus$2;
    const nodeIndex = DOMUtils.nodeIndex;
    const normalizedParent = node => {
      const parentNode = node.parentNode;
      if (isBogus(parentNode)) {
        return normalizedParent(parentNode);
      }
      return parentNode;
    };
    const getChildNodes = node => {
      if (!node) {
        return [];
      }
      return reduce(node.childNodes, (result, node) => {
        if (isBogus(node) && node.nodeName !== 'BR') {
          result = result.concat(getChildNodes(node));
        } else {
          result.push(node);
        }
        return result;
      }, []);
    };
    const normalizedTextOffset = (node, offset) => {
      let tempNode = node;
      while (tempNode = tempNode.previousSibling) {
        if (!isText$6(tempNode)) {
          break;
        }
        offset += tempNode.data.length;
      }
      return offset;
    };
    const equal = a => b => a === b;
    const normalizedNodeIndex = node => {
      let nodes, index;
      nodes = getChildNodes(normalizedParent(node));
      index = findIndex$1(nodes, equal(node), node);
      nodes = nodes.slice(0, index + 1);
      const numTextFragments = reduce(nodes, (result, node, i) => {
        if (isText$6(node) && isText$6(nodes[i - 1])) {
          result++;
        }
        return result;
      }, 0);
      nodes = filter$3(nodes, matchNodeNames([node.nodeName]));
      index = findIndex$1(nodes, equal(node), node);
      return index - numTextFragments;
    };
    const createPathItem = node => {
      const name = isText$6(node) ? 'text()' : node.nodeName.toLowerCase();
      return name + '[' + normalizedNodeIndex(node) + ']';
    };
    const parentsUntil$1 = (root, node, predicate) => {
      const parents = [];
      for (let tempNode = node.parentNode; tempNode && tempNode !== root; tempNode = tempNode.parentNode) {
        if (predicate && predicate(tempNode)) {
          break;
        }
        parents.push(tempNode);
      }
      return parents;
    };
    const create$b = (root, caretPosition) => {
      let path = [];
      let container = caretPosition.container();
      let offset = caretPosition.offset();
      let outputOffset;
      if (isText$6(container)) {
        outputOffset = normalizedTextOffset(container, offset);
      } else {
        const childNodes = container.childNodes;
        if (offset >= childNodes.length) {
          outputOffset = 'after';
          offset = childNodes.length - 1;
        } else {
          outputOffset = 'before';
        }
        container = childNodes[offset];
      }
      path.push(createPathItem(container));
      let parents = parentsUntil$1(root, container);
      parents = filter$3(parents, not(isBogus$2));
      path = path.concat(map$1(parents, node => {
        return createPathItem(node);
      }));
      return path.reverse().join('/') + ',' + outputOffset;
    };
    const resolvePathItem = (node, name, index) => {
      let nodes = getChildNodes(node);
      nodes = filter$3(nodes, (node, index) => {
        return !isText$6(node) || !isText$6(nodes[index - 1]);
      });
      nodes = filter$3(nodes, matchNodeNames([name]));
      return nodes[index];
    };
    const findTextPosition = (container, offset) => {
      let node = container;
      let targetOffset = 0;
      while (isText$6(node)) {
        const dataLen = node.data.length;
        if (offset >= targetOffset && offset <= targetOffset + dataLen) {
          container = node;
          offset = offset - targetOffset;
          break;
        }
        if (!isText$6(node.nextSibling)) {
          container = node;
          offset = dataLen;
          break;
        }
        targetOffset += dataLen;
        node = node.nextSibling;
      }
      if (isText$6(container) && offset > container.data.length) {
        offset = container.data.length;
      }
      return CaretPosition(container, offset);
    };
    const resolve$1 = (root, path) => {
      if (!path) {
        return null;
      }
      const parts = path.split(',');
      const paths = parts[0].split('/');
      const offset = parts.length > 1 ? parts[1] : 'before';
      const container = reduce(paths, (result, value) => {
        const match = /([\w\-\(\)]+)\[([0-9]+)\]/.exec(value);
        if (!match) {
          return null;
        }
        if (match[1] === 'text()') {
          match[1] = '#text';
        }
        return resolvePathItem(result, match[1], parseInt(match[2], 10));
      }, root);
      if (!container) {
        return null;
      }
      if (!isText$6(container) && container.parentNode) {
        let nodeOffset;
        if (offset === 'after') {
          nodeOffset = nodeIndex(container) + 1;
        } else {
          nodeOffset = nodeIndex(container);
        }
        return CaretPosition(container.parentNode, nodeOffset);
      }
      return findTextPosition(container, parseInt(offset, 10));
    };

    const isContentEditableFalse$9 = isContentEditableFalse$b;
    const getNormalizedTextOffset$1 = (trim, container, offset) => {
      let trimmedOffset = trim(container.data.slice(0, offset)).length;
      for (let node = container.previousSibling; node && isText$a(node); node = node.previousSibling) {
        trimmedOffset += trim(node.data).length;
      }
      return trimmedOffset;
    };
    const getPoint = (dom, trim, normalized, rng, start) => {
      const container = start ? rng.startContainer : rng.endContainer;
      let offset = start ? rng.startOffset : rng.endOffset;
      const point = [];
      const root = dom.getRoot();
      if (isText$a(container)) {
        point.push(normalized ? getNormalizedTextOffset$1(trim, container, offset) : offset);
      } else {
        let after = 0;
        const childNodes = container.childNodes;
        if (offset >= childNodes.length && childNodes.length) {
          after = 1;
          offset = Math.max(0, childNodes.length - 1);
        }
        point.push(dom.nodeIndex(childNodes[offset], normalized) + after);
      }
      for (let node = container; node && node !== root; node = node.parentNode) {
        point.push(dom.nodeIndex(node, normalized));
      }
      return point;
    };
    const getLocation = (trim, selection, normalized, rng) => {
      const dom = selection.dom;
      const start = getPoint(dom, trim, normalized, rng, true);
      const forward = selection.isForward();
      const fakeCaret = isRangeInCaretContainerBlock(rng) ? { isFakeCaret: true } : {};
      if (!selection.isCollapsed()) {
        const end = getPoint(dom, trim, normalized, rng, false);
        return {
          start,
          end,
          forward,
          ...fakeCaret
        };
      } else {
        return {
          start,
          forward,
          ...fakeCaret
        };
      }
    };
    const findIndex = (dom, name, element) => {
      let count = 0;
      Tools.each(dom.select(name), node => {
        if (node.getAttribute('data-mce-bogus') === 'all') {
          return;
        } else if (node === element) {
          return false;
        } else {
          count++;
          return;
        }
      });
      return count;
    };
    const moveEndPoint$1 = (rng, start) => {
      let container = start ? rng.startContainer : rng.endContainer;
      let offset = start ? rng.startOffset : rng.endOffset;
      if (isElement$6(container) && container.nodeName === 'TR') {
        const childNodes = container.childNodes;
        container = childNodes[Math.min(start ? offset : offset - 1, childNodes.length - 1)];
        if (container) {
          offset = start ? 0 : container.childNodes.length;
          if (start) {
            rng.setStart(container, offset);
          } else {
            rng.setEnd(container, offset);
          }
        }
      }
    };
    const normalizeTableCellSelection = rng => {
      moveEndPoint$1(rng, true);
      moveEndPoint$1(rng, false);
      return rng;
    };
    const findSibling = (node, offset) => {
      if (isElement$6(node)) {
        node = getNode$1(node, offset);
        if (isContentEditableFalse$9(node)) {
          return node;
        }
      }
      if (isCaretContainer$2(node)) {
        if (isText$a(node) && isCaretContainerBlock$1(node)) {
          node = node.parentNode;
        }
        let sibling = node.previousSibling;
        if (isContentEditableFalse$9(sibling)) {
          return sibling;
        }
        sibling = node.nextSibling;
        if (isContentEditableFalse$9(sibling)) {
          return sibling;
        }
      }
      return undefined;
    };
    const findAdjacentContentEditableFalseElm = rng => {
      return findSibling(rng.startContainer, rng.startOffset) || findSibling(rng.endContainer, rng.endOffset);
    };
    const getOffsetBookmark = (trim, normalized, selection) => {
      const element = selection.getNode();
      const rng = selection.getRng();
      if (element.nodeName === 'IMG' || isContentEditableFalse$9(element)) {
        const name = element.nodeName;
        return {
          name,
          index: findIndex(selection.dom, name, element)
        };
      }
      const sibling = findAdjacentContentEditableFalseElm(rng);
      if (sibling) {
        const name = sibling.tagName;
        return {
          name,
          index: findIndex(selection.dom, name, sibling)
        };
      }
      return getLocation(trim, selection, normalized, rng);
    };
    const getCaretBookmark = selection => {
      const rng = selection.getRng();
      return {
        start: create$b(selection.dom.getRoot(), CaretPosition.fromRangeStart(rng)),
        end: create$b(selection.dom.getRoot(), CaretPosition.fromRangeEnd(rng)),
        forward: selection.isForward()
      };
    };
    const getRangeBookmark = selection => {
      return {
        rng: selection.getRng(),
        forward: selection.isForward()
      };
    };
    const createBookmarkSpan = (dom, id, filled) => {
      const args = {
        'data-mce-type': 'bookmark',
        id,
        'style': 'overflow:hidden;line-height:0px'
      };
      return filled ? dom.create('span', args, '&#xFEFF;') : dom.create('span', args);
    };
    const getPersistentBookmark = (selection, filled) => {
      const dom = selection.dom;
      let rng = selection.getRng();
      const id = dom.uniqueId();
      const collapsed = selection.isCollapsed();
      const element = selection.getNode();
      const name = element.nodeName;
      const forward = selection.isForward();
      if (name === 'IMG') {
        return {
          name,
          index: findIndex(dom, name, element)
        };
      }
      const rng2 = normalizeTableCellSelection(rng.cloneRange());
      if (!collapsed) {
        rng2.collapse(false);
        const endBookmarkNode = createBookmarkSpan(dom, id + '_end', filled);
        rangeInsertNode(dom, rng2, endBookmarkNode);
      }
      rng = normalizeTableCellSelection(rng);
      rng.collapse(true);
      const startBookmarkNode = createBookmarkSpan(dom, id + '_start', filled);
      rangeInsertNode(dom, rng, startBookmarkNode);
      selection.moveToBookmark({
        id,
        keep: true,
        forward
      });
      return {
        id,
        forward
      };
    };
    const getBookmark$2 = (selection, type, normalized = false) => {
      if (type === 2) {
        return getOffsetBookmark(trim$2, normalized, selection);
      } else if (type === 3) {
        return getCaretBookmark(selection);
      } else if (type) {
        return getRangeBookmark(selection);
      } else {
        return getPersistentBookmark(selection, false);
      }
    };
    const getUndoBookmark = curry(getOffsetBookmark, identity, true);

    const value$1 = value => {
      const applyHelper = fn => fn(value);
      const constHelper = constant(value);
      const outputHelper = () => output;
      const output = {
        tag: true,
        inner: value,
        fold: (_onError, onValue) => onValue(value),
        isValue: always,
        isError: never,
        map: mapper => Result.value(mapper(value)),
        mapError: outputHelper,
        bind: applyHelper,
        exists: applyHelper,
        forall: applyHelper,
        getOr: constHelper,
        or: outputHelper,
        getOrThunk: constHelper,
        orThunk: outputHelper,
        getOrDie: constHelper,
        each: fn => {
          fn(value);
        },
        toOptional: () => Optional.some(value)
      };
      return output;
    };
    const error = error => {
      const outputHelper = () => output;
      const output = {
        tag: false,
        inner: error,
        fold: (onError, _onValue) => onError(error),
        isValue: never,
        isError: always,
        map: outputHelper,
        mapError: mapper => Result.error(mapper(error)),
        bind: outputHelper,
        exists: never,
        forall: always,
        getOr: identity,
        or: identity,
        getOrThunk: apply$1,
        orThunk: apply$1,
        getOrDie: die(String(error)),
        each: noop,
        toOptional: Optional.none
      };
      return output;
    };
    const fromOption = (optional, err) => optional.fold(() => error(err), value$1);
    const Result = {
      value: value$1,
      error,
      fromOption
    };

    const generate = cases => {
      if (!isArray$1(cases)) {
        throw new Error('cases must be an array');
      }
      if (cases.length === 0) {
        throw new Error('there must be at least one case');
      }
      const constructors = [];
      const adt = {};
      each$e(cases, (acase, count) => {
        const keys$1 = keys(acase);
        if (keys$1.length !== 1) {
          throw new Error('one and only one name per case');
        }
        const key = keys$1[0];
        const value = acase[key];
        if (adt[key] !== undefined) {
          throw new Error('duplicate key detected:' + key);
        } else if (key === 'cata') {
          throw new Error('cannot have a case named cata (sorry)');
        } else if (!isArray$1(value)) {
          throw new Error('case arguments must be an array');
        }
        constructors.push(key);
        adt[key] = (...args) => {
          const argLength = args.length;
          if (argLength !== value.length) {
            throw new Error('Wrong number of arguments to case ' + key + '. Expected ' + value.length + ' (' + value + '), got ' + argLength);
          }
          const match = branches => {
            const branchKeys = keys(branches);
            if (constructors.length !== branchKeys.length) {
              throw new Error('Wrong number of arguments to match. Expected: ' + constructors.join(',') + '\nActual: ' + branchKeys.join(','));
            }
            const allReqd = forall(constructors, reqKey => {
              return contains$2(branchKeys, reqKey);
            });
            if (!allReqd) {
              throw new Error('Not all branches were specified when using match. Specified: ' + branchKeys.join(', ') + '\nRequired: ' + constructors.join(', '));
            }
            return branches[key].apply(null, args);
          };
          return {
            fold: (...foldArgs) => {
              if (foldArgs.length !== cases.length) {
                throw new Error('Wrong number of arguments to fold. Expected ' + cases.length + ', got ' + foldArgs.length);
              }
              const target = foldArgs[count];
              return target.apply(null, args);
            },
            match,
            log: label => {
              console.log(label, {
                constructors,
                constructor: key,
                params: args
              });
            }
          };
        };
      });
      return adt;
    };
    const Adt = { generate };

    Adt.generate([
      {
        bothErrors: [
          'error1',
          'error2'
        ]
      },
      {
        firstError: [
          'error1',
          'value2'
        ]
      },
      {
        secondError: [
          'value1',
          'error2'
        ]
      },
      {
        bothValues: [
          'value1',
          'value2'
        ]
      }
    ]);
    const partition$1 = results => {
      const errors = [];
      const values = [];
      each$e(results, result => {
        result.fold(err => {
          errors.push(err);
        }, value => {
          values.push(value);
        });
      });
      return {
        errors,
        values
      };
    };

    const isInlinePattern = pattern => pattern.type === 'inline-command' || pattern.type === 'inline-format';
    const isBlockPattern = pattern => pattern.type === 'block-command' || pattern.type === 'block-format';
    const normalizePattern = pattern => {
      const err = message => Result.error({
        message,
        pattern
      });
      const formatOrCmd = (name, onFormat, onCommand) => {
        if (pattern.format !== undefined) {
          let formats;
          if (isArray$1(pattern.format)) {
            if (!forall(pattern.format, isString)) {
              return err(name + ' pattern has non-string items in the `format` array');
            }
            formats = pattern.format;
          } else if (isString(pattern.format)) {
            formats = [pattern.format];
          } else {
            return err(name + ' pattern has non-string `format` parameter');
          }
          return Result.value(onFormat(formats));
        } else if (pattern.cmd !== undefined) {
          if (!isString(pattern.cmd)) {
            return err(name + ' pattern has non-string `cmd` parameter');
          }
          return Result.value(onCommand(pattern.cmd, pattern.value));
        } else {
          return err(name + ' pattern is missing both `format` and `cmd` parameters');
        }
      };
      if (!isObject(pattern)) {
        return err('Raw pattern is not an object');
      }
      if (!isString(pattern.start)) {
        return err('Raw pattern is missing `start` parameter');
      }
      if (pattern.end !== undefined) {
        if (!isString(pattern.end)) {
          return err('Inline pattern has non-string `end` parameter');
        }
        if (pattern.start.length === 0 && pattern.end.length === 0) {
          return err('Inline pattern has empty `start` and `end` parameters');
        }
        let start = pattern.start;
        let end = pattern.end;
        if (end.length === 0) {
          end = start;
          start = '';
        }
        return formatOrCmd('Inline', format => ({
          type: 'inline-format',
          start,
          end,
          format
        }), (cmd, value) => ({
          type: 'inline-command',
          start,
          end,
          cmd,
          value
        }));
      } else if (pattern.replacement !== undefined) {
        if (!isString(pattern.replacement)) {
          return err('Replacement pattern has non-string `replacement` parameter');
        }
        if (pattern.start.length === 0) {
          return err('Replacement pattern has empty `start` parameter');
        }
        return Result.value({
          type: 'inline-command',
          start: '',
          end: pattern.start,
          cmd: 'mceInsertContent',
          value: pattern.replacement
        });
      } else {
        if (pattern.start.length === 0) {
          return err('Block pattern has empty `start` parameter');
        }
        return formatOrCmd('Block', formats => ({
          type: 'block-format',
          start: pattern.start,
          format: formats[0]
        }), (command, commandValue) => ({
          type: 'block-command',
          start: pattern.start,
          cmd: command,
          value: commandValue
        }));
      }
    };
    const getBlockPatterns = patterns => filter$5(patterns, isBlockPattern);
    const getInlinePatterns = patterns => filter$5(patterns, isInlinePattern);
    const createPatternSet = (patterns, dynamicPatternsLookup) => ({
      inlinePatterns: getInlinePatterns(patterns),
      blockPatterns: getBlockPatterns(patterns),
      dynamicPatternsLookup
    });
    const fromRawPatterns = patterns => {
      const normalized = partition$1(map$3(patterns, normalizePattern));
      each$e(normalized.errors, err => console.error(err.message, err.pattern));
      return normalized.values;
    };
    const fromRawPatternsLookup = lookupFn => {
      return ctx => {
        const rawPatterns = lookupFn(ctx);
        return fromRawPatterns(rawPatterns);
      };
    };

    const deviceDetection$1 = detect$2().deviceType;
    const isTouch = deviceDetection$1.isTouch();
    const DOM$a = DOMUtils.DOM;
    const getHash = value => {
      const items = value.indexOf('=') > 0 ? value.split(/[;,](?![^=;,]*(?:[;,]|$))/) : value.split(',');
      return foldl(items, (output, item) => {
        const arr = item.split('=');
        const key = arr[0];
        const val = arr.length > 1 ? arr[1] : key;
        output[trim$4(key)] = trim$4(val);
        return output;
      }, {});
    };
    const isRegExp = x => is$4(x, RegExp);
    const option = name => editor => editor.options.get(name);
    const stringOrObjectProcessor = value => isString(value) || isObject(value);
    const bodyOptionProcessor = (editor, defaultValue = '') => value => {
      const valid = isString(value);
      if (valid) {
        if (value.indexOf('=') !== -1) {
          const bodyObj = getHash(value);
          return {
            value: get$a(bodyObj, editor.id).getOr(defaultValue),
            valid
          };
        } else {
          return {
            value,
            valid
          };
        }
      } else {
        return {
          valid: false,
          message: 'Must be a string.'
        };
      }
    };
    const register$7 = editor => {
      const registerOption = editor.options.register;
      registerOption('id', {
        processor: 'string',
        default: editor.id
      });
      registerOption('selector', { processor: 'string' });
      registerOption('target', { processor: 'object' });
      registerOption('suffix', { processor: 'string' });
      registerOption('cache_suffix', { processor: 'string' });
      registerOption('base_url', { processor: 'string' });
      registerOption('referrer_policy', {
        processor: 'string',
        default: ''
      });
      registerOption('language_load', {
        processor: 'boolean',
        default: true
      });
      registerOption('inline', {
        processor: 'boolean',
        default: false
      });
      registerOption('iframe_attrs', {
        processor: 'object',
        default: {}
      });
      registerOption('doctype', {
        processor: 'string',
        default: '<!DOCTYPE html>'
      });
      registerOption('document_base_url', {
        processor: 'string',
        default: editor.documentBaseUrl
      });
      registerOption('body_id', {
        processor: bodyOptionProcessor(editor, 'tinymce'),
        default: 'tinymce'
      });
      registerOption('body_class', {
        processor: bodyOptionProcessor(editor),
        default: ''
      });
      registerOption('content_security_policy', {
        processor: 'string',
        default: ''
      });
      registerOption('br_in_pre', {
        processor: 'boolean',
        default: true
      });
      registerOption('forced_root_block', {
        processor: value => {
          const valid = isString(value) && isNotEmpty(value);
          if (valid) {
            return {
              value,
              valid
            };
          } else {
            return {
              valid: false,
              message: 'Must be a non-empty string.'
            };
          }
        },
        default: 'p'
      });
      registerOption('forced_root_block_attrs', {
        processor: 'object',
        default: {}
      });
      registerOption('newline_behavior', {
        processor: value => {
          const valid = contains$2([
            'block',
            'linebreak',
            'invert',
            'default'
          ], value);
          return valid ? {
            value,
            valid
          } : {
            valid: false,
            message: 'Must be one of: block, linebreak, invert or default.'
          };
        },
        default: 'default'
      });
      registerOption('br_newline_selector', {
        processor: 'string',
        default: '.mce-toc h2,figcaption,caption'
      });
      registerOption('no_newline_selector', {
        processor: 'string',
        default: ''
      });
      registerOption('keep_styles', {
        processor: 'boolean',
        default: true
      });
      registerOption('end_container_on_empty_block', {
        processor: value => {
          if (isBoolean(value)) {
            return {
              valid: true,
              value
            };
          } else if (isString(value)) {
            return {
              valid: true,
              value
            };
          } else {
            return {
              valid: false,
              message: 'Must be boolean or a string'
            };
          }
        },
        default: 'blockquote'
      });
      registerOption('font_size_style_values', {
        processor: 'string',
        default: 'xx-small,x-small,small,medium,large,x-large,xx-large'
      });
      registerOption('font_size_legacy_values', {
        processor: 'string',
        default: 'xx-small,small,medium,large,x-large,xx-large,300%'
      });
      registerOption('font_size_classes', {
        processor: 'string',
        default: ''
      });
      registerOption('automatic_uploads', {
        processor: 'boolean',
        default: true
      });
      registerOption('images_reuse_filename', {
        processor: 'boolean',
        default: false
      });
      registerOption('images_replace_blob_uris', {
        processor: 'boolean',
        default: true
      });
      registerOption('icons', {
        processor: 'string',
        default: ''
      });
      registerOption('icons_url', {
        processor: 'string',
        default: ''
      });
      registerOption('images_upload_url', {
        processor: 'string',
        default: ''
      });
      registerOption('images_upload_base_path', {
        processor: 'string',
        default: ''
      });
      registerOption('images_upload_credentials', {
        processor: 'boolean',
        default: false
      });
      registerOption('images_upload_handler', { processor: 'function' });
      registerOption('language', {
        processor: 'string',
        default: 'en'
      });
      registerOption('language_url', {
        processor: 'string',
        default: ''
      });
      registerOption('entity_encoding', {
        processor: 'string',
        default: 'named'
      });
      registerOption('indent', {
        processor: 'boolean',
        default: true
      });
      registerOption('indent_before', {
        processor: 'string',
        default: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' + 'tfoot,tbody,tr,section,details,summary,article,hgroup,aside,figure,figcaption,option,optgroup,datalist'
      });
      registerOption('indent_after', {
        processor: 'string',
        default: 'p,h1,h2,h3,h4,h5,h6,blockquote,div,title,style,pre,script,td,th,ul,ol,li,dl,dt,dd,area,table,thead,' + 'tfoot,tbody,tr,section,details,summary,article,hgroup,aside,figure,figcaption,option,optgroup,datalist'
      });
      registerOption('indent_use_margin', {
        processor: 'boolean',
        default: false
      });
      registerOption('indentation', {
        processor: 'string',
        default: '40px'
      });
      registerOption('content_css', {
        processor: value => {
          const valid = value === false || isString(value) || isArrayOf(value, isString);
          if (valid) {
            if (isString(value)) {
              return {
                value: map$3(value.split(','), trim$4),
                valid
              };
            } else if (isArray$1(value)) {
              return {
                value,
                valid
              };
            } else if (value === false) {
              return {
                value: [],
                valid
              };
            } else {
              return {
                value,
                valid
              };
            }
          } else {
            return {
              valid: false,
              message: 'Must be false, a string or an array of strings.'
            };
          }
        },
        default: isInline$1(editor) ? [] : ['default']
      });
      registerOption('content_style', { processor: 'string' });
      registerOption('content_css_cors', {
        processor: 'boolean',
        default: false
      });
      registerOption('font_css', {
        processor: value => {
          const valid = isString(value) || isArrayOf(value, isString);
          if (valid) {
            const newValue = isArray$1(value) ? value : map$3(value.split(','), trim$4);
            return {
              value: newValue,
              valid
            };
          } else {
            return {
              valid: false,
              message: 'Must be a string or an array of strings.'
            };
          }
        },
        default: []
      });
      registerOption('inline_boundaries', {
        processor: 'boolean',
        default: true
      });
      registerOption('inline_boundaries_selector', {
        processor: 'string',
        default: 'a[href],code,span.mce-annotation'
      });
      registerOption('object_resizing', {
        processor: value => {
          const valid = isBoolean(value) || isString(value);
          if (valid) {
            if (value === false || deviceDetection$1.isiPhone() || deviceDetection$1.isiPad()) {
              return {
                value: '',
                valid
              };
            } else {
              return {
                value: value === true ? 'table,img,figure.image,div,video,iframe' : value,
                valid
              };
            }
          } else {
            return {
              valid: false,
              message: 'Must be boolean or a string'
            };
          }
        },
        default: !isTouch
      });
      registerOption('resize_img_proportional', {
        processor: 'boolean',
        default: true
      });
      registerOption('event_root', { processor: 'object' });
      registerOption('service_message', { processor: 'string' });
      registerOption('theme', {
        processor: value => value === false || isString(value) || isFunction(value),
        default: 'silver'
      });
      registerOption('theme_url', { processor: 'string' });
      registerOption('formats', { processor: 'object' });
      registerOption('format_empty_lines', {
        processor: 'boolean',
        default: false
      });
      registerOption('format_noneditable_selector', {
        processor: 'string',
        default: ''
      });
      registerOption('preview_styles', {
        processor: value => {
          const valid = value === false || isString(value);
          if (valid) {
            return {
              value: value === false ? '' : value,
              valid
            };
          } else {
            return {
              valid: false,
              message: 'Must be false or a string'
            };
          }
        },
        default: 'font-family font-size font-weight font-style text-decoration text-transform color background-color border border-radius outline text-shadow'
      });
      registerOption('custom_ui_selector', {
        processor: 'string',
        default: ''
      });
      registerOption('hidden_input', {
        processor: 'boolean',
        default: true
      });
      registerOption('submit_patch', {
        processor: 'boolean',
        default: true
      });
      registerOption('encoding', { processor: 'string' });
      registerOption('add_form_submit_trigger', {
        processor: 'boolean',
        default: true
      });
      registerOption('add_unload_trigger', {
        processor: 'boolean',
        default: true
      });
      registerOption('custom_undo_redo_levels', {
        processor: 'number',
        default: 0
      });
      registerOption('disable_nodechange', {
        processor: 'boolean',
        default: false
      });
      registerOption('readonly', {
        processor: 'boolean',
        default: false
      });
      registerOption('editable_root', {
        processor: 'boolean',
        default: true
      });
      registerOption('plugins', {
        processor: 'string[]',
        default: []
      });
      registerOption('external_plugins', { processor: 'object' });
      registerOption('forced_plugins', { processor: 'string[]' });
      registerOption('model', {
        processor: 'string',
        default: editor.hasPlugin('rtc') ? 'plugin' : 'dom'
      });
      registerOption('model_url', { processor: 'string' });
      registerOption('block_unsupported_drop', {
        processor: 'boolean',
        default: true
      });
      registerOption('visual', {
        processor: 'boolean',
        default: true
      });
      registerOption('visual_table_class', {
        processor: 'string',
        default: 'mce-item-table'
      });
      registerOption('visual_anchor_class', {
        processor: 'string',
        default: 'mce-item-anchor'
      });
      registerOption('iframe_aria_text', {
        processor: 'string',
        default: 'Rich Text Area. Press ALT-0 for help.'
      });
      registerOption('setup', { processor: 'function' });
      registerOption('init_instance_callback', { processor: 'function' });
      registerOption('url_converter', {
        processor: 'function',
        default: editor.convertURL
      });
      registerOption('url_converter_scope', {
        processor: 'object',
        default: editor
      });
      registerOption('urlconverter_callback', { processor: 'function' });
      registerOption('allow_conditional_comments', {
        processor: 'boolean',
        default: false
      });
      registerOption('allow_html_data_urls', {
        processor: 'boolean',
        default: false
      });
      registerOption('allow_svg_data_urls', { processor: 'boolean' });
      registerOption('allow_html_in_named_anchor', {
        processor: 'boolean',
        default: false
      });
      registerOption('allow_script_urls', {
        processor: 'boolean',
        default: false
      });
      registerOption('allow_unsafe_link_target', {
        processor: 'boolean',
        default: false
      });
      registerOption('convert_fonts_to_spans', {
        processor: 'boolean',
        default: true,
        deprecated: true
      });
      registerOption('fix_list_elements', {
        processor: 'boolean',
        default: false
      });
      registerOption('preserve_cdata', {
        processor: 'boolean',
        default: false
      });
      registerOption('remove_trailing_brs', {
        processor: 'boolean',
        default: true
      });
      registerOption('pad_empty_with_br', {
        processor: 'boolean',
        default: false
      });
      registerOption('inline_styles', {
        processor: 'boolean',
        default: true,
        deprecated: true
      });
      registerOption('element_format', {
        processor: 'string',
        default: 'html'
      });
      registerOption('entities', { processor: 'string' });
      registerOption('schema', {
        processor: 'string',
        default: 'html5'
      });
      registerOption('convert_urls', {
        processor: 'boolean',
        default: true
      });
      registerOption('relative_urls', {
        processor: 'boolean',
        default: true
      });
      registerOption('remove_script_host', {
        processor: 'boolean',
        default: true
      });
      registerOption('custom_elements', { processor: 'string' });
      registerOption('extended_valid_elements', { processor: 'string' });
      registerOption('invalid_elements', { processor: 'string' });
      registerOption('invalid_styles', { processor: stringOrObjectProcessor });
      registerOption('valid_children', { processor: 'string' });
      registerOption('valid_classes', { processor: stringOrObjectProcessor });
      registerOption('valid_elements', { processor: 'string' });
      registerOption('valid_styles', { processor: stringOrObjectProcessor });
      registerOption('verify_html', {
        processor: 'boolean',
        default: true
      });
      registerOption('auto_focus', { processor: value => isString(value) || value === true });
      registerOption('browser_spellcheck', {
        processor: 'boolean',
        default: false
      });
      registerOption('protect', { processor: 'array' });
      registerOption('images_file_types', {
        processor: 'string',
        default: 'jpeg,jpg,jpe,jfi,jif,jfif,png,gif,bmp,webp'
      });
      registerOption('deprecation_warnings', {
        processor: 'boolean',
        default: true
      });
      registerOption('a11y_advanced_options', {
        processor: 'boolean',
        default: false
      });
      registerOption('api_key', { processor: 'string' });
      registerOption('paste_block_drop', {
        processor: 'boolean',
        default: false
      });
      registerOption('paste_data_images', {
        processor: 'boolean',
        default: true
      });
      registerOption('paste_preprocess', { processor: 'function' });
      registerOption('paste_postprocess', { processor: 'function' });
      registerOption('paste_webkit_styles', {
        processor: 'string',
        default: 'none'
      });
      registerOption('paste_remove_styles_if_webkit', {
        processor: 'boolean',
        default: true
      });
      registerOption('paste_merge_formats', {
        processor: 'boolean',
        default: true
      });
      registerOption('smart_paste', {
        processor: 'boolean',
        default: true
      });
      registerOption('paste_as_text', {
        processor: 'boolean',
        default: false
      });
      registerOption('paste_tab_spaces', {
        processor: 'number',
        default: 4
      });
      registerOption('text_patterns', {
        processor: value => {
          if (isArrayOf(value, isObject) || value === false) {
            const patterns = value === false ? [] : value;
            return {
              value: fromRawPatterns(patterns),
              valid: true
            };
          } else {
            return {
              valid: false,
              message: 'Must be an array of objects or false.'
            };
          }
        },
        default: [
          {
            start: '*',
            end: '*',
            format: 'italic'
          },
          {
            start: '**',
            end: '**',
            format: 'bold'
          },
          {
            start: '#',
            format: 'h1'
          },
          {
            start: '##',
            format: 'h2'
          },
          {
            start: '###',
            format: 'h3'
          },
          {
            start: '####',
            format: 'h4'
          },
          {
            start: '#####',
            format: 'h5'
          },
          {
            start: '######',
            format: 'h6'
          },
          {
            start: '1. ',
            cmd: 'InsertOrderedList'
          },
          {
            start: '* ',
            cmd: 'InsertUnorderedList'
          },
          {
            start: '- ',
            cmd: 'InsertUnorderedList'
          }
        ]
      });
      registerOption('text_patterns_lookup', {
        processor: value => {
          if (isFunction(value)) {
            return {
              value: fromRawPatternsLookup(value),
              valid: true
            };
          } else {
            return {
              valid: false,
              message: 'Must be a single function'
            };
          }
        },
        default: _ctx => []
      });
      registerOption('noneditable_class', {
        processor: 'string',
        default: 'mceNonEditable'
      });
      registerOption('editable_class', {
        processor: 'string',
        default: 'mceEditable'
      });
      registerOption('noneditable_regexp', {
        processor: value => {
          if (isArrayOf(value, isRegExp)) {
            return {
              value,
              valid: true
            };
          } else if (isRegExp(value)) {
            return {
              value: [value],
              valid: true
            };
          } else {
            return {
              valid: false,
              message: 'Must be a RegExp or an array of RegExp.'
            };
          }
        },
        default: []
      });
      registerOption('table_tab_navigation', {
        processor: 'boolean',
        default: true
      });
      registerOption('highlight_on_focus', {
        processor: 'boolean',
        default: false
      });
      registerOption('xss_sanitization', {
        processor: 'boolean',
        default: true
      });
      registerOption('details_initial_state', {
        processor: value => {
          const valid = contains$2([
            'inherited',
            'collapsed',
            'expanded'
          ], value);
          return valid ? {
            value,
            valid
          } : {
            valid: false,
            message: 'Must be one of: inherited, collapsed, or expanded.'
          };
        },
        default: 'inherited'
      });
      registerOption('details_serialized_state', {
        processor: value => {
          const valid = contains$2([
            'inherited',
            'collapsed',
            'expanded'
          ], value);
          return valid ? {
            value,
            valid
          } : {
            valid: false,
            message: 'Must be one of: inherited, collapsed, or expanded.'
          };
        },
        default: 'inherited'
      });
      registerOption('init_content_sync', {
        processor: 'boolean',
        default: false
      });
      registerOption('newdocument_content', {
        processor: 'string',
        default: ''
      });
      registerOption('force_hex_color', {
        processor: value => {
          const options = [
            'always',
            'rgb_only',
            'off'
          ];
          const valid = contains$2(options, value);
          return valid ? {
            value,
            valid
          } : {
            valid: false,
            message: `Must be one of: ${ options.join(', ') }.`
          };
        },
        default: 'off'
      });
      registerOption('sandbox_iframes', {
        processor: 'boolean',
        default: false
      });
      registerOption('convert_unsafe_embeds', {
        processor: 'boolean',
        default: false
      });
      editor.on('ScriptsLoaded', () => {
        registerOption('directionality', {
          processor: 'string',
          default: I18n.isRtl() ? 'rtl' : undefined
        });
        registerOption('placeholder', {
          processor: 'string',
          default: DOM$a.getAttrib(editor.getElement(), 'placeholder')
        });
      });
    };
    const getIframeAttrs = option('iframe_attrs');
    const getDocType = option('doctype');
    const getDocumentBaseUrl = option('document_base_url');
    const getBodyId = option('body_id');
    const getBodyClass = option('body_class');
    const getContentSecurityPolicy = option('content_security_policy');
    const shouldPutBrInPre$1 = option('br_in_pre');
    const getForcedRootBlock = option('forced_root_block');
    const getForcedRootBlockAttrs = option('forced_root_block_attrs');
    const getNewlineBehavior = option('newline_behavior');
    const getBrNewLineSelector = option('br_newline_selector');
    const getNoNewLineSelector = option('no_newline_selector');
    const shouldKeepStyles = option('keep_styles');
    const shouldEndContainerOnEmptyBlock = option('end_container_on_empty_block');
    const isAutomaticUploadsEnabled = option('automatic_uploads');
    const shouldReuseFileName = option('images_reuse_filename');
    const shouldReplaceBlobUris = option('images_replace_blob_uris');
    const getIconPackName = option('icons');
    const getIconsUrl = option('icons_url');
    const getImageUploadUrl = option('images_upload_url');
    const getImageUploadBasePath = option('images_upload_base_path');
    const getImagesUploadCredentials = option('images_upload_credentials');
    const getImagesUploadHandler = option('images_upload_handler');
    const shouldUseContentCssCors = option('content_css_cors');
    const getReferrerPolicy = option('referrer_policy');
    const getLanguageCode = option('language');
    const getLanguageUrl = option('language_url');
    const shouldIndentUseMargin = option('indent_use_margin');
    const getIndentation = option('indentation');
    const getContentCss = option('content_css');
    const getContentStyle = option('content_style');
    const getFontCss = option('font_css');
    const getDirectionality = option('directionality');
    const getInlineBoundarySelector = option('inline_boundaries_selector');
    const getObjectResizing = option('object_resizing');
    const getResizeImgProportional = option('resize_img_proportional');
    const getPlaceholder = option('placeholder');
    const getEventRoot = option('event_root');
    const getServiceMessage = option('service_message');
    const getTheme = option('theme');
    const getThemeUrl = option('theme_url');
    const getModel = option('model');
    const getModelUrl = option('model_url');
    const isInlineBoundariesEnabled = option('inline_boundaries');
    const getFormats = option('formats');
    const getPreviewStyles = option('preview_styles');
    const canFormatEmptyLines = option('format_empty_lines');
    const getFormatNoneditableSelector = option('format_noneditable_selector');
    const getCustomUiSelector = option('custom_ui_selector');
    const isInline$1 = option('inline');
    const hasHiddenInput = option('hidden_input');
    const shouldPatchSubmit = option('submit_patch');
    const shouldAddFormSubmitTrigger = option('add_form_submit_trigger');
    const shouldAddUnloadTrigger = option('add_unload_trigger');
    const getCustomUndoRedoLevels = option('custom_undo_redo_levels');
    const shouldDisableNodeChange = option('disable_nodechange');
    const isReadOnly$1 = option('readonly');
    const hasEditableRoot$1 = option('editable_root');
    const hasContentCssCors = option('content_css_cors');
    const getPlugins = option('plugins');
    const getExternalPlugins$1 = option('external_plugins');
    const shouldBlockUnsupportedDrop = option('block_unsupported_drop');
    const isVisualAidsEnabled = option('visual');
    const getVisualAidsTableClass = option('visual_table_class');
    const getVisualAidsAnchorClass = option('visual_anchor_class');
    const getIframeAriaText = option('iframe_aria_text');
    const getSetupCallback = option('setup');
    const getInitInstanceCallback = option('init_instance_callback');
    const getUrlConverterCallback = option('urlconverter_callback');
    const getAutoFocus = option('auto_focus');
    const shouldBrowserSpellcheck = option('browser_spellcheck');
    const getProtect = option('protect');
    const shouldPasteBlockDrop = option('paste_block_drop');
    const shouldPasteDataImages = option('paste_data_images');
    const getPastePreProcess = option('paste_preprocess');
    const getPastePostProcess = option('paste_postprocess');
    const getNewDocumentContent = option('newdocument_content');
    const getPasteWebkitStyles = option('paste_webkit_styles');
    const shouldPasteRemoveWebKitStyles = option('paste_remove_styles_if_webkit');
    const shouldPasteMergeFormats = option('paste_merge_formats');
    const isSmartPasteEnabled = option('smart_paste');
    const isPasteAsTextEnabled = option('paste_as_text');
    const getPasteTabSpaces = option('paste_tab_spaces');
    const shouldAllowHtmlDataUrls = option('allow_html_data_urls');
    const getTextPatterns = option('text_patterns');
    const getTextPatternsLookup = option('text_patterns_lookup');
    const getNonEditableClass = option('noneditable_class');
    const getEditableClass = option('editable_class');
    const getNonEditableRegExps = option('noneditable_regexp');
    const shouldPreserveCData = option('preserve_cdata');
    const shouldHighlightOnFocus = option('highlight_on_focus');
    const shouldSanitizeXss = option('xss_sanitization');
    const shouldUseDocumentWrite = option('init_content_sync');
    const hasTextPatternsLookup = editor => editor.options.isSet('text_patterns_lookup');
    const getFontStyleValues = editor => Tools.explode(editor.options.get('font_size_style_values'));
    const getFontSizeClasses = editor => Tools.explode(editor.options.get('font_size_classes'));
    const isEncodingXml = editor => editor.options.get('encoding') === 'xml';
    const getAllowedImageFileTypes = editor => Tools.explode(editor.options.get('images_file_types'));
    const hasTableTabNavigation = option('table_tab_navigation');
    const getDetailsInitialState = option('details_initial_state');
    const getDetailsSerializedState = option('details_serialized_state');
    const shouldForceHexColor = option('force_hex_color');
    const shouldSandboxIframes = option('sandbox_iframes');

    const isElement$3 = isElement$6;
    const isText$5 = isText$a;
    const removeNode$1 = node => {
      const parentNode = node.parentNode;
      if (parentNode) {
        parentNode.removeChild(node);
      }
    };
    const trimCount = text => {
      const trimmedText = trim$2(text);
      return {
        count: text.length - trimmedText.length,
        text: trimmedText
      };
    };
    const deleteZwspChars = caretContainer => {
      let idx;
      while ((idx = caretContainer.data.lastIndexOf(ZWSP$1)) !== -1) {
        caretContainer.deleteData(idx, 1);
      }
    };
    const removeUnchanged = (caretContainer, pos) => {
      remove$3(caretContainer);
      return pos;
    };
    const removeTextAndReposition = (caretContainer, pos) => {
      const before = trimCount(caretContainer.data.substr(0, pos.offset()));
      const after = trimCount(caretContainer.data.substr(pos.offset()));
      const text = before.text + after.text;
      if (text.length > 0) {
        deleteZwspChars(caretContainer);
        return CaretPosition(caretContainer, pos.offset() - before.count);
      } else {
        return pos;
      }
    };
    const removeElementAndReposition = (caretContainer, pos) => {
      const parentNode = pos.container();
      const newPosition = indexOf$1(from(parentNode.childNodes), caretContainer).map(index => {
        return index < pos.offset() ? CaretPosition(parentNode, pos.offset() - 1) : pos;
      }).getOr(pos);
      remove$3(caretContainer);
      return newPosition;
    };
    const removeTextCaretContainer = (caretContainer, pos) => isText$5(caretContainer) && pos.container() === caretContainer ? removeTextAndReposition(caretContainer, pos) : removeUnchanged(caretContainer, pos);
    const removeElementCaretContainer = (caretContainer, pos) => pos.container() === caretContainer.parentNode ? removeElementAndReposition(caretContainer, pos) : removeUnchanged(caretContainer, pos);
    const removeAndReposition = (container, pos) => CaretPosition.isTextPosition(pos) ? removeTextCaretContainer(container, pos) : removeElementCaretContainer(container, pos);
    const remove$3 = caretContainerNode => {
      if (isElement$3(caretContainerNode) && isCaretContainer$2(caretContainerNode)) {
        if (hasContent(caretContainerNode)) {
          caretContainerNode.removeAttribute('data-mce-caret');
        } else {
          removeNode$1(caretContainerNode);
        }
      }
      if (isText$5(caretContainerNode)) {
        deleteZwspChars(caretContainerNode);
        if (caretContainerNode.data.length === 0) {
          removeNode$1(caretContainerNode);
        }
      }
    };

    const isContentEditableFalse$8 = isContentEditableFalse$b;
    const isMedia$1 = isMedia$2;
    const isTableCell$1 = isTableCell$3;
    const inlineFakeCaretSelector = '*[contentEditable=false],video,audio,embed,object';
    const getAbsoluteClientRect = (root, element, before) => {
      const clientRect = collapse(element.getBoundingClientRect(), before);
      let scrollX;
      let scrollY;
      if (root.tagName === 'BODY') {
        const docElm = root.ownerDocument.documentElement;
        scrollX = root.scrollLeft || docElm.scrollLeft;
        scrollY = root.scrollTop || docElm.scrollTop;
      } else {
        const rootRect = root.getBoundingClientRect();
        scrollX = root.scrollLeft - rootRect.left;
        scrollY = root.scrollTop - rootRect.top;
      }
      clientRect.left += scrollX;
      clientRect.right += scrollX;
      clientRect.top += scrollY;
      clientRect.bottom += scrollY;
      clientRect.width = 1;
      let margin = element.offsetWidth - element.clientWidth;
      if (margin > 0) {
        if (before) {
          margin *= -1;
        }
        clientRect.left += margin;
        clientRect.right += margin;
      }
      return clientRect;
    };
    const trimInlineCaretContainers = root => {
      var _a, _b;
      const fakeCaretTargetNodes = descendants(SugarElement.fromDom(root), inlineFakeCaretSelector);
      for (let i = 0; i < fakeCaretTargetNodes.length; i++) {
        const node = fakeCaretTargetNodes[i].dom;
        let sibling = node.previousSibling;
        if (endsWithCaretContainer$1(sibling)) {
          const data = sibling.data;
          if (data.length === 1) {
            (_a = sibling.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(sibling);
          } else {
            sibling.deleteData(data.length - 1, 1);
          }
        }
        sibling = node.nextSibling;
        if (startsWithCaretContainer$1(sibling)) {
          const data = sibling.data;
          if (data.length === 1) {
            (_b = sibling.parentNode) === null || _b === void 0 ? void 0 : _b.removeChild(sibling);
          } else {
            sibling.deleteData(0, 1);
          }
        }
      }
    };
    const FakeCaret = (editor, root, isBlock, hasFocus) => {
      const lastVisualCaret = value$2();
      let cursorInterval;
      let caretContainerNode;
      const caretBlock = getForcedRootBlock(editor);
      const dom = editor.dom;
      const show = (before, element) => {
        let rng;
        hide();
        if (isTableCell$1(element)) {
          return null;
        }
        if (isBlock(element)) {
          const caretContainer = insertBlock(caretBlock, element, before);
          const clientRect = getAbsoluteClientRect(root, element, before);
          dom.setStyle(caretContainer, 'top', clientRect.top);
          caretContainerNode = caretContainer;
          const caret = dom.create('div', {
            'class': 'mce-visual-caret',
            'data-mce-bogus': 'all'
          });
          dom.setStyles(caret, { ...clientRect });
          dom.add(root, caret);
          lastVisualCaret.set({
            caret,
            element,
            before
          });
          if (before) {
            dom.addClass(caret, 'mce-visual-caret-before');
          }
          startBlink();
          rng = element.ownerDocument.createRange();
          rng.setStart(caretContainer, 0);
          rng.setEnd(caretContainer, 0);
        } else {
          caretContainerNode = insertInline$1(element, before);
          rng = element.ownerDocument.createRange();
          if (isInlineFakeCaretTarget(caretContainerNode.nextSibling)) {
            rng.setStart(caretContainerNode, 0);
            rng.setEnd(caretContainerNode, 0);
          } else {
            rng.setStart(caretContainerNode, 1);
            rng.setEnd(caretContainerNode, 1);
          }
          return rng;
        }
        return rng;
      };
      const hide = () => {
        trimInlineCaretContainers(root);
        if (caretContainerNode) {
          remove$3(caretContainerNode);
          caretContainerNode = null;
        }
        lastVisualCaret.on(caretState => {
          dom.remove(caretState.caret);
          lastVisualCaret.clear();
        });
        if (cursorInterval) {
          clearInterval(cursorInterval);
          cursorInterval = undefined;
        }
      };
      const startBlink = () => {
        cursorInterval = setInterval(() => {
          lastVisualCaret.on(caretState => {
            if (hasFocus()) {
              dom.toggleClass(caretState.caret, 'mce-visual-caret-hidden');
            } else {
              dom.addClass(caretState.caret, 'mce-visual-caret-hidden');
            }
          });
        }, 500);
      };
      const reposition = () => {
        lastVisualCaret.on(caretState => {
          const clientRect = getAbsoluteClientRect(root, caretState.element, caretState.before);
          dom.setStyles(caretState.caret, { ...clientRect });
        });
      };
      const destroy = () => clearInterval(cursorInterval);
      const getCss = () => '.mce-visual-caret {' + 'position: absolute;' + 'background-color: black;' + 'background-color: currentcolor;' + '}' + '.mce-visual-caret-hidden {' + 'display: none;' + '}' + '*[data-mce-caret] {' + 'position: absolute;' + 'left: -1000px;' + 'right: auto;' + 'top: 0;' + 'margin: 0;' + 'padding: 0;' + '}';
      return {
        show,
        hide,
        getCss,
        reposition,
        destroy
      };
    };
    const isFakeCaretTableBrowser = () => Env.browser.isFirefox();
    const isInlineFakeCaretTarget = node => isContentEditableFalse$8(node) || isMedia$1(node);
    const isFakeCaretTarget = node => {
      const isTarget = isInlineFakeCaretTarget(node) || isTable$2(node) && isFakeCaretTableBrowser();
      return isTarget && parentElement(SugarElement.fromDom(node)).exists(isEditable$2);
    };

    const isContentEditableTrue$1 = isContentEditableTrue$3;
    const isContentEditableFalse$7 = isContentEditableFalse$b;
    const isMedia = isMedia$2;
    const isBlockLike = matchStyleValues('display', 'block table table-cell table-caption list-item');
    const isCaretContainer = isCaretContainer$2;
    const isCaretContainerBlock = isCaretContainerBlock$1;
    const isElement$2 = isElement$6;
    const isText$4 = isText$a;
    const isCaretCandidate$1 = isCaretCandidate$3;
    const isForwards = direction => direction > 0;
    const isBackwards = direction => direction < 0;
    const skipCaretContainers = (walk, shallow) => {
      let node;
      while (node = walk(shallow)) {
        if (!isCaretContainerBlock(node)) {
          return node;
        }
      }
      return null;
    };
    const findNode = (node, direction, predicateFn, rootNode, shallow) => {
      const walker = new DomTreeWalker(node, rootNode);
      const isCefOrCaretContainer = isContentEditableFalse$7(node) || isCaretContainerBlock(node);
      let tempNode;
      if (isBackwards(direction)) {
        if (isCefOrCaretContainer) {
          tempNode = skipCaretContainers(walker.prev.bind(walker), true);
          if (predicateFn(tempNode)) {
            return tempNode;
          }
        }
        while (tempNode = skipCaretContainers(walker.prev.bind(walker), shallow)) {
          if (predicateFn(tempNode)) {
            return tempNode;
          }
        }
      }
      if (isForwards(direction)) {
        if (isCefOrCaretContainer) {
          tempNode = skipCaretContainers(walker.next.bind(walker), true);
          if (predicateFn(tempNode)) {
            return tempNode;
          }
        }
        while (tempNode = skipCaretContainers(walker.next.bind(walker), shallow)) {
          if (predicateFn(tempNode)) {
            return tempNode;
          }
        }
      }
      return null;
    };
    const getEditingHost = (node, rootNode) => {
      const isCETrue = node => isContentEditableTrue$1(node.dom);
      const isRoot = node => node.dom === rootNode;
      return ancestor$4(SugarElement.fromDom(node), isCETrue, isRoot).map(elm => elm.dom).getOr(rootNode);
    };
    const getParentBlock$3 = (node, rootNode) => {
      while (node && node !== rootNode) {
        if (isBlockLike(node)) {
          return node;
        }
        node = node.parentNode;
      }
      return null;
    };
    const isInSameBlock = (caretPosition1, caretPosition2, rootNode) => getParentBlock$3(caretPosition1.container(), rootNode) === getParentBlock$3(caretPosition2.container(), rootNode);
    const getChildNodeAtRelativeOffset = (relativeOffset, caretPosition) => {
      if (!caretPosition) {
        return Optional.none();
      }
      const container = caretPosition.container();
      const offset = caretPosition.offset();
      if (!isElement$2(container)) {
        return Optional.none();
      }
      return Optional.from(container.childNodes[offset + relativeOffset]);
    };
    const beforeAfter = (before, node) => {
      var _a;
      const doc = (_a = node.ownerDocument) !== null && _a !== void 0 ? _a : document;
      const range = doc.createRange();
      if (before) {
        range.setStartBefore(node);
        range.setEndBefore(node);
      } else {
        range.setStartAfter(node);
        range.setEndAfter(node);
      }
      return range;
    };
    const isNodesInSameBlock = (root, node1, node2) => getParentBlock$3(node1, root) === getParentBlock$3(node2, root);
    const lean = (left, root, node) => {
      const siblingName = left ? 'previousSibling' : 'nextSibling';
      let tempNode = node;
      while (tempNode && tempNode !== root) {
        let sibling = tempNode[siblingName];
        if (sibling && isCaretContainer(sibling)) {
          sibling = sibling[siblingName];
        }
        if (isContentEditableFalse$7(sibling) || isMedia(sibling)) {
          if (isNodesInSameBlock(root, sibling, tempNode)) {
            return sibling;
          }
          break;
        }
        if (isCaretCandidate$1(sibling)) {
          break;
        }
        tempNode = tempNode.parentNode;
      }
      return null;
    };
    const before$2 = curry(beforeAfter, true);
    const after$2 = curry(beforeAfter, false);
    const normalizeRange = (direction, root, range) => {
      let node;
      const leanLeft = curry(lean, true, root);
      const leanRight = curry(lean, false, root);
      const container = range.startContainer;
      const offset = range.startOffset;
      if (isCaretContainerBlock$1(container)) {
        const block = isText$4(container) ? container.parentNode : container;
        const location = block.getAttribute('data-mce-caret');
        if (location === 'before') {
          node = block.nextSibling;
          if (isFakeCaretTarget(node)) {
            return before$2(node);
          }
        }
        if (location === 'after') {
          node = block.previousSibling;
          if (isFakeCaretTarget(node)) {
            return after$2(node);
          }
        }
      }
      if (!range.collapsed) {
        return range;
      }
      if (isText$a(container)) {
        if (isCaretContainer(container)) {
          if (direction === 1) {
            node = leanRight(container);
            if (node) {
              return before$2(node);
            }
            node = leanLeft(container);
            if (node) {
              return after$2(node);
            }
          }
          if (direction === -1) {
            node = leanLeft(container);
            if (node) {
              return after$2(node);
            }
            node = leanRight(container);
            if (node) {
              return before$2(node);
            }
          }
          return range;
        }
        if (endsWithCaretContainer$1(container) && offset >= container.data.length - 1) {
          if (direction === 1) {
            node = leanRight(container);
            if (node) {
              return before$2(node);
            }
          }
          return range;
        }
        if (startsWithCaretContainer$1(container) && offset <= 1) {
          if (direction === -1) {
            node = leanLeft(container);
            if (node) {
              return after$2(node);
            }
          }
          return range;
        }
        if (offset === container.data.length) {
          node = leanRight(container);
          if (node) {
            return before$2(node);
          }
          return range;
        }
        if (offset === 0) {
          node = leanLeft(container);
          if (node) {
            return after$2(node);
          }
          return range;
        }
      }
      return range;
    };
    const getRelativeCefElm = (forward, caretPosition) => getChildNodeAtRelativeOffset(forward ? 0 : -1, caretPosition).filter(isContentEditableFalse$7);
    const getNormalizedRangeEndPoint = (direction, root, range) => {
      const normalizedRange = normalizeRange(direction, root, range);
      return direction === -1 ? CaretPosition.fromRangeStart(normalizedRange) : CaretPosition.fromRangeEnd(normalizedRange);
    };
    const getElementFromPosition = pos => Optional.from(pos.getNode()).map(SugarElement.fromDom);
    const getElementFromPrevPosition = pos => Optional.from(pos.getNode(true)).map(SugarElement.fromDom);
    const getVisualCaretPosition = (walkFn, caretPosition) => {
      let pos = caretPosition;
      while (pos = walkFn(pos)) {
        if (pos.isVisible()) {
          return pos;
        }
      }
      return pos;
    };
    const isMoveInsideSameBlock = (from, to) => {
      const inSameBlock = isInSameBlock(from, to);
      if (!inSameBlock && isBr$6(from.getNode())) {
        return true;
      }
      return inSameBlock;
    };

    var HDirection;
    (function (HDirection) {
      HDirection[HDirection['Backwards'] = -1] = 'Backwards';
      HDirection[HDirection['Forwards'] = 1] = 'Forwards';
    }(HDirection || (HDirection = {})));
    const isContentEditableFalse$6 = isContentEditableFalse$b;
    const isText$3 = isText$a;
    const isElement$1 = isElement$6;
    const isBr$2 = isBr$6;
    const isCaretCandidate = isCaretCandidate$3;
    const isAtomic = isAtomic$1;
    const isEditableCaretCandidate = isEditableCaretCandidate$1;
    const getParents$3 = (node, root) => {
      const parents = [];
      let tempNode = node;
      while (tempNode && tempNode !== root) {
        parents.push(tempNode);
        tempNode = tempNode.parentNode;
      }
      return parents;
    };
    const nodeAtIndex = (container, offset) => {
      if (container.hasChildNodes() && offset < container.childNodes.length) {
        return container.childNodes[offset];
      }
      return null;
    };
    const getCaretCandidatePosition = (direction, node) => {
      if (isForwards(direction)) {
        if (isCaretCandidate(node.previousSibling) && !isText$3(node.previousSibling)) {
          return CaretPosition.before(node);
        }
        if (isText$3(node)) {
          return CaretPosition(node, 0);
        }
      }
      if (isBackwards(direction)) {
        if (isCaretCandidate(node.nextSibling) && !isText$3(node.nextSibling)) {
          return CaretPosition.after(node);
        }
        if (isText$3(node)) {
          return CaretPosition(node, node.data.length);
        }
      }
      if (isBackwards(direction)) {
        if (isBr$2(node)) {
          return CaretPosition.before(node);
        }
        return CaretPosition.after(node);
      }
      return CaretPosition.before(node);
    };
    const moveForwardFromBr = (root, nextNode) => {
      const nextSibling = nextNode.nextSibling;
      if (nextSibling && isCaretCandidate(nextSibling)) {
        if (isText$3(nextSibling)) {
          return CaretPosition(nextSibling, 0);
        } else {
          return CaretPosition.before(nextSibling);
        }
      } else {
        return findCaretPosition$1(HDirection.Forwards, CaretPosition.after(nextNode), root);
      }
    };
    const findCaretPosition$1 = (direction, startPos, root) => {
      let node;
      let nextNode;
      let innerNode;
      let caretPosition;
      if (!isElement$1(root) || !startPos) {
        return null;
      }
      if (startPos.isEqual(CaretPosition.after(root)) && root.lastChild) {
        caretPosition = CaretPosition.after(root.lastChild);
        if (isBackwards(direction) && isCaretCandidate(root.lastChild) && isElement$1(root.lastChild)) {
          return isBr$2(root.lastChild) ? CaretPosition.before(root.lastChild) : caretPosition;
        }
      } else {
        caretPosition = startPos;
      }
      const container = caretPosition.container();
      let offset = caretPosition.offset();
      if (isText$3(container)) {
        if (isBackwards(direction) && offset > 0) {
          return CaretPosition(container, --offset);
        }
        if (isForwards(direction) && offset < container.length) {
          return CaretPosition(container, ++offset);
        }
        node = container;
      } else {
        if (isBackwards(direction) && offset > 0) {
          nextNode = nodeAtIndex(container, offset - 1);
          if (isCaretCandidate(nextNode)) {
            if (!isAtomic(nextNode)) {
              innerNode = findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
              if (innerNode) {
                if (isText$3(innerNode)) {
                  return CaretPosition(innerNode, innerNode.data.length);
                }
                return CaretPosition.after(innerNode);
              }
            }
            if (isText$3(nextNode)) {
              return CaretPosition(nextNode, nextNode.data.length);
            }
            return CaretPosition.before(nextNode);
          }
        }
        if (isForwards(direction) && offset < container.childNodes.length) {
          nextNode = nodeAtIndex(container, offset);
          if (isCaretCandidate(nextNode)) {
            if (isBr$2(nextNode)) {
              return moveForwardFromBr(root, nextNode);
            }
            if (!isAtomic(nextNode)) {
              innerNode = findNode(nextNode, direction, isEditableCaretCandidate, nextNode);
              if (innerNode) {
                if (isText$3(innerNode)) {
                  return CaretPosition(innerNode, 0);
                }
                return CaretPosition.before(innerNode);
              }
            }
            if (isText$3(nextNode)) {
              return CaretPosition(nextNode, 0);
            }
            return CaretPosition.after(nextNode);
          }
        }
        node = nextNode ? nextNode : caretPosition.getNode();
      }
      if (node && (isForwards(direction) && caretPosition.isAtEnd() || isBackwards(direction) && caretPosition.isAtStart())) {
        node = findNode(node, direction, always, root, true);
        if (isEditableCaretCandidate(node, root)) {
          return getCaretCandidatePosition(direction, node);
        }
      }
      nextNode = node ? findNode(node, direction, isEditableCaretCandidate, root) : node;
      const rootContentEditableFalseElm = last$2(filter$5(getParents$3(container, root), isContentEditableFalse$6));
      if (rootContentEditableFalseElm && (!nextNode || !rootContentEditableFalseElm.contains(nextNode))) {
        if (isForwards(direction)) {
          caretPosition = CaretPosition.after(rootContentEditableFalseElm);
        } else {
          caretPosition = CaretPosition.before(rootContentEditableFalseElm);
        }
        return caretPosition;
      }
      if (nextNode) {
        return getCaretCandidatePosition(direction, nextNode);
      }
      return null;
    };
    const CaretWalker = root => ({
      next: caretPosition => {
        return findCaretPosition$1(HDirection.Forwards, caretPosition, root);
      },
      prev: caretPosition => {
        return findCaretPosition$1(HDirection.Backwards, caretPosition, root);
      }
    });

    const walkToPositionIn = (forward, root, start) => {
      const position = forward ? CaretPosition.before(start) : CaretPosition.after(start);
      return fromPosition(forward, root, position);
    };
    const afterElement = node => isBr$6(node) ? CaretPosition.before(node) : CaretPosition.after(node);
    const isBeforeOrStart = position => {
      if (CaretPosition.isTextPosition(position)) {
        return position.offset() === 0;
      } else {
        return isCaretCandidate$3(position.getNode());
      }
    };
    const isAfterOrEnd = position => {
      if (CaretPosition.isTextPosition(position)) {
        const container = position.container();
        return position.offset() === container.data.length;
      } else {
        return isCaretCandidate$3(position.getNode(true));
      }
    };
    const isBeforeAfterSameElement = (from, to) => !CaretPosition.isTextPosition(from) && !CaretPosition.isTextPosition(to) && from.getNode() === to.getNode(true);
    const isAtBr = position => !CaretPosition.isTextPosition(position) && isBr$6(position.getNode());
    const shouldSkipPosition = (forward, from, to) => {
      if (forward) {
        return !isBeforeAfterSameElement(from, to) && !isAtBr(from) && isAfterOrEnd(from) && isBeforeOrStart(to);
      } else {
        return !isBeforeAfterSameElement(to, from) && isBeforeOrStart(from) && isAfterOrEnd(to);
      }
    };
    const fromPosition = (forward, root, pos) => {
      const walker = CaretWalker(root);
      return Optional.from(forward ? walker.next(pos) : walker.prev(pos));
    };
    const navigate = (forward, root, from) => fromPosition(forward, root, from).bind(to => {
      if (isInSameBlock(from, to, root) && shouldSkipPosition(forward, from, to)) {
        return fromPosition(forward, root, to);
      } else {
        return Optional.some(to);
      }
    });
    const navigateIgnore = (forward, root, from, ignoreFilter) => navigate(forward, root, from).bind(pos => ignoreFilter(pos) ? navigateIgnore(forward, root, pos, ignoreFilter) : Optional.some(pos));
    const positionIn = (forward, element) => {
      const startNode = forward ? element.firstChild : element.lastChild;
      if (isText$a(startNode)) {
        return Optional.some(CaretPosition(startNode, forward ? 0 : startNode.data.length));
      } else if (startNode) {
        if (isCaretCandidate$3(startNode)) {
          return Optional.some(forward ? CaretPosition.before(startNode) : afterElement(startNode));
        } else {
          return walkToPositionIn(forward, element, startNode);
        }
      } else {
        return Optional.none();
      }
    };
    const nextPosition = curry(fromPosition, true);
    const prevPosition = curry(fromPosition, false);
    const firstPositionIn = curry(positionIn, true);
    const lastPositionIn = curry(positionIn, false);

    const CARET_ID = '_mce_caret';
    const isCaretNode = node => isElement$6(node) && node.id === CARET_ID;
    const getParentCaretContainer = (body, node) => {
      let currentNode = node;
      while (currentNode && currentNode !== body) {
        if (isCaretNode(currentNode)) {
          return currentNode;
        }
        currentNode = currentNode.parentNode;
      }
      return null;
    };

    const isStringPathBookmark = bookmark => isString(bookmark.start);
    const isRangeBookmark = bookmark => has$2(bookmark, 'rng');
    const isIdBookmark = bookmark => has$2(bookmark, 'id');
    const isIndexBookmark = bookmark => has$2(bookmark, 'name');
    const isPathBookmark = bookmark => Tools.isArray(bookmark.start);

    const isForwardBookmark = bookmark => !isIndexBookmark(bookmark) && isBoolean(bookmark.forward) ? bookmark.forward : true;
    const addBogus = (dom, node) => {
      if (isElement$6(node) && dom.isBlock(node) && !node.innerHTML) {
        node.innerHTML = '<br data-mce-bogus="1" />';
      }
      return node;
    };
    const resolveCaretPositionBookmark = (dom, bookmark) => {
      const startPos = Optional.from(resolve$1(dom.getRoot(), bookmark.start));
      const endPos = Optional.from(resolve$1(dom.getRoot(), bookmark.end));
      return lift2(startPos, endPos, (start, end) => {
        const range = dom.createRng();
        range.setStart(start.container(), start.offset());
        range.setEnd(end.container(), end.offset());
        return {
          range,
          forward: isForwardBookmark(bookmark)
        };
      });
    };
    const insertZwsp = (node, rng) => {
      var _a;
      const doc = (_a = node.ownerDocument) !== null && _a !== void 0 ? _a : document;
      const textNode = doc.createTextNode(ZWSP$1);
      node.appendChild(textNode);
      rng.setStart(textNode, 0);
      rng.setEnd(textNode, 0);
    };
    const isEmpty$1 = node => !node.hasChildNodes();
    const tryFindRangePosition = (node, rng) => lastPositionIn(node).fold(never, pos => {
      rng.setStart(pos.container(), pos.offset());
      rng.setEnd(pos.container(), pos.offset());
      return true;
    });
    const padEmptyCaretContainer = (root, node, rng) => {
      if (isEmpty$1(node) && getParentCaretContainer(root, node)) {
        insertZwsp(node, rng);
        return true;
      } else {
        return false;
      }
    };
    const setEndPoint = (dom, start, bookmark, rng) => {
      const point = bookmark[start ? 'start' : 'end'];
      const root = dom.getRoot();
      if (point) {
        let node = root;
        let offset = point[0];
        for (let i = point.length - 1; node && i >= 1; i--) {
          const children = node.childNodes;
          if (padEmptyCaretContainer(root, node, rng)) {
            return true;
          }
          if (point[i] > children.length - 1) {
            if (padEmptyCaretContainer(root, node, rng)) {
              return true;
            }
            return tryFindRangePosition(node, rng);
          }
          node = children[point[i]];
        }
        if (isText$a(node)) {
          offset = Math.min(point[0], node.data.length);
        }
        if (isElement$6(node)) {
          offset = Math.min(point[0], node.childNodes.length);
        }
        if (start) {
          rng.setStart(node, offset);
        } else {
          rng.setEnd(node, offset);
        }
      }
      return true;
    };
    const isValidTextNode = node => isText$a(node) && node.data.length > 0;
    const restoreEndPoint = (dom, suffix, bookmark) => {
      const marker = dom.get(bookmark.id + '_' + suffix);
      const markerParent = marker === null || marker === void 0 ? void 0 : marker.parentNode;
      const keep = bookmark.keep;
      if (marker && markerParent) {
        let container;
        let offset;
        if (suffix === 'start') {
          if (!keep) {
            container = markerParent;
            offset = dom.nodeIndex(marker);
          } else {
            if (marker.hasChildNodes()) {
              container = marker.firstChild;
              offset = 1;
            } else if (isValidTextNode(marker.nextSibling)) {
              container = marker.nextSibling;
              offset = 0;
            } else if (isValidTextNode(marker.previousSibling)) {
              container = marker.previousSibling;
              offset = marker.previousSibling.data.length;
            } else {
              container = markerParent;
              offset = dom.nodeIndex(marker) + 1;
            }
          }
        } else {
          if (!keep) {
            container = markerParent;
            offset = dom.nodeIndex(marker);
          } else {
            if (marker.hasChildNodes()) {
              container = marker.firstChild;
              offset = 1;
            } else if (isValidTextNode(marker.previousSibling)) {
              container = marker.previousSibling;
              offset = marker.previousSibling.data.length;
            } else {
              container = markerParent;
              offset = dom.nodeIndex(marker);
            }
          }
        }
        if (!keep) {
          const prev = marker.previousSibling;
          const next = marker.nextSibling;
          Tools.each(Tools.grep(marker.childNodes), node => {
            if (isText$a(node)) {
              node.data = node.data.replace(/\uFEFF/g, '');
            }
          });
          let otherMarker;
          while (otherMarker = dom.get(bookmark.id + '_' + suffix)) {
            dom.remove(otherMarker, true);
          }
          if (isText$a(next) && isText$a(prev) && !Env.browser.isOpera()) {
            const idx = prev.data.length;
            prev.appendData(next.data);
            dom.remove(next);
            container = prev;
            offset = idx;
          }
        }
        return Optional.some(CaretPosition(container, offset));
      } else {
        return Optional.none();
      }
    };
    const resolvePaths = (dom, bookmark) => {
      const range = dom.createRng();
      if (setEndPoint(dom, true, bookmark, range) && setEndPoint(dom, false, bookmark, range)) {
        return Optional.some({
          range,
          forward: isForwardBookmark(bookmark)
        });
      } else {
        return Optional.none();
      }
    };
    const resolveId = (dom, bookmark) => {
      const startPos = restoreEndPoint(dom, 'start', bookmark);
      const endPos = restoreEndPoint(dom, 'end', bookmark);
      return lift2(startPos, endPos.or(startPos), (spos, epos) => {
        const range = dom.createRng();
        range.setStart(addBogus(dom, spos.container()), spos.offset());
        range.setEnd(addBogus(dom, epos.container()), epos.offset());
        return {
          range,
          forward: isForwardBookmark(bookmark)
        };
      });
    };
    const resolveIndex = (dom, bookmark) => Optional.from(dom.select(bookmark.name)[bookmark.index]).map(elm => {
      const range = dom.createRng();
      range.selectNode(elm);
      return {
        range,
        forward: true
      };
    });
    const resolve = (selection, bookmark) => {
      const dom = selection.dom;
      if (bookmark) {
        if (isPathBookmark(bookmark)) {
          return resolvePaths(dom, bookmark);
        } else if (isStringPathBookmark(bookmark)) {
          return resolveCaretPositionBookmark(dom, bookmark);
        } else if (isIdBookmark(bookmark)) {
          return resolveId(dom, bookmark);
        } else if (isIndexBookmark(bookmark)) {
          return resolveIndex(dom, bookmark);
        } else if (isRangeBookmark(bookmark)) {
          return Optional.some({
            range: bookmark.rng,
            forward: isForwardBookmark(bookmark)
          });
        }
      }
      return Optional.none();
    };

    const getBookmark$1 = (selection, type, normalized) => {
      return getBookmark$2(selection, type, normalized);
    };
    const moveToBookmark = (selection, bookmark) => {
      resolve(selection, bookmark).each(({range, forward}) => {
        selection.setRng(range, forward);
      });
    };
    const isBookmarkNode$1 = node => {
      return isElement$6(node) && node.tagName === 'SPAN' && node.getAttribute('data-mce-type') === 'bookmark';
    };

    const is = expected => actual => expected === actual;
    const isNbsp = is(nbsp);
    const isWhiteSpace = chr => chr !== '' && ' \f\n\r\t\x0B'.indexOf(chr) !== -1;
    const isContent = chr => !isWhiteSpace(chr) && !isNbsp(chr) && !isZwsp$2(chr);

    const getRanges$1 = selection => {
      const ranges = [];
      if (selection) {
        for (let i = 0; i < selection.rangeCount; i++) {
          ranges.push(selection.getRangeAt(i));
        }
      }
      return ranges;
    };
    const getSelectedNodes = ranges => {
      return bind$3(ranges, range => {
        const node = getSelectedNode(range);
        return node ? [SugarElement.fromDom(node)] : [];
      });
    };
    const hasMultipleRanges = selection => {
      return getRanges$1(selection).length > 1;
    };

    const getCellsFromRanges = ranges => filter$5(getSelectedNodes(ranges), isTableCell$2);
    const getCellsFromElement = elm => descendants(elm, 'td[data-mce-selected],th[data-mce-selected]');
    const getCellsFromElementOrRanges = (ranges, element) => {
      const selectedCells = getCellsFromElement(element);
      return selectedCells.length > 0 ? selectedCells : getCellsFromRanges(ranges);
    };
    const getCellsFromEditor = editor => getCellsFromElementOrRanges(getRanges$1(editor.selection.getSel()), SugarElement.fromDom(editor.getBody()));
    const getClosestTable = (cell, isRoot) => ancestor$3(cell, 'table', isRoot);

    const getStartNode = rng => {
      const sc = rng.startContainer, so = rng.startOffset;
      if (isText$a(sc)) {
        return so === 0 ? Optional.some(SugarElement.fromDom(sc)) : Optional.none();
      } else {
        return Optional.from(sc.childNodes[so]).map(SugarElement.fromDom);
      }
    };
    const getEndNode = rng => {
      const ec = rng.endContainer, eo = rng.endOffset;
      if (isText$a(ec)) {
        return eo === ec.data.length ? Optional.some(SugarElement.fromDom(ec)) : Optional.none();
      } else {
        return Optional.from(ec.childNodes[eo - 1]).map(SugarElement.fromDom);
      }
    };
    const getFirstChildren = node => {
      return firstChild(node).fold(constant([node]), child => {
        return [node].concat(getFirstChildren(child));
      });
    };
    const getLastChildren = node => {
      return lastChild(node).fold(constant([node]), child => {
        if (name(child) === 'br') {
          return prevSibling(child).map(sibling => {
            return [node].concat(getLastChildren(sibling));
          }).getOr([]);
        } else {
          return [node].concat(getLastChildren(child));
        }
      });
    };
    const hasAllContentsSelected = (elm, rng) => {
      return lift2(getStartNode(rng), getEndNode(rng), (startNode, endNode) => {
        const start = find$2(getFirstChildren(elm), curry(eq, startNode));
        const end = find$2(getLastChildren(elm), curry(eq, endNode));
        return start.isSome() && end.isSome();
      }).getOr(false);
    };
    const moveEndPoint = (dom, rng, node, start) => {
      const root = node;
      const walker = new DomTreeWalker(node, root);
      const moveCaretBeforeOnEnterElementsMap = filter$4(dom.schema.getMoveCaretBeforeOnEnterElements(), (_, name) => !contains$2([
        'td',
        'th',
        'table'
      ], name.toLowerCase()));
      let currentNode = node;
      do {
        if (isText$a(currentNode) && Tools.trim(currentNode.data).length !== 0) {
          if (start) {
            rng.setStart(currentNode, 0);
          } else {
            rng.setEnd(currentNode, currentNode.data.length);
          }
          return;
        }
        if (moveCaretBeforeOnEnterElementsMap[currentNode.nodeName]) {
          if (start) {
            rng.setStartBefore(currentNode);
          } else {
            if (currentNode.nodeName === 'BR') {
              rng.setEndBefore(currentNode);
            } else {
              rng.setEndAfter(currentNode);
            }
          }
          return;
        }
      } while (currentNode = start ? walker.next() : walker.prev());
      if (root.nodeName === 'BODY') {
        if (start) {
          rng.setStart(root, 0);
        } else {
          rng.setEnd(root, root.childNodes.length);
        }
      }
    };
    const hasAnyRanges = editor => {
      const sel = editor.selection.getSel();
      return isNonNullable(sel) && sel.rangeCount > 0;
    };
    const runOnRanges = (editor, executor) => {
      const fakeSelectionNodes = getCellsFromEditor(editor);
      if (fakeSelectionNodes.length > 0) {
        each$e(fakeSelectionNodes, elem => {
          const node = elem.dom;
          const fakeNodeRng = editor.dom.createRng();
          fakeNodeRng.setStartBefore(node);
          fakeNodeRng.setEndAfter(node);
          executor(fakeNodeRng, true);
        });
      } else {
        executor(editor.selection.getRng(), false);
      }
    };
    const preserve = (selection, fillBookmark, executor) => {
      const bookmark = getPersistentBookmark(selection, fillBookmark);
      executor(bookmark);
      selection.moveToBookmark(bookmark);
    };

    const isNode = node => isNumber(node === null || node === void 0 ? void 0 : node.nodeType);
    const isElementNode$1 = node => isElement$6(node) && !isBookmarkNode$1(node) && !isCaretNode(node) && !isBogus$2(node);
    const isElementDirectlySelected = (dom, node) => {
      if (isElementNode$1(node) && !/^(TD|TH)$/.test(node.nodeName)) {
        const selectedAttr = dom.getAttrib(node, 'data-mce-selected');
        const value = parseInt(selectedAttr, 10);
        return !isNaN(value) && value > 0;
      } else {
        return false;
      }
    };
    const preserveSelection = (editor, action, shouldMoveStart) => {
      const {selection, dom} = editor;
      const selectedNodeBeforeAction = selection.getNode();
      const isSelectedBeforeNodeNoneditable = isContentEditableFalse$b(selectedNodeBeforeAction);
      preserve(selection, true, () => {
        action();
      });
      const isBeforeNodeStillNoneditable = isSelectedBeforeNodeNoneditable && isContentEditableFalse$b(selectedNodeBeforeAction);
      if (isBeforeNodeStillNoneditable && dom.isChildOf(selectedNodeBeforeAction, editor.getBody())) {
        editor.selection.select(selectedNodeBeforeAction);
      } else if (shouldMoveStart(selection.getStart())) {
        moveStartToNearestText(dom, selection);
      }
    };
    const moveStartToNearestText = (dom, selection) => {
      var _a, _b;
      const rng = selection.getRng();
      const {startContainer, startOffset} = rng;
      const selectedNode = selection.getNode();
      if (isElementDirectlySelected(dom, selectedNode)) {
        return;
      }
      if (isElement$6(startContainer)) {
        const nodes = startContainer.childNodes;
        const root = dom.getRoot();
        let walker;
        if (startOffset < nodes.length) {
          const startNode = nodes[startOffset];
          walker = new DomTreeWalker(startNode, (_a = dom.getParent(startNode, dom.isBlock)) !== null && _a !== void 0 ? _a : root);
        } else {
          const startNode = nodes[nodes.length - 1];
          walker = new DomTreeWalker(startNode, (_b = dom.getParent(startNode, dom.isBlock)) !== null && _b !== void 0 ? _b : root);
          walker.next(true);
        }
        for (let node = walker.current(); node; node = walker.next()) {
          if (dom.getContentEditable(node) === 'false') {
            return;
          } else if (isText$a(node) && !isWhiteSpaceNode$1(node)) {
            rng.setStart(node, 0);
            selection.setRng(rng);
            return;
          }
        }
      }
    };
    const getNonWhiteSpaceSibling = (node, next, inc) => {
      if (node) {
        const nextName = next ? 'nextSibling' : 'previousSibling';
        for (node = inc ? node : node[nextName]; node; node = node[nextName]) {
          if (isElement$6(node) || !isWhiteSpaceNode$1(node)) {
            return node;
          }
        }
      }
      return undefined;
    };
    const isTextBlock$1 = (schema, node) => !!schema.getTextBlockElements()[node.nodeName.toLowerCase()] || isTransparentBlock(schema, node);
    const isValid = (ed, parent, child) => {
      return ed.schema.isValidChild(parent, child);
    };
    const isWhiteSpaceNode$1 = (node, allowSpaces = false) => {
      if (isNonNullable(node) && isText$a(node)) {
        const data = allowSpaces ? node.data.replace(/ /g, '\xA0') : node.data;
        return isWhitespaceText(data);
      } else {
        return false;
      }
    };
    const isEmptyTextNode$1 = node => {
      return isNonNullable(node) && isText$a(node) && node.length === 0;
    };
    const isWrapNoneditableTarget = (editor, node) => {
      const baseDataSelector = '[data-mce-cef-wrappable]';
      const formatNoneditableSelector = getFormatNoneditableSelector(editor);
      const selector = isEmpty$3(formatNoneditableSelector) ? baseDataSelector : `${ baseDataSelector },${ formatNoneditableSelector }`;
      return is$1(SugarElement.fromDom(node), selector);
    };
    const isWrappableNoneditable = (editor, node) => {
      const dom = editor.dom;
      return isElementNode$1(node) && dom.getContentEditable(node) === 'false' && isWrapNoneditableTarget(editor, node) && dom.select('[contenteditable="true"]', node).length === 0;
    };
    const replaceVars = (value, vars) => {
      if (isFunction(value)) {
        return value(vars);
      } else if (isNonNullable(vars)) {
        value = value.replace(/%(\w+)/g, (str, name) => {
          return vars[name] || str;
        });
      }
      return value;
    };
    const isEq$5 = (str1, str2) => {
      str1 = str1 || '';
      str2 = str2 || '';
      str1 = '' + (str1.nodeName || str1);
      str2 = '' + (str2.nodeName || str2);
      return str1.toLowerCase() === str2.toLowerCase();
    };
    const normalizeStyleValue = (value, name) => {
      if (isNullable(value)) {
        return null;
      } else {
        let strValue = String(value);
        if (name === 'color' || name === 'backgroundColor') {
          strValue = rgbaToHexString(strValue);
        }
        if (name === 'fontWeight' && value === 700) {
          strValue = 'bold';
        }
        if (name === 'fontFamily') {
          strValue = strValue.replace(/[\'\"]/g, '').replace(/,\s+/g, ',');
        }
        return strValue;
      }
    };
    const getStyle = (dom, node, name) => {
      const style = dom.getStyle(node, name);
      return normalizeStyleValue(style, name);
    };
    const getTextDecoration = (dom, node) => {
      let decoration;
      dom.getParent(node, n => {
        if (isElement$6(n)) {
          decoration = dom.getStyle(n, 'text-decoration');
          return !!decoration && decoration !== 'none';
        } else {
          return false;
        }
      });
      return decoration;
    };
    const getParents$2 = (dom, node, selector) => {
      return dom.getParents(node, selector, dom.getRoot());
    };
    const isFormatPredicate = (editor, formatName, predicate) => {
      const formats = editor.formatter.get(formatName);
      return isNonNullable(formats) && exists(formats, predicate);
    };
    const isVariableFormatName = (editor, formatName) => {
      const hasVariableValues = format => {
        const isVariableValue = val => isFunction(val) || val.length > 1 && val.charAt(0) === '%';
        return exists([
          'styles',
          'attributes'
        ], key => get$a(format, key).exists(field => {
          const fieldValues = isArray$1(field) ? field : values(field);
          return exists(fieldValues, isVariableValue);
        }));
      };
      return isFormatPredicate(editor, formatName, hasVariableValues);
    };
    const areSimilarFormats = (editor, formatName, otherFormatName) => {
      const validKeys = [
        'inline',
        'block',
        'selector',
        'attributes',
        'styles',
        'classes'
      ];
      const filterObj = format => filter$4(format, (_, key) => exists(validKeys, validKey => validKey === key));
      return isFormatPredicate(editor, formatName, fmt1 => {
        const filteredFmt1 = filterObj(fmt1);
        return isFormatPredicate(editor, otherFormatName, fmt2 => {
          const filteredFmt2 = filterObj(fmt2);
          return equal$1(filteredFmt1, filteredFmt2);
        });
      });
    };
    const isBlockFormat = format => hasNonNullableKey(format, 'block');
    const isWrappingBlockFormat = format => isBlockFormat(format) && format.wrapper === true;
    const isNonWrappingBlockFormat = format => isBlockFormat(format) && format.wrapper !== true;
    const isSelectorFormat = format => hasNonNullableKey(format, 'selector');
    const isInlineFormat = format => hasNonNullableKey(format, 'inline');
    const isMixedFormat = format => isSelectorFormat(format) && isInlineFormat(format) && is$2(get$a(format, 'mixed'), true);
    const shouldExpandToSelector = format => isSelectorFormat(format) && format.expand !== false && !isInlineFormat(format);
    const getEmptyCaretContainers = node => {
      const nodes = [];
      let tempNode = node;
      while (tempNode) {
        if (isText$a(tempNode) && tempNode.data !== ZWSP$1 || tempNode.childNodes.length > 1) {
          return [];
        }
        if (isElement$6(tempNode)) {
          nodes.push(tempNode);
        }
        tempNode = tempNode.firstChild;
      }
      return nodes;
    };
    const isCaretContainerEmpty = node => {
      return getEmptyCaretContainers(node).length > 0;
    };
    const isEmptyCaretFormatElement = element => {
      return isCaretNode(element.dom) && isCaretContainerEmpty(element.dom);
    };

    const isBookmarkNode = isBookmarkNode$1;
    const getParents$1 = getParents$2;
    const isWhiteSpaceNode = isWhiteSpaceNode$1;
    const isTextBlock = isTextBlock$1;
    const isBogusBr = node => {
      return isBr$6(node) && node.getAttribute('data-mce-bogus') && !node.nextSibling;
    };
    const findParentContentEditable = (dom, node) => {
      let parent = node;
      while (parent) {
        if (isElement$6(parent) && dom.getContentEditable(parent)) {
          return dom.getContentEditable(parent) === 'false' ? parent : node;
        }
        parent = parent.parentNode;
      }
      return node;
    };
    const walkText = (start, node, offset, predicate) => {
      const str = node.data;
      if (start) {
        for (let i = offset; i > 0; i--) {
          if (predicate(str.charAt(i - 1))) {
            return i;
          }
        }
      } else {
        for (let i = offset; i < str.length; i++) {
          if (predicate(str.charAt(i))) {
            return i;
          }
        }
      }
      return -1;
    };
    const findSpace = (start, node, offset) => walkText(start, node, offset, c => isNbsp(c) || isWhiteSpace(c));
    const findContent = (start, node, offset) => walkText(start, node, offset, isContent);
    const findWordEndPoint = (dom, body, container, offset, start, includeTrailingSpaces) => {
      let lastTextNode;
      const rootNode = dom.getParent(container, dom.isBlock) || body;
      const walk = (container, offset, pred) => {
        const textSeeker = TextSeeker(dom);
        const walker = start ? textSeeker.backwards : textSeeker.forwards;
        return Optional.from(walker(container, offset, (text, textOffset) => {
          if (isBookmarkNode(text.parentNode)) {
            return -1;
          } else {
            lastTextNode = text;
            return pred(start, text, textOffset);
          }
        }, rootNode));
      };
      const spaceResult = walk(container, offset, findSpace);
      return spaceResult.bind(result => includeTrailingSpaces ? walk(result.container, result.offset + (start ? -1 : 0), findContent) : Optional.some(result)).orThunk(() => lastTextNode ? Optional.some({
        container: lastTextNode,
        offset: start ? 0 : lastTextNode.length
      }) : Optional.none());
    };
    const findSelectorEndPoint = (dom, formatList, rng, container, siblingName) => {
      const sibling = container[siblingName];
      if (isText$a(container) && isEmpty$3(container.data) && sibling) {
        container = sibling;
      }
      const parents = getParents$1(dom, container);
      for (let i = 0; i < parents.length; i++) {
        for (let y = 0; y < formatList.length; y++) {
          const curFormat = formatList[y];
          if (isNonNullable(curFormat.collapsed) && curFormat.collapsed !== rng.collapsed) {
            continue;
          }
          if (isSelectorFormat(curFormat) && dom.is(parents[i], curFormat.selector)) {
            return parents[i];
          }
        }
      }
      return container;
    };
    const findBlockEndPoint = (dom, formatList, container, siblingName) => {
      var _a;
      let node = container;
      const root = dom.getRoot();
      const format = formatList[0];
      if (isBlockFormat(format)) {
        node = format.wrapper ? null : dom.getParent(container, format.block, root);
      }
      if (!node) {
        const scopeRoot = (_a = dom.getParent(container, 'LI,TD,TH,SUMMARY')) !== null && _a !== void 0 ? _a : root;
        node = dom.getParent(isText$a(container) ? container.parentNode : container, node => node !== root && isTextBlock(dom.schema, node), scopeRoot);
      }
      if (node && isBlockFormat(format) && format.wrapper) {
        node = getParents$1(dom, node, 'ul,ol').reverse()[0] || node;
      }
      if (!node) {
        node = container;
        while (node && node[siblingName] && !dom.isBlock(node[siblingName])) {
          node = node[siblingName];
          if (isEq$5(node, 'br')) {
            break;
          }
        }
      }
      return node || container;
    };
    const isAtBlockBoundary$1 = (dom, root, container, siblingName) => {
      const parent = container.parentNode;
      if (isNonNullable(container[siblingName])) {
        return false;
      } else if (parent === root || isNullable(parent) || dom.isBlock(parent)) {
        return true;
      } else {
        return isAtBlockBoundary$1(dom, root, parent, siblingName);
      }
    };
    const findParentContainer = (dom, formatList, container, offset, start) => {
      let parent = container;
      const siblingName = start ? 'previousSibling' : 'nextSibling';
      const root = dom.getRoot();
      if (isText$a(container) && !isWhiteSpaceNode(container)) {
        if (start ? offset > 0 : offset < container.data.length) {
          return container;
        }
      }
      while (parent) {
        if (!formatList[0].block_expand && dom.isBlock(parent)) {
          return parent;
        }
        for (let sibling = parent[siblingName]; sibling; sibling = sibling[siblingName]) {
          const allowSpaces = isText$a(sibling) && !isAtBlockBoundary$1(dom, root, sibling, siblingName);
          if (!isBookmarkNode(sibling) && !isBogusBr(sibling) && !isWhiteSpaceNode(sibling, allowSpaces)) {
            return parent;
          }
        }
        if (parent === root || parent.parentNode === root) {
          container = parent;
          break;
        }
        parent = parent.parentNode;
      }
      return container;
    };
    const isSelfOrParentBookmark = container => isBookmarkNode(container.parentNode) || isBookmarkNode(container);
    const expandRng = (dom, rng, formatList, includeTrailingSpace = false) => {
      let {startContainer, startOffset, endContainer, endOffset} = rng;
      const format = formatList[0];
      if (isElement$6(startContainer) && startContainer.hasChildNodes()) {
        startContainer = getNode$1(startContainer, startOffset);
        if (isText$a(startContainer)) {
          startOffset = 0;
        }
      }
      if (isElement$6(endContainer) && endContainer.hasChildNodes()) {
        endContainer = getNode$1(endContainer, rng.collapsed ? endOffset : endOffset - 1);
        if (isText$a(endContainer)) {
          endOffset = endContainer.data.length;
        }
      }
      startContainer = findParentContentEditable(dom, startContainer);
      endContainer = findParentContentEditable(dom, endContainer);
      if (isSelfOrParentBookmark(startContainer)) {
        startContainer = isBookmarkNode(startContainer) ? startContainer : startContainer.parentNode;
        if (rng.collapsed) {
          startContainer = startContainer.previousSibling || startContainer;
        } else {
          startContainer = startContainer.nextSibling || startContainer;
        }
        if (isText$a(startContainer)) {
          startOffset = rng.collapsed ? startContainer.length : 0;
        }
      }
      if (isSelfOrParentBookmark(endContainer)) {
        endContainer = isBookmarkNode(endContainer) ? endContainer : endContainer.parentNode;
        if (rng.collapsed) {
          endContainer = endContainer.nextSibling || endContainer;
        } else {
          endContainer = endContainer.previousSibling || endContainer;
        }
        if (isText$a(endContainer)) {
          endOffset = rng.collapsed ? 0 : endContainer.length;
        }
      }
      if (rng.collapsed) {
        const startPoint = findWordEndPoint(dom, dom.getRoot(), startContainer, startOffset, true, includeTrailingSpace);
        startPoint.each(({container, offset}) => {
          startContainer = container;
          startOffset = offset;
        });
        const endPoint = findWordEndPoint(dom, dom.getRoot(), endContainer, endOffset, false, includeTrailingSpace);
        endPoint.each(({container, offset}) => {
          endContainer = container;
          endOffset = offset;
        });
      }
      if (isInlineFormat(format) || format.block_expand) {
        if (!isInlineFormat(format) || (!isText$a(startContainer) || startOffset === 0)) {
          startContainer = findParentContainer(dom, formatList, startContainer, startOffset, true);
        }
        if (!isInlineFormat(format) || (!isText$a(endContainer) || endOffset === endContainer.data.length)) {
          endContainer = findParentContainer(dom, formatList, endContainer, endOffset, false);
        }
      }
      if (shouldExpandToSelector(format)) {
        startContainer = findSelectorEndPoint(dom, formatList, rng, startContainer, 'previousSibling');
        endContainer = findSelectorEndPoint(dom, formatList, rng, endContainer, 'nextSibling');
      }
      if (isBlockFormat(format) || isSelectorFormat(format)) {
        startContainer = findBlockEndPoint(dom, formatList, startContainer, 'previousSibling');
        endContainer = findBlockEndPoint(dom, formatList, endContainer, 'nextSibling');
        if (isBlockFormat(format)) {
          if (!dom.isBlock(startContainer)) {
            startContainer = findParentContainer(dom, formatList, startContainer, startOffset, true);
          }
          if (!dom.isBlock(endContainer)) {
            endContainer = findParentContainer(dom, formatList, endContainer, endOffset, false);
          }
        }
      }
      if (isElement$6(startContainer) && startContainer.parentNode) {
        startOffset = dom.nodeIndex(startContainer);
        startContainer = startContainer.parentNode;
      }
      if (isElement$6(endContainer) && endContainer.parentNode) {
        endOffset = dom.nodeIndex(endContainer) + 1;
        endContainer = endContainer.parentNode;
      }
      return {
        startContainer,
        startOffset,
        endContainer,
        endOffset
      };
    };

    const walk$3 = (dom, rng, callback) => {
      var _a;
      const startOffset = rng.startOffset;
      const startContainer = getNode$1(rng.startContainer, startOffset);
      const endOffset = rng.endOffset;
      const endContainer = getNode$1(rng.endContainer, endOffset - 1);
      const exclude = nodes => {
        const firstNode = nodes[0];
        if (isText$a(firstNode) && firstNode === startContainer && startOffset >= firstNode.data.length) {
          nodes.splice(0, 1);
        }
        const lastNode = nodes[nodes.length - 1];
        if (endOffset === 0 && nodes.length > 0 && lastNode === endContainer && isText$a(lastNode)) {
          nodes.splice(nodes.length - 1, 1);
        }
        return nodes;
      };
      const collectSiblings = (node, name, endNode) => {
        const siblings = [];
        for (; node && node !== endNode; node = node[name]) {
          siblings.push(node);
        }
        return siblings;
      };
      const findEndPoint = (node, root) => dom.getParent(node, node => node.parentNode === root, root);
      const walkBoundary = (startNode, endNode, next) => {
        const siblingName = next ? 'nextSibling' : 'previousSibling';
        for (let node = startNode, parent = node.parentNode; node && node !== endNode; node = parent) {
          parent = node.parentNode;
          const siblings = collectSiblings(node === startNode ? node : node[siblingName], siblingName);
          if (siblings.length) {
            if (!next) {
              siblings.reverse();
            }
            callback(exclude(siblings));
          }
        }
      };
      if (startContainer === endContainer) {
        return callback(exclude([startContainer]));
      }
      const ancestor = (_a = dom.findCommonAncestor(startContainer, endContainer)) !== null && _a !== void 0 ? _a : dom.getRoot();
      if (dom.isChildOf(startContainer, endContainer)) {
        return walkBoundary(startContainer, ancestor, true);
      }
      if (dom.isChildOf(endContainer, startContainer)) {
        return walkBoundary(endContainer, ancestor);
      }
      const startPoint = findEndPoint(startContainer, ancestor) || startContainer;
      const endPoint = findEndPoint(endContainer, ancestor) || endContainer;
      walkBoundary(startContainer, startPoint, true);
      const siblings = collectSiblings(startPoint === startContainer ? startPoint : startPoint.nextSibling, 'nextSibling', endPoint === endContainer ? endPoint.nextSibling : endPoint);
      if (siblings.length) {
        callback(exclude(siblings));
      }
      walkBoundary(endContainer, endPoint);
    };

    const validBlocks = [
      'pre[class*=language-][contenteditable="false"]',
      'figure.image',
      'div[data-ephox-embed-iri]',
      'div.tiny-pageembed',
      'div.mce-toc',
      'div[data-mce-toc]'
    ];
    const isZeroWidth = elem => isText$b(elem) && get$3(elem) === ZWSP$1;
    const context = (editor, elem, wrapName, nodeName) => parent(elem).fold(() => 'skipping', parent => {
      if (nodeName === 'br' || isZeroWidth(elem)) {
        return 'valid';
      } else if (isAnnotation(elem)) {
        return 'existing';
      } else if (isCaretNode(elem.dom)) {
        return 'caret';
      } else if (exists(validBlocks, selector => is$1(elem, selector))) {
        return 'valid-block';
      } else if (!isValid(editor, wrapName, nodeName) || !isValid(editor, name(parent), wrapName)) {
        return 'invalid-child';
      } else {
        return 'valid';
      }
    });

    const applyWordGrab = (editor, rng) => {
      const r = expandRng(editor.dom, rng, [{ inline: 'span' }]);
      rng.setStart(r.startContainer, r.startOffset);
      rng.setEnd(r.endContainer, r.endOffset);
      editor.selection.setRng(rng);
    };
    const applyAnnotation = (elem, masterUId, data, annotationName, decorate, directAnnotation) => {
      const {uid = masterUId, ...otherData} = data;
      add$2(elem, annotation());
      set$3(elem, `${ dataAnnotationId() }`, uid);
      set$3(elem, `${ dataAnnotation() }`, annotationName);
      const {attributes = {}, classes = []} = decorate(uid, otherData);
      setAll$1(elem, attributes);
      add(elem, classes);
      if (directAnnotation) {
        if (classes.length > 0) {
          set$3(elem, `${ dataAnnotationClasses() }`, classes.join(','));
        }
        const attributeNames = keys(attributes);
        if (attributeNames.length > 0) {
          set$3(elem, `${ dataAnnotationAttributes() }`, attributeNames.join(','));
        }
      }
    };
    const removeDirectAnnotation = elem => {
      remove$7(elem, annotation());
      remove$a(elem, `${ dataAnnotationId() }`);
      remove$a(elem, `${ dataAnnotation() }`);
      remove$a(elem, `${ dataAnnotationActive() }`);
      const customAttrNames = getOpt(elem, `${ dataAnnotationAttributes() }`).map(names => names.split(',')).getOr([]);
      const customClasses = getOpt(elem, `${ dataAnnotationClasses() }`).map(names => names.split(',')).getOr([]);
      each$e(customAttrNames, name => remove$a(elem, name));
      remove$4(elem, customClasses);
      remove$a(elem, `${ dataAnnotationClasses() }`);
      remove$a(elem, `${ dataAnnotationAttributes() }`);
    };
    const makeAnnotation = (eDoc, uid, data, annotationName, decorate) => {
      const master = SugarElement.fromTag('span', eDoc);
      applyAnnotation(master, uid, data, annotationName, decorate, false);
      return master;
    };
    const annotate = (editor, rng, uid, annotationName, decorate, data) => {
      const newWrappers = [];
      const master = makeAnnotation(editor.getDoc(), uid, data, annotationName, decorate);
      const wrapper = value$2();
      const finishWrapper = () => {
        wrapper.clear();
      };
      const getOrOpenWrapper = () => wrapper.get().getOrThunk(() => {
        const nu = shallow$1(master);
        newWrappers.push(nu);
        wrapper.set(nu);
        return nu;
      });
      const processElements = elems => {
        each$e(elems, processElement);
      };
      const processElement = elem => {
        const ctx = context(editor, elem, 'span', name(elem));
        switch (ctx) {
        case 'invalid-child': {
            finishWrapper();
            const children = children$1(elem);
            processElements(children);
            finishWrapper();
            break;
          }
        case 'valid-block': {
            finishWrapper();
            applyAnnotation(elem, uid, data, annotationName, decorate, true);
            break;
          }
        case 'valid': {
            const w = getOrOpenWrapper();
            wrap$2(elem, w);
            break;
          }
        }
      };
      const processNodes = nodes => {
        const elems = map$3(nodes, SugarElement.fromDom);
        processElements(elems);
      };
      walk$3(editor.dom, rng, nodes => {
        finishWrapper();
        processNodes(nodes);
      });
      return newWrappers;
    };
    const annotateWithBookmark = (editor, name, settings, data) => {
      editor.undoManager.transact(() => {
        const selection = editor.selection;
        const initialRng = selection.getRng();
        const hasFakeSelection = getCellsFromEditor(editor).length > 0;
        const masterUid = generate$1('mce-annotation');
        if (initialRng.collapsed && !hasFakeSelection) {
          applyWordGrab(editor, initialRng);
        }
        if (selection.getRng().collapsed && !hasFakeSelection) {
          const wrapper = makeAnnotation(editor.getDoc(), masterUid, data, name, settings.decorate);
          set$1(wrapper, nbsp);
          selection.getRng().insertNode(wrapper.dom);
          selection.select(wrapper.dom);
        } else {
          preserve(selection, false, () => {
            runOnRanges(editor, selectionRng => {
              annotate(editor, selectionRng, masterUid, name, settings.decorate, data);
            });
          });
        }
      });
    };

    const Annotator = editor => {
      const registry = create$c();
      setup$x(editor, registry);
      const changes = setup$y(editor, registry);
      const isSpan = isTag('span');
      const removeAnnotations = elements => {
        each$e(elements, element => {
          if (isSpan(element)) {
            unwrap(element);
          } else {
            removeDirectAnnotation(element);
          }
        });
      };
      return {
        register: (name, settings) => {
          registry.register(name, settings);
        },
        annotate: (name, data) => {
          registry.lookup(name).each(settings => {
            annotateWithBookmark(editor, name, settings, data);
          });
        },
        annotationChanged: (name, callback) => {
          changes.addListener(name, callback);
        },
        remove: name => {
          identify(editor, Optional.some(name)).each(({elements}) => {
            const bookmark = editor.selection.getBookmark();
            removeAnnotations(elements);
            editor.selection.moveToBookmark(bookmark);
          });
        },
        removeAll: name => {
          const bookmark = editor.selection.getBookmark();
          each$d(findAll(editor, name), (elements, _) => {
            removeAnnotations(elements);
          });
          editor.selection.moveToBookmark(bookmark);
        },
        getAll: name => {
          const directory = findAll(editor, name);
          return map$2(directory, elems => map$3(elems, elem => elem.dom));
        }
      };
    };

    const BookmarkManager = selection => {
      return {
        getBookmark: curry(getBookmark$1, selection),
        moveToBookmark: curry(moveToBookmark, selection)
      };
    };
    BookmarkManager.isBookmarkNode = isBookmarkNode$1;

    const isXYWithinRange = (clientX, clientY, range) => {
      if (range.collapsed) {
        return false;
      } else {
        return exists(range.getClientRects(), rect => containsXY(rect, clientX, clientY));
      }
    };

    const firePreProcess = (editor, args) => editor.dispatch('PreProcess', args);
    const firePostProcess = (editor, args) => editor.dispatch('PostProcess', args);
    const fireRemove = editor => {
      editor.dispatch('remove');
    };
    const fireDetach = editor => {
      editor.dispatch('detach');
    };
    const fireSwitchMode = (editor, mode) => {
      editor.dispatch('SwitchMode', { mode });
    };
    const fireObjectResizeStart = (editor, target, width, height, origin) => {
      editor.dispatch('ObjectResizeStart', {
        target,
        width,
        height,
        origin
      });
    };
    const fireObjectResized = (editor, target, width, height, origin) => {
      editor.dispatch('ObjectResized', {
        target,
        width,
        height,
        origin
      });
    };
    const firePreInit = editor => {
      editor.dispatch('PreInit');
    };
    const firePostRender = editor => {
      editor.dispatch('PostRender');
    };
    const fireInit = editor => {
      editor.dispatch('Init');
    };
    const firePlaceholderToggle = (editor, state) => {
      editor.dispatch('PlaceholderToggle', { state });
    };
    const fireError = (editor, errorType, error) => {
      editor.dispatch(errorType, error);
    };
    const fireFormatApply = (editor, format, node, vars) => {
      editor.dispatch('FormatApply', {
        format,
        node,
        vars
      });
    };
    const fireFormatRemove = (editor, format, node, vars) => {
      editor.dispatch('FormatRemove', {
        format,
        node,
        vars
      });
    };
    const fireBeforeSetContent = (editor, args) => editor.dispatch('BeforeSetContent', args);
    const fireSetContent = (editor, args) => editor.dispatch('SetContent', args);
    const fireBeforeGetContent = (editor, args) => editor.dispatch('BeforeGetContent', args);
    const fireGetContent = (editor, args) => editor.dispatch('GetContent', args);
    const fireAutocompleterStart = (editor, args) => {
      editor.dispatch('AutocompleterStart', args);
    };
    const fireAutocompleterUpdate = (editor, args) => {
      editor.dispatch('AutocompleterUpdate', args);
    };
    const fireAutocompleterEnd = editor => {
      editor.dispatch('AutocompleterEnd');
    };
    const firePastePreProcess = (editor, html, internal) => editor.dispatch('PastePreProcess', {
      content: html,
      internal
    });
    const firePastePostProcess = (editor, node, internal) => editor.dispatch('PastePostProcess', {
      node,
      internal
    });
    const firePastePlainTextToggle = (editor, state) => editor.dispatch('PastePlainTextToggle', { state });
    const fireEditableRootStateChange = (editor, state) => editor.dispatch('EditableRootStateChange', { state });

    const VK = {
      BACKSPACE: 8,
      DELETE: 46,
      DOWN: 40,
      ENTER: 13,
      ESC: 27,
      LEFT: 37,
      RIGHT: 39,
      SPACEBAR: 32,
      TAB: 9,
      UP: 38,
      PAGE_UP: 33,
      PAGE_DOWN: 34,
      END: 35,
      HOME: 36,
      modifierPressed: e => {
        return e.shiftKey || e.ctrlKey || e.altKey || VK.metaKeyPressed(e);
      },
      metaKeyPressed: e => {
        return Env.os.isMacOS() || Env.os.isiOS() ? e.metaKey : e.ctrlKey && !e.altKey;
      }
    };

    const elementSelectionAttr = 'data-mce-selected';
    const controlElmSelector = 'table,img,figure.image,hr,video,span.mce-preview-object,details';
    const abs = Math.abs;
    const round$1 = Math.round;
    const resizeHandles = {
      nw: [
        0,
        0,
        -1,
        -1
      ],
      ne: [
        1,
        0,
        1,
        -1
      ],
      se: [
        1,
        1,
        1,
        1
      ],
      sw: [
        0,
        1,
        -1,
        1
      ]
    };
    const isTouchEvent = evt => evt.type === 'longpress' || evt.type.indexOf('touch') === 0;
    const ControlSelection = (selection, editor) => {
      const dom = editor.dom;
      const editableDoc = editor.getDoc();
      const rootDocument = document;
      const rootElement = editor.getBody();
      let selectedElm, selectedElmGhost, resizeHelper, selectedHandle, resizeBackdrop;
      let startX, startY, selectedElmX, selectedElmY, startW, startH, ratio, resizeStarted;
      let width;
      let height;
      let startScrollWidth;
      let startScrollHeight;
      const isImage = elm => isNonNullable(elm) && (isImg(elm) || dom.is(elm, 'figure.image'));
      const isMedia = elm => isMedia$2(elm) || dom.hasClass(elm, 'mce-preview-object');
      const isEventOnImageOutsideRange = (evt, range) => {
        if (isTouchEvent(evt)) {
          const touch = evt.touches[0];
          return isImage(evt.target) && !isXYWithinRange(touch.clientX, touch.clientY, range);
        } else {
          return isImage(evt.target) && !isXYWithinRange(evt.clientX, evt.clientY, range);
        }
      };
      const contextMenuSelectImage = evt => {
        const target = evt.target;
        if (isEventOnImageOutsideRange(evt, editor.selection.getRng()) && !evt.isDefaultPrevented()) {
          editor.selection.select(target);
        }
      };
      const getResizeTargets = elm => {
        if (dom.hasClass(elm, 'mce-preview-object') && isNonNullable(elm.firstElementChild)) {
          return [
            elm,
            elm.firstElementChild
          ];
        } else if (dom.is(elm, 'figure.image')) {
          return [elm.querySelector('img')];
        } else {
          return [elm];
        }
      };
      const isResizable = elm => {
        const selector = getObjectResizing(editor);
        if (!selector) {
          return false;
        }
        if (elm.getAttribute('data-mce-resize') === 'false') {
          return false;
        }
        if (elm === editor.getBody()) {
          return false;
        }
        if (dom.hasClass(elm, 'mce-preview-object') && isNonNullable(elm.firstElementChild)) {
          return is$1(SugarElement.fromDom(elm.firstElementChild), selector);
        } else {
          return is$1(SugarElement.fromDom(elm), selector);
        }
      };
      const createGhostElement = elm => {
        if (isMedia(elm)) {
          return dom.create('img', { src: Env.transparentSrc });
        } else {
          return elm.cloneNode(true);
        }
      };
      const setSizeProp = (element, name, value) => {
        if (isNonNullable(value)) {
          const targets = getResizeTargets(element);
          each$e(targets, target => {
            if (target.style[name] || !editor.schema.isValid(target.nodeName.toLowerCase(), name)) {
              dom.setStyle(target, name, value);
            } else {
              dom.setAttrib(target, name, '' + value);
            }
          });
        }
      };
      const setGhostElmSize = (ghostElm, width, height) => {
        setSizeProp(ghostElm, 'width', width);
        setSizeProp(ghostElm, 'height', height);
      };
      const resizeGhostElement = e => {
        let deltaX, deltaY, proportional;
        let resizeHelperX, resizeHelperY;
        deltaX = e.screenX - startX;
        deltaY = e.screenY - startY;
        width = deltaX * selectedHandle[2] + startW;
        height = deltaY * selectedHandle[3] + startH;
        width = width < 5 ? 5 : width;
        height = height < 5 ? 5 : height;
        if ((isImage(selectedElm) || isMedia(selectedElm)) && getResizeImgProportional(editor) !== false) {
          proportional = !VK.modifierPressed(e);
        } else {
          proportional = VK.modifierPressed(e);
        }
        if (proportional) {
          if (abs(deltaX) > abs(deltaY)) {
            height = round$1(width * ratio);
            width = round$1(height / ratio);
          } else {
            width = round$1(height / ratio);
            height = round$1(width * ratio);
          }
        }
        setGhostElmSize(selectedElmGhost, width, height);
        resizeHelperX = selectedHandle.startPos.x + deltaX;
        resizeHelperY = selectedHandle.startPos.y + deltaY;
        resizeHelperX = resizeHelperX > 0 ? resizeHelperX : 0;
        resizeHelperY = resizeHelperY > 0 ? resizeHelperY : 0;
        dom.setStyles(resizeHelper, {
          left: resizeHelperX,
          top: resizeHelperY,
          display: 'block'
        });
        resizeHelper.innerHTML = width + ' &times; ' + height;
        if (selectedHandle[2] < 0 && selectedElmGhost.clientWidth <= width) {
          dom.setStyle(selectedElmGhost, 'left', selectedElmX + (startW - width));
        }
        if (selectedHandle[3] < 0 && selectedElmGhost.clientHeight <= height) {
          dom.setStyle(selectedElmGhost, 'top', selectedElmY + (startH - height));
        }
        deltaX = rootElement.scrollWidth - startScrollWidth;
        deltaY = rootElement.scrollHeight - startScrollHeight;
        if (deltaX + deltaY !== 0) {
          dom.setStyles(resizeHelper, {
            left: resizeHelperX - deltaX,
            top: resizeHelperY - deltaY
          });
        }
        if (!resizeStarted) {
          fireObjectResizeStart(editor, selectedElm, startW, startH, 'corner-' + selectedHandle.name);
          resizeStarted = true;
        }
      };
      const endGhostResize = () => {
        const wasResizeStarted = resizeStarted;
        resizeStarted = false;
        if (wasResizeStarted) {
          setSizeProp(selectedElm, 'width', width);
          setSizeProp(selectedElm, 'height', height);
        }
        dom.unbind(editableDoc, 'mousemove', resizeGhostElement);
        dom.unbind(editableDoc, 'mouseup', endGhostResize);
        if (rootDocument !== editableDoc) {
          dom.unbind(rootDocument, 'mousemove', resizeGhostElement);
          dom.unbind(rootDocument, 'mouseup', endGhostResize);
        }
        dom.remove(selectedElmGhost);
        dom.remove(resizeHelper);
        dom.remove(resizeBackdrop);
        showResizeRect(selectedElm);
        if (wasResizeStarted) {
          fireObjectResized(editor, selectedElm, width, height, 'corner-' + selectedHandle.name);
          dom.setAttrib(selectedElm, 'style', dom.getAttrib(selectedElm, 'style'));
        }
        editor.nodeChanged();
      };
      const showResizeRect = targetElm => {
        unbindResizeHandleEvents();
        const position = dom.getPos(targetElm, rootElement);
        const selectedElmX = position.x;
        const selectedElmY = position.y;
        const rect = targetElm.getBoundingClientRect();
        const targetWidth = rect.width || rect.right - rect.left;
        const targetHeight = rect.height || rect.bottom - rect.top;
        if (selectedElm !== targetElm) {
          hideResizeRect();
          selectedElm = targetElm;
          width = height = 0;
        }
        const e = editor.dispatch('ObjectSelected', { target: targetElm });
        if (isResizable(targetElm) && !e.isDefaultPrevented()) {
          each$d(resizeHandles, (handle, name) => {
            const startDrag = e => {
              const target = getResizeTargets(selectedElm)[0];
              startX = e.screenX;
              startY = e.screenY;
              startW = target.clientWidth;
              startH = target.clientHeight;
              ratio = startH / startW;
              selectedHandle = handle;
              selectedHandle.name = name;
              selectedHandle.startPos = {
                x: targetWidth * handle[0] + selectedElmX,
                y: targetHeight * handle[1] + selectedElmY
              };
              startScrollWidth = rootElement.scrollWidth;
              startScrollHeight = rootElement.scrollHeight;
              resizeBackdrop = dom.add(rootElement, 'div', {
                'class': 'mce-resize-backdrop',
                'data-mce-bogus': 'all'
              });
              dom.setStyles(resizeBackdrop, {
                position: 'fixed',
                left: '0',
                top: '0',
                width: '100%',
                height: '100%'
              });
              selectedElmGhost = createGhostElement(selectedElm);
              dom.addClass(selectedElmGhost, 'mce-clonedresizable');
              dom.setAttrib(selectedElmGhost, 'data-mce-bogus', 'all');
              selectedElmGhost.contentEditable = 'false';
              dom.setStyles(selectedElmGhost, {
                left: selectedElmX,
                top: selectedElmY,
                margin: 0
              });
              setGhostElmSize(selectedElmGhost, targetWidth, targetHeight);
              selectedElmGhost.removeAttribute(elementSelectionAttr);
              rootElement.appendChild(selectedElmGhost);
              dom.bind(editableDoc, 'mousemove', resizeGhostElement);
              dom.bind(editableDoc, 'mouseup', endGhostResize);
              if (rootDocument !== editableDoc) {
                dom.bind(rootDocument, 'mousemove', resizeGhostElement);
                dom.bind(rootDocument, 'mouseup', endGhostResize);
              }
              resizeHelper = dom.add(rootElement, 'div', {
                'class': 'mce-resize-helper',
                'data-mce-bogus': 'all'
              }, startW + ' &times; ' + startH);
            };
            let handleElm = dom.get('mceResizeHandle' + name);
            if (handleElm) {
              dom.remove(handleElm);
            }
            handleElm = dom.add(rootElement, 'div', {
              'id': 'mceResizeHandle' + name,
              'data-mce-bogus': 'all',
              'class': 'mce-resizehandle',
              'unselectable': true,
              'style': 'cursor:' + name + '-resize; margin:0; padding:0'
            });
            dom.bind(handleElm, 'mousedown', e => {
              e.stopImmediatePropagation();
              e.preventDefault();
              startDrag(e);
            });
            handle.elm = handleElm;
            dom.setStyles(handleElm, {
              left: targetWidth * handle[0] + selectedElmX - handleElm.offsetWidth / 2,
              top: targetHeight * handle[1] + selectedElmY - handleElm.offsetHeight / 2
            });
          });
        } else {
          hideResizeRect(false);
        }
      };
      const throttledShowResizeRect = first$1(showResizeRect, 0);
      const hideResizeRect = (removeSelected = true) => {
        throttledShowResizeRect.cancel();
        unbindResizeHandleEvents();
        if (selectedElm && removeSelected) {
          selectedElm.removeAttribute(elementSelectionAttr);
        }
        each$d(resizeHandles, (value, name) => {
          const handleElm = dom.get('mceResizeHandle' + name);
          if (handleElm) {
            dom.unbind(handleElm);
            dom.remove(handleElm);
          }
        });
      };
      const isChildOrEqual = (node, parent) => dom.isChildOf(node, parent);
      const updateResizeRect = e => {
        if (resizeStarted || editor.removed || editor.composing) {
          return;
        }
        const targetElm = e.type === 'mousedown' ? e.target : selection.getNode();
        const controlElm = closest$3(SugarElement.fromDom(targetElm), controlElmSelector).map(e => e.dom).filter(e => dom.isEditable(e.parentElement) || e.nodeName === 'IMG' && dom.isEditable(e)).getOrUndefined();
        const selectedValue = isNonNullable(controlElm) ? dom.getAttrib(controlElm, elementSelectionAttr, '1') : '1';
        each$e(dom.select(`img[${ elementSelectionAttr }],hr[${ elementSelectionAttr }]`), img => {
          img.removeAttribute(elementSelectionAttr);
        });
        if (isNonNullable(controlElm) && isChildOrEqual(controlElm, rootElement) && editor.hasFocus()) {
          disableGeckoResize();
          const startElm = selection.getStart(true);
          if (isChildOrEqual(startElm, controlElm) && isChildOrEqual(selection.getEnd(true), controlElm)) {
            dom.setAttrib(controlElm, elementSelectionAttr, selectedValue);
            throttledShowResizeRect.throttle(controlElm);
            return;
          }
        }
        hideResizeRect();
      };
      const unbindResizeHandleEvents = () => {
        each$d(resizeHandles, handle => {
          if (handle.elm) {
            dom.unbind(handle.elm);
            delete handle.elm;
          }
        });
      };
      const disableGeckoResize = () => {
        try {
          editor.getDoc().execCommand('enableObjectResizing', false, 'false');
        } catch (ex) {
        }
      };
      editor.on('init', () => {
        disableGeckoResize();
        editor.on('NodeChange ResizeEditor ResizeWindow ResizeContent drop', updateResizeRect);
        editor.on('keyup compositionend', e => {
          if (selectedElm && selectedElm.nodeName === 'TABLE') {
            updateResizeRect(e);
          }
        });
        editor.on('hide blur', hideResizeRect);
        editor.on('contextmenu longpress', contextMenuSelectImage, true);
      });
      editor.on('remove', unbindResizeHandleEvents);
      const destroy = () => {
        throttledShowResizeRect.cancel();
        selectedElm = selectedElmGhost = resizeBackdrop = null;
      };
      return {
        isResizable,
        showResizeRect,
        hideResizeRect,
        updateResizeRect,
        destroy
      };
    };

    const setStart = (rng, situ) => {
      situ.fold(e => {
        rng.setStartBefore(e.dom);
      }, (e, o) => {
        rng.setStart(e.dom, o);
      }, e => {
        rng.setStartAfter(e.dom);
      });
    };
    const setFinish = (rng, situ) => {
      situ.fold(e => {
        rng.setEndBefore(e.dom);
      }, (e, o) => {
        rng.setEnd(e.dom, o);
      }, e => {
        rng.setEndAfter(e.dom);
      });
    };
    const relativeToNative = (win, startSitu, finishSitu) => {
      const range = win.document.createRange();
      setStart(range, startSitu);
      setFinish(range, finishSitu);
      return range;
    };
    const exactToNative = (win, start, soffset, finish, foffset) => {
      const rng = win.document.createRange();
      rng.setStart(start.dom, soffset);
      rng.setEnd(finish.dom, foffset);
      return rng;
    };

    const adt$3 = Adt.generate([
      {
        ltr: [
          'start',
          'soffset',
          'finish',
          'foffset'
        ]
      },
      {
        rtl: [
          'start',
          'soffset',
          'finish',
          'foffset'
        ]
      }
    ]);
    const fromRange = (win, type, range) => type(SugarElement.fromDom(range.startContainer), range.startOffset, SugarElement.fromDom(range.endContainer), range.endOffset);
    const getRanges = (win, selection) => selection.match({
      domRange: rng => {
        return {
          ltr: constant(rng),
          rtl: Optional.none
        };
      },
      relative: (startSitu, finishSitu) => {
        return {
          ltr: cached(() => relativeToNative(win, startSitu, finishSitu)),
          rtl: cached(() => Optional.some(relativeToNative(win, finishSitu, startSitu)))
        };
      },
      exact: (start, soffset, finish, foffset) => {
        return {
          ltr: cached(() => exactToNative(win, start, soffset, finish, foffset)),
          rtl: cached(() => Optional.some(exactToNative(win, finish, foffset, start, soffset)))
        };
      }
    });
    const doDiagnose = (win, ranges) => {
      const rng = ranges.ltr();
      if (rng.collapsed) {
        const reversed = ranges.rtl().filter(rev => rev.collapsed === false);
        return reversed.map(rev => adt$3.rtl(SugarElement.fromDom(rev.endContainer), rev.endOffset, SugarElement.fromDom(rev.startContainer), rev.startOffset)).getOrThunk(() => fromRange(win, adt$3.ltr, rng));
      } else {
        return fromRange(win, adt$3.ltr, rng);
      }
    };
    const diagnose = (win, selection) => {
      const ranges = getRanges(win, selection);
      return doDiagnose(win, ranges);
    };
    adt$3.ltr;
    adt$3.rtl;

    const create$a = (start, soffset, finish, foffset) => ({
      start,
      soffset,
      finish,
      foffset
    });
    const SimRange = { create: create$a };

    const caretPositionFromPoint = (doc, x, y) => {
      var _a, _b;
      return Optional.from((_b = (_a = doc.dom).caretPositionFromPoint) === null || _b === void 0 ? void 0 : _b.call(_a, x, y)).bind(pos => {
        if (pos.offsetNode === null) {
          return Optional.none();
        }
        const r = doc.dom.createRange();
        r.setStart(pos.offsetNode, pos.offset);
        r.collapse();
        return Optional.some(r);
      });
    };
    const caretRangeFromPoint = (doc, x, y) => {
      var _a, _b;
      return Optional.from((_b = (_a = doc.dom).caretRangeFromPoint) === null || _b === void 0 ? void 0 : _b.call(_a, x, y));
    };
    const availableSearch = (() => {
      if (document.caretPositionFromPoint) {
        return caretPositionFromPoint;
      } else if (document.caretRangeFromPoint) {
        return caretRangeFromPoint;
      } else {
        return Optional.none;
      }
    })();
    const fromPoint$1 = (win, x, y) => {
      const doc = SugarElement.fromDom(win.document);
      return availableSearch(doc, x, y).map(rng => SimRange.create(SugarElement.fromDom(rng.startContainer), rng.startOffset, SugarElement.fromDom(rng.endContainer), rng.endOffset));
    };

    const adt$2 = Adt.generate([
      { before: ['element'] },
      {
        on: [
          'element',
          'offset'
        ]
      },
      { after: ['element'] }
    ]);
    const cata = (subject, onBefore, onOn, onAfter) => subject.fold(onBefore, onOn, onAfter);
    const getStart$2 = situ => situ.fold(identity, identity, identity);
    const before$1 = adt$2.before;
    const on = adt$2.on;
    const after$1 = adt$2.after;
    const Situ = {
      before: before$1,
      on,
      after: after$1,
      cata,
      getStart: getStart$2
    };

    const adt$1 = Adt.generate([
      { domRange: ['rng'] },
      {
        relative: [
          'startSitu',
          'finishSitu'
        ]
      },
      {
        exact: [
          'start',
          'soffset',
          'finish',
          'foffset'
        ]
      }
    ]);
    const exactFromRange = simRange => adt$1.exact(simRange.start, simRange.soffset, simRange.finish, simRange.foffset);
    const getStart$1 = selection => selection.match({
      domRange: rng => SugarElement.fromDom(rng.startContainer),
      relative: (startSitu, _finishSitu) => Situ.getStart(startSitu),
      exact: (start, _soffset, _finish, _foffset) => start
    });
    const domRange = adt$1.domRange;
    const relative = adt$1.relative;
    const exact = adt$1.exact;
    const getWin = selection => {
      const start = getStart$1(selection);
      return defaultView(start);
    };
    const range = SimRange.create;
    const SimSelection = {
      domRange,
      relative,
      exact,
      exactFromRange,
      getWin,
      range
    };

    const beforeSpecial = (element, offset) => {
      const name$1 = name(element);
      if ('input' === name$1) {
        return Situ.after(element);
      } else if (!contains$2([
          'br',
          'img'
        ], name$1)) {
        return Situ.on(element, offset);
      } else {
        return offset === 0 ? Situ.before(element) : Situ.after(element);
      }
    };
    const preprocessRelative = (startSitu, finishSitu) => {
      const start = startSitu.fold(Situ.before, beforeSpecial, Situ.after);
      const finish = finishSitu.fold(Situ.before, beforeSpecial, Situ.after);
      return SimSelection.relative(start, finish);
    };
    const preprocessExact = (start, soffset, finish, foffset) => {
      const startSitu = beforeSpecial(start, soffset);
      const finishSitu = beforeSpecial(finish, foffset);
      return SimSelection.relative(startSitu, finishSitu);
    };
    const preprocess = selection => selection.match({
      domRange: rng => {
        const start = SugarElement.fromDom(rng.startContainer);
        const finish = SugarElement.fromDom(rng.endContainer);
        return preprocessExact(start, rng.startOffset, finish, rng.endOffset);
      },
      relative: preprocessRelative,
      exact: preprocessExact
    });

    const fromElements = (elements, scope) => {
      const doc = scope || document;
      const fragment = doc.createDocumentFragment();
      each$e(elements, element => {
        fragment.appendChild(element.dom);
      });
      return SugarElement.fromDom(fragment);
    };

    const toNative = selection => {
      const win = SimSelection.getWin(selection).dom;
      const getDomRange = (start, soffset, finish, foffset) => exactToNative(win, start, soffset, finish, foffset);
      const filtered = preprocess(selection);
      return diagnose(win, filtered).match({
        ltr: getDomRange,
        rtl: getDomRange
      });
    };
    const getAtPoint = (win, x, y) => fromPoint$1(win, x, y);

    const fromPoint = (clientX, clientY, doc) => {
      const win = defaultView(SugarElement.fromDom(doc));
      return getAtPoint(win.dom, clientX, clientY).map(simRange => {
        const rng = doc.createRange();
        rng.setStart(simRange.start.dom, simRange.soffset);
        rng.setEnd(simRange.finish.dom, simRange.foffset);
        return rng;
      }).getOrUndefined();
    };

    const isEq$4 = (rng1, rng2) => {
      return isNonNullable(rng1) && isNonNullable(rng2) && (rng1.startContainer === rng2.startContainer && rng1.startOffset === rng2.startOffset) && (rng1.endContainer === rng2.endContainer && rng1.endOffset === rng2.endOffset);
    };

    const findParent = (node, rootNode, predicate) => {
      let currentNode = node;
      while (currentNode && currentNode !== rootNode) {
        if (predicate(currentNode)) {
          return currentNode;
        }
        currentNode = currentNode.parentNode;
      }
      return null;
    };
    const hasParent$1 = (node, rootNode, predicate) => findParent(node, rootNode, predicate) !== null;
    const hasParentWithName = (node, rootNode, name) => hasParent$1(node, rootNode, node => node.nodeName === name);
    const isCeFalseCaretContainer = (node, rootNode) => isCaretContainer$2(node) && !hasParent$1(node, rootNode, isCaretNode);
    const hasBrBeforeAfter = (dom, node, left) => {
      const parentNode = node.parentNode;
      if (parentNode) {
        const walker = new DomTreeWalker(node, dom.getParent(parentNode, dom.isBlock) || dom.getRoot());
        let currentNode;
        while (currentNode = walker[left ? 'prev' : 'next']()) {
          if (isBr$6(currentNode)) {
            return true;
          }
        }
      }
      return false;
    };
    const isPrevNode = (node, name) => {
      var _a;
      return ((_a = node.previousSibling) === null || _a === void 0 ? void 0 : _a.nodeName) === name;
    };
    const hasContentEditableFalseParent = (root, node) => {
      let currentNode = node;
      while (currentNode && currentNode !== root) {
        if (isContentEditableFalse$b(currentNode)) {
          return true;
        }
        currentNode = currentNode.parentNode;
      }
      return false;
    };
    const findTextNodeRelative = (dom, isAfterNode, collapsed, left, startNode) => {
      const body = dom.getRoot();
      const nonEmptyElementsMap = dom.schema.getNonEmptyElements();
      const parentNode = startNode.parentNode;
      let lastInlineElement;
      let node;
      if (!parentNode) {
        return Optional.none();
      }
      const parentBlockContainer = dom.getParent(parentNode, dom.isBlock) || body;
      if (left && isBr$6(startNode) && isAfterNode && dom.isEmpty(parentBlockContainer)) {
        return Optional.some(CaretPosition(parentNode, dom.nodeIndex(startNode)));
      }
      const walker = new DomTreeWalker(startNode, parentBlockContainer);
      while (node = walker[left ? 'prev' : 'next']()) {
        if (dom.getContentEditableParent(node) === 'false' || isCeFalseCaretContainer(node, body)) {
          return Optional.none();
        }
        if (isText$a(node) && node.data.length > 0) {
          if (!hasParentWithName(node, body, 'A')) {
            return Optional.some(CaretPosition(node, left ? node.data.length : 0));
          }
          return Optional.none();
        }
        if (dom.isBlock(node) || nonEmptyElementsMap[node.nodeName.toLowerCase()]) {
          return Optional.none();
        }
        lastInlineElement = node;
      }
      if (isComment(lastInlineElement)) {
        return Optional.none();
      }
      if (collapsed && lastInlineElement) {
        return Optional.some(CaretPosition(lastInlineElement, 0));
      }
      return Optional.none();
    };
    const normalizeEndPoint = (dom, collapsed, start, rng) => {
      const body = dom.getRoot();
      let node;
      let normalized = false;
      let container = start ? rng.startContainer : rng.endContainer;
      let offset = start ? rng.startOffset : rng.endOffset;
      const isAfterNode = isElement$6(container) && offset === container.childNodes.length;
      const nonEmptyElementsMap = dom.schema.getNonEmptyElements();
      let directionLeft = start;
      if (isCaretContainer$2(container)) {
        return Optional.none();
      }
      if (isElement$6(container) && offset > container.childNodes.length - 1) {
        directionLeft = false;
      }
      if (isDocument$1(container)) {
        container = body;
        offset = 0;
      }
      if (container === body) {
        if (directionLeft) {
          node = container.childNodes[offset > 0 ? offset - 1 : 0];
          if (node) {
            if (isCaretContainer$2(node)) {
              return Optional.none();
            }
            if (nonEmptyElementsMap[node.nodeName] || isTable$2(node)) {
              return Optional.none();
            }
          }
        }
        if (container.hasChildNodes()) {
          offset = Math.min(!directionLeft && offset > 0 ? offset - 1 : offset, container.childNodes.length - 1);
          container = container.childNodes[offset];
          offset = isText$a(container) && isAfterNode ? container.data.length : 0;
          if (!collapsed && container === body.lastChild && isTable$2(container)) {
            return Optional.none();
          }
          if (hasContentEditableFalseParent(body, container) || isCaretContainer$2(container)) {
            return Optional.none();
          }
          if (isDetails(container)) {
            return Optional.none();
          }
          if (container.hasChildNodes() && !isTable$2(container)) {
            node = container;
            const walker = new DomTreeWalker(container, body);
            do {
              if (isContentEditableFalse$b(node) || isCaretContainer$2(node)) {
                normalized = false;
                break;
              }
              if (isText$a(node) && node.data.length > 0) {
                offset = directionLeft ? 0 : node.data.length;
                container = node;
                normalized = true;
                break;
              }
              if (nonEmptyElementsMap[node.nodeName.toLowerCase()] && !isTableCellOrCaption(node)) {
                offset = dom.nodeIndex(node);
                container = node.parentNode;
                if (!directionLeft) {
                  offset++;
                }
                normalized = true;
                break;
              }
            } while (node = directionLeft ? walker.next() : walker.prev());
          }
        }
      }
      if (collapsed) {
        if (isText$a(container) && offset === 0) {
          findTextNodeRelative(dom, isAfterNode, collapsed, true, container).each(pos => {
            container = pos.container();
            offset = pos.offset();
            normalized = true;
          });
        }
        if (isElement$6(container)) {
          node = container.childNodes[offset];
          if (!node) {
            node = container.childNodes[offset - 1];
          }
          if (node && isBr$6(node) && !isPrevNode(node, 'A') && !hasBrBeforeAfter(dom, node, false) && !hasBrBeforeAfter(dom, node, true)) {
            findTextNodeRelative(dom, isAfterNode, collapsed, true, node).each(pos => {
              container = pos.container();
              offset = pos.offset();
              normalized = true;
            });
          }
        }
      }
      if (directionLeft && !collapsed && isText$a(container) && offset === container.data.length) {
        findTextNodeRelative(dom, isAfterNode, collapsed, false, container).each(pos => {
          container = pos.container();
          offset = pos.offset();
          normalized = true;
        });
      }
      return normalized && container ? Optional.some(CaretPosition(container, offset)) : Optional.none();
    };
    const normalize$2 = (dom, rng) => {
      const collapsed = rng.collapsed, normRng = rng.cloneRange();
      const startPos = CaretPosition.fromRangeStart(rng);
      normalizeEndPoint(dom, collapsed, true, normRng).each(pos => {
        if (!collapsed || !CaretPosition.isAbove(startPos, pos)) {
          normRng.setStart(pos.container(), pos.offset());
        }
      });
      if (!collapsed) {
        normalizeEndPoint(dom, collapsed, false, normRng).each(pos => {
          normRng.setEnd(pos.container(), pos.offset());
        });
      }
      if (collapsed) {
        normRng.collapse(true);
      }
      return isEq$4(rng, normRng) ? Optional.none() : Optional.some(normRng);
    };

    const splitText = (node, offset) => {
      return node.splitText(offset);
    };
    const split = rng => {
      let startContainer = rng.startContainer, startOffset = rng.startOffset, endContainer = rng.endContainer, endOffset = rng.endOffset;
      if (startContainer === endContainer && isText$a(startContainer)) {
        if (startOffset > 0 && startOffset < startContainer.data.length) {
          endContainer = splitText(startContainer, startOffset);
          startContainer = endContainer.previousSibling;
          if (endOffset > startOffset) {
            endOffset = endOffset - startOffset;
            const newContainer = splitText(endContainer, endOffset).previousSibling;
            startContainer = endContainer = newContainer;
            endOffset = newContainer.data.length;
            startOffset = 0;
          } else {
            endOffset = 0;
          }
        }
      } else {
        if (isText$a(startContainer) && startOffset > 0 && startOffset < startContainer.data.length) {
          startContainer = splitText(startContainer, startOffset);
          startOffset = 0;
        }
        if (isText$a(endContainer) && endOffset > 0 && endOffset < endContainer.data.length) {
          const newContainer = splitText(endContainer, endOffset).previousSibling;
          endContainer = newContainer;
          endOffset = newContainer.data.length;
        }
      }
      return {
        startContainer,
        startOffset,
        endContainer,
        endOffset
      };
    };

    const RangeUtils = dom => {
      const walk = (rng, callback) => {
        return walk$3(dom, rng, callback);
      };
      const split$1 = split;
      const normalize = rng => {
        return normalize$2(dom, rng).fold(never, normalizedRng => {
          rng.setStart(normalizedRng.startContainer, normalizedRng.startOffset);
          rng.setEnd(normalizedRng.endContainer, normalizedRng.endOffset);
          return true;
        });
      };
      const expand = (rng, options = { type: 'word' }) => {
        if (options.type === 'word') {
          const rangeLike = expandRng(dom, rng, [{ inline: 'span' }]);
          const newRange = dom.createRng();
          newRange.setStart(rangeLike.startContainer, rangeLike.startOffset);
          newRange.setEnd(rangeLike.endContainer, rangeLike.endOffset);
          return newRange;
        }
        return rng;
      };
      return {
        walk,
        split: split$1,
        expand,
        normalize
      };
    };
    RangeUtils.compareRanges = isEq$4;
    RangeUtils.getCaretRangeFromPoint = fromPoint;
    RangeUtils.getSelectedNode = getSelectedNode;
    RangeUtils.getNode = getNode$1;

    const Dimension = (name, getOffset) => {
      const set = (element, h) => {
        if (!isNumber(h) && !h.match(/^[0-9]+$/)) {
          throw new Error(name + '.set accepts only positive integer values. Value was ' + h);
        }
        const dom = element.dom;
        if (isSupported(dom)) {
          dom.style[name] = h + 'px';
        }
      };
      const get = element => {
        const r = getOffset(element);
        if (r <= 0 || r === null) {
          const css = get$7(element, name);
          return parseFloat(css) || 0;
        }
        return r;
      };
      const getOuter = get;
      const aggregate = (element, properties) => foldl(properties, (acc, property) => {
        const val = get$7(element, property);
        const value = val === undefined ? 0 : parseInt(val, 10);
        return isNaN(value) ? acc : acc + value;
      }, 0);
      const max = (element, value, properties) => {
        const cumulativeInclusions = aggregate(element, properties);
        const absoluteMax = value > cumulativeInclusions ? value - cumulativeInclusions : 0;
        return absoluteMax;
      };
      return {
        set,
        get,
        getOuter,
        aggregate,
        max
      };
    };

    const api = Dimension('height', element => {
      const dom = element.dom;
      return inBody(element) ? dom.getBoundingClientRect().height : dom.offsetHeight;
    });
    const get$2 = element => api.get(element);

    const getDocument = () => SugarElement.fromDom(document);

    const walkUp = (navigation, doc) => {
      const frame = navigation.view(doc);
      return frame.fold(constant([]), f => {
        const parent = navigation.owner(f);
        const rest = walkUp(navigation, parent);
        return [f].concat(rest);
      });
    };
    const pathTo = (element, navigation) => {
      const d = navigation.owner(element);
      return walkUp(navigation, d);
    };

    const view = doc => {
      var _a;
      const element = doc.dom === document ? Optional.none() : Optional.from((_a = doc.dom.defaultView) === null || _a === void 0 ? void 0 : _a.frameElement);
      return element.map(SugarElement.fromDom);
    };
    const owner = element => documentOrOwner(element);

    var Navigation = /*#__PURE__*/Object.freeze({
        __proto__: null,
        view: view,
        owner: owner
    });

    const find = element => {
      const doc = getDocument();
      const scroll = get$5(doc);
      const frames = pathTo(element, Navigation);
      const offset = viewport(element);
      const r = foldr(frames, (b, a) => {
        const loc = viewport(a);
        return {
          left: b.left + loc.left,
          top: b.top + loc.top
        };
      }, {
        left: 0,
        top: 0
      });
      return SugarPosition(r.left + offset.left + scroll.left, r.top + offset.top + scroll.top);
    };

    const excludeFromDescend = element => name(element) === 'textarea';
    const fireScrollIntoViewEvent = (editor, data) => {
      const scrollEvent = editor.dispatch('ScrollIntoView', data);
      return scrollEvent.isDefaultPrevented();
    };
    const fireAfterScrollIntoViewEvent = (editor, data) => {
      editor.dispatch('AfterScrollIntoView', data);
    };
    const descend = (element, offset) => {
      const children = children$1(element);
      if (children.length === 0 || excludeFromDescend(element)) {
        return {
          element,
          offset
        };
      } else if (offset < children.length && !excludeFromDescend(children[offset])) {
        return {
          element: children[offset],
          offset: 0
        };
      } else {
        const last = children[children.length - 1];
        if (excludeFromDescend(last)) {
          return {
            element,
            offset
          };
        } else {
          if (name(last) === 'img') {
            return {
              element: last,
              offset: 1
            };
          } else if (isText$b(last)) {
            return {
              element: last,
              offset: get$3(last).length
            };
          } else {
            return {
              element: last,
              offset: children$1(last).length
            };
          }
        }
      }
    };
    const markerInfo = (element, cleanupFun) => {
      const pos = absolute(element);
      const height = get$2(element);
      return {
        element,
        bottom: pos.top + height,
        height,
        pos,
        cleanup: cleanupFun
      };
    };
    const createMarker$1 = (element, offset) => {
      const startPoint = descend(element, offset);
      const span = SugarElement.fromHtml('<span data-mce-bogus="all" style="display: inline-block;">' + ZWSP$1 + '</span>');
      before$3(startPoint.element, span);
      return markerInfo(span, () => remove$5(span));
    };
    const elementMarker = element => markerInfo(SugarElement.fromDom(element), noop);
    const withMarker = (editor, f, rng, alignToTop) => {
      preserveWith(editor, (_s, _e) => applyWithMarker(editor, f, rng, alignToTop), rng);
    };
    const withScrollEvents = (editor, doc, f, marker, alignToTop) => {
      const data = {
        elm: marker.element.dom,
        alignToTop
      };
      if (fireScrollIntoViewEvent(editor, data)) {
        return;
      }
      const scrollTop = get$5(doc).top;
      f(editor, doc, scrollTop, marker, alignToTop);
      fireAfterScrollIntoViewEvent(editor, data);
    };
    const applyWithMarker = (editor, f, rng, alignToTop) => {
      const body = SugarElement.fromDom(editor.getBody());
      const doc = SugarElement.fromDom(editor.getDoc());
      reflow(body);
      const marker = createMarker$1(SugarElement.fromDom(rng.startContainer), rng.startOffset);
      withScrollEvents(editor, doc, f, marker, alignToTop);
      marker.cleanup();
    };
    const withElement = (editor, element, f, alignToTop) => {
      const doc = SugarElement.fromDom(editor.getDoc());
      withScrollEvents(editor, doc, f, elementMarker(element), alignToTop);
    };
    const preserveWith = (editor, f, rng) => {
      const startElement = rng.startContainer;
      const startOffset = rng.startOffset;
      const endElement = rng.endContainer;
      const endOffset = rng.endOffset;
      f(SugarElement.fromDom(startElement), SugarElement.fromDom(endElement));
      const newRng = editor.dom.createRng();
      newRng.setStart(startElement, startOffset);
      newRng.setEnd(endElement, endOffset);
      editor.selection.setRng(rng);
    };
    const scrollToMarker = (editor, marker, viewHeight, alignToTop, doc) => {
      const pos = marker.pos;
      if (alignToTop) {
        to(pos.left, pos.top, doc);
      } else {
        const y = pos.top - viewHeight + marker.height;
        to(-editor.getBody().getBoundingClientRect().left, y, doc);
      }
    };
    const intoWindowIfNeeded = (editor, doc, scrollTop, viewHeight, marker, alignToTop) => {
      const viewportBottom = viewHeight + scrollTop;
      const markerTop = marker.pos.top;
      const markerBottom = marker.bottom;
      const largerThanViewport = markerBottom - markerTop >= viewHeight;
      if (markerTop < scrollTop) {
        scrollToMarker(editor, marker, viewHeight, alignToTop !== false, doc);
      } else if (markerTop > viewportBottom) {
        const align = largerThanViewport ? alignToTop !== false : alignToTop === true;
        scrollToMarker(editor, marker, viewHeight, align, doc);
      } else if (markerBottom > viewportBottom && !largerThanViewport) {
        scrollToMarker(editor, marker, viewHeight, alignToTop === true, doc);
      }
    };
    const intoWindow = (editor, doc, scrollTop, marker, alignToTop) => {
      const viewHeight = defaultView(doc).dom.innerHeight;
      intoWindowIfNeeded(editor, doc, scrollTop, viewHeight, marker, alignToTop);
    };
    const intoFrame = (editor, doc, scrollTop, marker, alignToTop) => {
      const frameViewHeight = defaultView(doc).dom.innerHeight;
      intoWindowIfNeeded(editor, doc, scrollTop, frameViewHeight, marker, alignToTop);
      const op = find(marker.element);
      const viewportBounds = getBounds(window);
      if (op.top < viewportBounds.y) {
        intoView(marker.element, alignToTop !== false);
      } else if (op.top > viewportBounds.bottom) {
        intoView(marker.element, alignToTop === true);
      }
    };
    const rangeIntoWindow = (editor, rng, alignToTop) => withMarker(editor, intoWindow, rng, alignToTop);
    const elementIntoWindow = (editor, element, alignToTop) => withElement(editor, element, intoWindow, alignToTop);
    const rangeIntoFrame = (editor, rng, alignToTop) => withMarker(editor, intoFrame, rng, alignToTop);
    const elementIntoFrame = (editor, element, alignToTop) => withElement(editor, element, intoFrame, alignToTop);
    const scrollElementIntoView = (editor, element, alignToTop) => {
      const scroller = editor.inline ? elementIntoWindow : elementIntoFrame;
      scroller(editor, element, alignToTop);
    };
    const scrollRangeIntoView = (editor, rng, alignToTop) => {
      const scroller = editor.inline ? rangeIntoWindow : rangeIntoFrame;
      scroller(editor, rng, alignToTop);
    };

    const focus$1 = (element, preventScroll = false) => element.dom.focus({ preventScroll });
    const hasFocus$1 = element => {
      const root = getRootNode(element).dom;
      return element.dom === root.activeElement;
    };
    const active$1 = (root = getDocument()) => Optional.from(root.dom.activeElement).map(SugarElement.fromDom);
    const search = element => active$1(getRootNode(element)).filter(e => element.dom.contains(e.dom));

    const clamp$1 = (offset, element) => {
      const max = isText$b(element) ? get$3(element).length : children$1(element).length + 1;
      if (offset > max) {
        return max;
      } else if (offset < 0) {
        return 0;
      }
      return offset;
    };
    const normalizeRng = rng => SimSelection.range(rng.start, clamp$1(rng.soffset, rng.start), rng.finish, clamp$1(rng.foffset, rng.finish));
    const isOrContains = (root, elm) => !isRestrictedNode(elm.dom) && (contains(root, elm) || eq(root, elm));
    const isRngInRoot = root => rng => isOrContains(root, rng.start) && isOrContains(root, rng.finish);
    const shouldStore = editor => editor.inline || Env.browser.isFirefox();
    const nativeRangeToSelectionRange = r => SimSelection.range(SugarElement.fromDom(r.startContainer), r.startOffset, SugarElement.fromDom(r.endContainer), r.endOffset);
    const readRange = win => {
      const selection = win.getSelection();
      const rng = !selection || selection.rangeCount === 0 ? Optional.none() : Optional.from(selection.getRangeAt(0));
      return rng.map(nativeRangeToSelectionRange);
    };
    const getBookmark = root => {
      const win = defaultView(root);
      return readRange(win.dom).filter(isRngInRoot(root));
    };
    const validate = (root, bookmark) => Optional.from(bookmark).filter(isRngInRoot(root)).map(normalizeRng);
    const bookmarkToNativeRng = bookmark => {
      const rng = document.createRange();
      try {
        rng.setStart(bookmark.start.dom, bookmark.soffset);
        rng.setEnd(bookmark.finish.dom, bookmark.foffset);
        return Optional.some(rng);
      } catch (_) {
        return Optional.none();
      }
    };
    const store = editor => {
      const newBookmark = shouldStore(editor) ? getBookmark(SugarElement.fromDom(editor.getBody())) : Optional.none();
      editor.bookmark = newBookmark.isSome() ? newBookmark : editor.bookmark;
    };
    const getRng = editor => {
      const bookmark = editor.bookmark ? editor.bookmark : Optional.none();
      return bookmark.bind(x => validate(SugarElement.fromDom(editor.getBody()), x)).bind(bookmarkToNativeRng);
    };
    const restore = editor => {
      getRng(editor).each(rng => editor.selection.setRng(rng));
    };

    const isEditorUIElement$1 = elm => {
      const className = elm.className.toString();
      return className.indexOf('tox-') !== -1 || className.indexOf('mce-') !== -1;
    };
    const FocusManager = { isEditorUIElement: isEditorUIElement$1 };

    const wrappedSetTimeout = (callback, time) => {
      if (!isNumber(time)) {
        time = 0;
      }
      return setTimeout(callback, time);
    };
    const wrappedSetInterval = (callback, time) => {
      if (!isNumber(time)) {
        time = 0;
      }
      return setInterval(callback, time);
    };
    const Delay = {
      setEditorTimeout: (editor, callback, time) => {
        return wrappedSetTimeout(() => {
          if (!editor.removed) {
            callback();
          }
        }, time);
      },
      setEditorInterval: (editor, callback, time) => {
        const timer = wrappedSetInterval(() => {
          if (!editor.removed) {
            callback();
          } else {
            clearInterval(timer);
          }
        }, time);
        return timer;
      }
    };

    const isManualNodeChange = e => {
      return e.type === 'nodechange' && e.selectionChange;
    };
    const registerPageMouseUp = (editor, throttledStore) => {
      const mouseUpPage = () => {
        throttledStore.throttle();
      };
      DOMUtils.DOM.bind(document, 'mouseup', mouseUpPage);
      editor.on('remove', () => {
        DOMUtils.DOM.unbind(document, 'mouseup', mouseUpPage);
      });
    };
    const registerMouseUp = (editor, throttledStore) => {
      editor.on('mouseup touchend', _e => {
        throttledStore.throttle();
      });
    };
    const registerEditorEvents = (editor, throttledStore) => {
      registerMouseUp(editor, throttledStore);
      editor.on('keyup NodeChange AfterSetSelectionRange', e => {
        if (!isManualNodeChange(e)) {
          store(editor);
        }
      });
    };
    const register$6 = editor => {
      const throttledStore = first$1(() => {
        store(editor);
      }, 0);
      editor.on('init', () => {
        if (editor.inline) {
          registerPageMouseUp(editor, throttledStore);
        }
        registerEditorEvents(editor, throttledStore);
      });
      editor.on('remove', () => {
        throttledStore.cancel();
      });
    };

    let documentFocusInHandler;
    const DOM$9 = DOMUtils.DOM;
    const isEditorUIElement = elm => {
      return isElement$6(elm) && FocusManager.isEditorUIElement(elm);
    };
    const isEditorContentAreaElement = elm => {
      const classList = elm.classList;
      if (classList !== undefined) {
        return classList.contains('tox-edit-area') || classList.contains('tox-edit-area__iframe') || classList.contains('mce-content-body');
      } else {
        return false;
      }
    };
    const isUIElement = (editor, elm) => {
      const customSelector = getCustomUiSelector(editor);
      const parent = DOM$9.getParent(elm, elm => {
        return isEditorUIElement(elm) || (customSelector ? editor.dom.is(elm, customSelector) : false);
      });
      return parent !== null;
    };
    const getActiveElement = editor => {
      try {
        const root = getRootNode(SugarElement.fromDom(editor.getElement()));
        return active$1(root).fold(() => document.body, x => x.dom);
      } catch (ex) {
        return document.body;
      }
    };
    const registerEvents$1 = (editorManager, e) => {
      const editor = e.editor;
      register$6(editor);
      const toggleContentAreaOnFocus = (editor, fn) => {
        if (shouldHighlightOnFocus(editor) && editor.inline !== true) {
          const contentArea = SugarElement.fromDom(editor.getContainer());
          fn(contentArea, 'tox-edit-focus');
        }
      };
      editor.on('focusin', () => {
        const focusedEditor = editorManager.focusedEditor;
        if (isEditorContentAreaElement(getActiveElement(editor))) {
          toggleContentAreaOnFocus(editor, add$2);
        }
        if (focusedEditor !== editor) {
          if (focusedEditor) {
            focusedEditor.dispatch('blur', { focusedEditor: editor });
          }
          editorManager.setActive(editor);
          editorManager.focusedEditor = editor;
          editor.dispatch('focus', { blurredEditor: focusedEditor });
          editor.focus(true);
        }
      });
      editor.on('focusout', () => {
        Delay.setEditorTimeout(editor, () => {
          const focusedEditor = editorManager.focusedEditor;
          if (!isEditorContentAreaElement(getActiveElement(editor)) || focusedEditor !== editor) {
            toggleContentAreaOnFocus(editor, remove$7);
          }
          if (!isUIElement(editor, getActiveElement(editor)) && focusedEditor === editor) {
            editor.dispatch('blur', { focusedEditor: null });
            editorManager.focusedEditor = null;
          }
        });
      });
      if (!documentFocusInHandler) {
        documentFocusInHandler = e => {
          const activeEditor = editorManager.activeEditor;
          if (activeEditor) {
            getOriginalEventTarget(e).each(target => {
              const elem = target;
              if (elem.ownerDocument === document) {
                if (elem !== document.body && !isUIElement(activeEditor, elem) && editorManager.focusedEditor === activeEditor) {
                  activeEditor.dispatch('blur', { focusedEditor: null });
                  editorManager.focusedEditor = null;
                }
              }
            });
          }
        };
        DOM$9.bind(document, 'focusin', documentFocusInHandler);
      }
    };
    const unregisterDocumentEvents = (editorManager, e) => {
      if (editorManager.focusedEditor === e.editor) {
        editorManager.focusedEditor = null;
      }
      if (!editorManager.activeEditor && documentFocusInHandler) {
        DOM$9.unbind(document, 'focusin', documentFocusInHandler);
        documentFocusInHandler = null;
      }
    };
    const setup$w = editorManager => {
      editorManager.on('AddEditor', curry(registerEvents$1, editorManager));
      editorManager.on('RemoveEditor', curry(unregisterDocumentEvents, editorManager));
    };

    const getContentEditableHost = (editor, node) => editor.dom.getParent(node, node => editor.dom.getContentEditable(node) === 'true');
    const getCollapsedNode = rng => rng.collapsed ? Optional.from(getNode$1(rng.startContainer, rng.startOffset)).map(SugarElement.fromDom) : Optional.none();
    const getFocusInElement = (root, rng) => getCollapsedNode(rng).bind(node => {
      if (isTableSection(node)) {
        return Optional.some(node);
      } else if (!contains(root, node)) {
        return Optional.some(root);
      } else {
        return Optional.none();
      }
    });
    const normalizeSelection = (editor, rng) => {
      getFocusInElement(SugarElement.fromDom(editor.getBody()), rng).bind(elm => {
        return firstPositionIn(elm.dom);
      }).fold(() => {
        editor.selection.normalize();
      }, caretPos => editor.selection.setRng(caretPos.toRange()));
    };
    const focusBody = body => {
      if (body.setActive) {
        try {
          body.setActive();
        } catch (ex) {
          body.focus();
        }
      } else {
        body.focus();
      }
    };
    const hasElementFocus = elm => hasFocus$1(elm) || search(elm).isSome();
    const hasIframeFocus = editor => isNonNullable(editor.iframeElement) && hasFocus$1(SugarElement.fromDom(editor.iframeElement));
    const hasInlineFocus = editor => {
      const rawBody = editor.getBody();
      return rawBody && hasElementFocus(SugarElement.fromDom(rawBody));
    };
    const hasUiFocus = editor => {
      const dos = getRootNode(SugarElement.fromDom(editor.getElement()));
      return active$1(dos).filter(elem => !isEditorContentAreaElement(elem.dom) && isUIElement(editor, elem.dom)).isSome();
    };
    const hasFocus = editor => editor.inline ? hasInlineFocus(editor) : hasIframeFocus(editor);
    const hasEditorOrUiFocus = editor => hasFocus(editor) || hasUiFocus(editor);
    const focusEditor = editor => {
      const selection = editor.selection;
      const body = editor.getBody();
      let rng = selection.getRng();
      editor.quirks.refreshContentEditable();
      if (isNonNullable(editor.bookmark) && !hasFocus(editor)) {
        getRng(editor).each(bookmarkRng => {
          editor.selection.setRng(bookmarkRng);
          rng = bookmarkRng;
        });
      }
      const contentEditableHost = getContentEditableHost(editor, selection.getNode());
      if (contentEditableHost && editor.dom.isChildOf(contentEditableHost, body)) {
        focusBody(contentEditableHost);
        normalizeSelection(editor, rng);
        activateEditor(editor);
        return;
      }
      if (!editor.inline) {
        if (!Env.browser.isOpera()) {
          focusBody(body);
        }
        editor.getWin().focus();
      }
      if (Env.browser.isFirefox() || editor.inline) {
        focusBody(body);
        normalizeSelection(editor, rng);
      }
      activateEditor(editor);
    };
    const activateEditor = editor => editor.editorManager.setActive(editor);
    const focus = (editor, skipFocus) => {
      if (editor.removed) {
        return;
      }
      if (skipFocus) {
        activateEditor(editor);
      } else {
        focusEditor(editor);
      }
    };

    const isEditableRange = (dom, rng) => {
      if (rng.collapsed) {
        return dom.isEditable(rng.startContainer);
      } else {
        return dom.isEditable(rng.startContainer) && dom.isEditable(rng.endContainer);
      }
    };

    const getEndpointElement = (root, rng, start, real, resolve) => {
      const container = start ? rng.startContainer : rng.endContainer;
      const offset = start ? rng.startOffset : rng.endOffset;
      return Optional.from(container).map(SugarElement.fromDom).map(elm => !real || !rng.collapsed ? child$1(elm, resolve(elm, offset)).getOr(elm) : elm).bind(elm => isElement$7(elm) ? Optional.some(elm) : parent(elm).filter(isElement$7)).map(elm => elm.dom).getOr(root);
    };
    const getStart = (root, rng, real = false) => getEndpointElement(root, rng, true, real, (elm, offset) => Math.min(childNodesCount(elm), offset));
    const getEnd$1 = (root, rng, real = false) => getEndpointElement(root, rng, false, real, (elm, offset) => offset > 0 ? offset - 1 : offset);
    const skipEmptyTextNodes = (node, forwards) => {
      const orig = node;
      while (node && isText$a(node) && node.length === 0) {
        node = forwards ? node.nextSibling : node.previousSibling;
      }
      return node || orig;
    };
    const getNode = (root, rng) => {
      if (!rng) {
        return root;
      }
      let startContainer = rng.startContainer;
      let endContainer = rng.endContainer;
      const startOffset = rng.startOffset;
      const endOffset = rng.endOffset;
      let node = rng.commonAncestorContainer;
      if (!rng.collapsed) {
        if (startContainer === endContainer) {
          if (endOffset - startOffset < 2) {
            if (startContainer.hasChildNodes()) {
              node = startContainer.childNodes[startOffset];
            }
          }
        }
        if (isText$a(startContainer) && isText$a(endContainer)) {
          if (startContainer.length === startOffset) {
            startContainer = skipEmptyTextNodes(startContainer.nextSibling, true);
          } else {
            startContainer = startContainer.parentNode;
          }
          if (endOffset === 0) {
            endContainer = skipEmptyTextNodes(endContainer.previousSibling, false);
          } else {
            endContainer = endContainer.parentNode;
          }
          if (startContainer && startContainer === endContainer) {
            node = startContainer;
          }
        }
      }
      const elm = isText$a(node) ? node.parentNode : node;
      return isHTMLElement(elm) ? elm : root;
    };
    const getSelectedBlocks = (dom, rng, startElm, endElm) => {
      const selectedBlocks = [];
      const root = dom.getRoot();
      const start = dom.getParent(startElm || getStart(root, rng, rng.collapsed), dom.isBlock);
      const end = dom.getParent(endElm || getEnd$1(root, rng, rng.collapsed), dom.isBlock);
      if (start && start !== root) {
        selectedBlocks.push(start);
      }
      if (start && end && start !== end) {
        let node;
        const walker = new DomTreeWalker(start, root);
        while ((node = walker.next()) && node !== end) {
          if (dom.isBlock(node)) {
            selectedBlocks.push(node);
          }
        }
      }
      if (end && start !== end && end !== root) {
        selectedBlocks.push(end);
      }
      return selectedBlocks;
    };
    const select = (dom, node, content) => Optional.from(node).bind(node => Optional.from(node.parentNode).map(parent => {
      const idx = dom.nodeIndex(node);
      const rng = dom.createRng();
      rng.setStart(parent, idx);
      rng.setEnd(parent, idx + 1);
      if (content) {
        moveEndPoint(dom, rng, node, true);
        moveEndPoint(dom, rng, node, false);
      }
      return rng;
    }));

    const processRanges = (editor, ranges) => map$3(ranges, range => {
      const evt = editor.dispatch('GetSelectionRange', { range });
      return evt.range !== range ? evt.range : range;
    });

    const getEnd = element => name(element) === 'img' ? 1 : getOption(element).fold(() => children$1(element).length, v => v.length);
    const isTextNodeWithCursorPosition = el => getOption(el).filter(text => text.trim().length !== 0 || text.indexOf(nbsp) > -1).isSome();
    const isContentEditableFalse$5 = elem => isHTMLElement$1(elem) && get$9(elem, 'contenteditable') === 'false';
    const elementsWithCursorPosition = [
      'img',
      'br'
    ];
    const isCursorPosition = elem => {
      const hasCursorPosition = isTextNodeWithCursorPosition(elem);
      return hasCursorPosition || contains$2(elementsWithCursorPosition, name(elem)) || isContentEditableFalse$5(elem);
    };

    const first = element => descendant$2(element, isCursorPosition);
    const last = element => descendantRtl(element, isCursorPosition);
    const descendantRtl = (scope, predicate) => {
      const descend = element => {
        const children = children$1(element);
        for (let i = children.length - 1; i >= 0; i--) {
          const child = children[i];
          if (predicate(child)) {
            return Optional.some(child);
          }
          const res = descend(child);
          if (res.isSome()) {
            return res;
          }
        }
        return Optional.none();
      };
      return descend(scope);
    };

    const autocompleteSelector = '[data-mce-autocompleter]';
    const create$9 = (editor, range) => {
      if (findIn(SugarElement.fromDom(editor.getBody())).isNone()) {
        const wrapper = SugarElement.fromHtml('<span data-mce-autocompleter="1" data-mce-bogus="1"></span>', editor.getDoc());
        append$1(wrapper, SugarElement.fromDom(range.extractContents()));
        range.insertNode(wrapper.dom);
        parent(wrapper).each(elm => elm.dom.normalize());
        last(wrapper).map(last => {
          editor.selection.setCursorLocation(last.dom, getEnd(last));
        });
      }
    };
    const detect$1 = elm => closest$3(elm, autocompleteSelector);
    const findIn = elm => descendant$1(elm, autocompleteSelector);
    const remove$2 = (editor, elm) => findIn(elm).each(wrapper => {
      const bookmark = editor.selection.getBookmark();
      unwrap(wrapper);
      editor.selection.moveToBookmark(bookmark);
    });

    const typeLookup = {
      '#text': 3,
      '#comment': 8,
      '#cdata': 4,
      '#pi': 7,
      '#doctype': 10,
      '#document-fragment': 11
    };
    const walk$2 = (node, root, prev) => {
      const startName = prev ? 'lastChild' : 'firstChild';
      const siblingName = prev ? 'prev' : 'next';
      if (node[startName]) {
        return node[startName];
      }
      if (node !== root) {
        let sibling = node[siblingName];
        if (sibling) {
          return sibling;
        }
        for (let parent = node.parent; parent && parent !== root; parent = parent.parent) {
          sibling = parent[siblingName];
          if (sibling) {
            return sibling;
          }
        }
      }
      return undefined;
    };
    const isEmptyTextNode = node => {
      var _a;
      const text = (_a = node.value) !== null && _a !== void 0 ? _a : '';
      if (!isWhitespaceText(text)) {
        return false;
      }
      const parentNode = node.parent;
      if (parentNode && (parentNode.name !== 'span' || parentNode.attr('style')) && /^[ ]+$/.test(text)) {
        return false;
      }
      return true;
    };
    const isNonEmptyElement = node => {
      const isNamedAnchor = node.name === 'a' && !node.attr('href') && node.attr('id');
      return node.attr('name') || node.attr('id') && !node.firstChild || node.attr('data-mce-bookmark') || isNamedAnchor;
    };
    class AstNode {
      static create(name, attrs) {
        const node = new AstNode(name, typeLookup[name] || 1);
        if (attrs) {
          each$d(attrs, (value, attrName) => {
            node.attr(attrName, value);
          });
        }
        return node;
      }
      constructor(name, type) {
        this.name = name;
        this.type = type;
        if (type === 1) {
          this.attributes = [];
          this.attributes.map = {};
        }
      }
      replace(node) {
        const self = this;
        if (node.parent) {
          node.remove();
        }
        self.insert(node, self);
        self.remove();
        return self;
      }
      attr(name, value) {
        const self = this;
        if (!isString(name)) {
          if (isNonNullable(name)) {
            each$d(name, (value, key) => {
              self.attr(key, value);
            });
          }
          return self;
        }
        const attrs = self.attributes;
        if (attrs) {
          if (value !== undefined) {
            if (value === null) {
              if (name in attrs.map) {
                delete attrs.map[name];
                let i = attrs.length;
                while (i--) {
                  if (attrs[i].name === name) {
                    attrs.splice(i, 1);
                    return self;
                  }
                }
              }
              return self;
            }
            if (name in attrs.map) {
              let i = attrs.length;
              while (i--) {
                if (attrs[i].name === name) {
                  attrs[i].value = value;
                  break;
                }
              }
            } else {
              attrs.push({
                name,
                value
              });
            }
            attrs.map[name] = value;
            return self;
          }
          return attrs.map[name];
        }
        return undefined;
      }
      clone() {
        const self = this;
        const clone = new AstNode(self.name, self.type);
        const selfAttrs = self.attributes;
        if (selfAttrs) {
          const cloneAttrs = [];
          cloneAttrs.map = {};
          for (let i = 0, l = selfAttrs.length; i < l; i++) {
            const selfAttr = selfAttrs[i];
            if (selfAttr.name !== 'id') {
              cloneAttrs[cloneAttrs.length] = {
                name: selfAttr.name,
                value: selfAttr.value
              };
              cloneAttrs.map[selfAttr.name] = selfAttr.value;
            }
          }
          clone.attributes = cloneAttrs;
        }
        clone.value = self.value;
        return clone;
      }
      wrap(wrapper) {
        const self = this;
        if (self.parent) {
          self.parent.insert(wrapper, self);
          wrapper.append(self);
        }
        return self;
      }
      unwrap() {
        const self = this;
        for (let node = self.firstChild; node;) {
          const next = node.next;
          self.insert(node, self, true);
          node = next;
        }
        self.remove();
      }
      remove() {
        const self = this, parent = self.parent, next = self.next, prev = self.prev;
        if (parent) {
          if (parent.firstChild === self) {
            parent.firstChild = next;
            if (next) {
              next.prev = null;
            }
          } else if (prev) {
            prev.next = next;
          }
          if (parent.lastChild === self) {
            parent.lastChild = prev;
            if (prev) {
              prev.next = null;
            }
          } else if (next) {
            next.prev = prev;
          }
          self.parent = self.next = self.prev = null;
        }
        return self;
      }
      append(node) {
        const self = this;
        if (node.parent) {
          node.remove();
        }
        const last = self.lastChild;
        if (last) {
          last.next = node;
          node.prev = last;
          self.lastChild = node;
        } else {
          self.lastChild = self.firstChild = node;
        }
        node.parent = self;
        return node;
      }
      insert(node, refNode, before) {
        if (node.parent) {
          node.remove();
        }
        const parent = refNode.parent || this;
        if (before) {
          if (refNode === parent.firstChild) {
            parent.firstChild = node;
          } else if (refNode.prev) {
            refNode.prev.next = node;
          }
          node.prev = refNode.prev;
          node.next = refNode;
          refNode.prev = node;
        } else {
          if (refNode === parent.lastChild) {
            parent.lastChild = node;
          } else if (refNode.next) {
            refNode.next.prev = node;
          }
          node.next = refNode.next;
          node.prev = refNode;
          refNode.next = node;
        }
        node.parent = parent;
        return node;
      }
      getAll(name) {
        const self = this;
        const collection = [];
        for (let node = self.firstChild; node; node = walk$2(node, self)) {
          if (node.name === name) {
            collection.push(node);
          }
        }
        return collection;
      }
      children() {
        const self = this;
        const collection = [];
        for (let node = self.firstChild; node; node = node.next) {
          collection.push(node);
        }
        return collection;
      }
      empty() {
        const self = this;
        if (self.firstChild) {
          const nodes = [];
          for (let node = self.firstChild; node; node = walk$2(node, self)) {
            nodes.push(node);
          }
          let i = nodes.length;
          while (i--) {
            const node = nodes[i];
            node.parent = node.firstChild = node.lastChild = node.next = node.prev = null;
          }
        }
        self.firstChild = self.lastChild = null;
        return self;
      }
      isEmpty(elements, whitespace = {}, predicate) {
        var _a;
        const self = this;
        let node = self.firstChild;
        if (isNonEmptyElement(self)) {
          return false;
        }
        if (node) {
          do {
            if (node.type === 1) {
              if (node.attr('data-mce-bogus')) {
                continue;
              }
              if (elements[node.name]) {
                return false;
              }
              if (isNonEmptyElement(node)) {
                return false;
              }
            }
            if (node.type === 8) {
              return false;
            }
            if (node.type === 3 && !isEmptyTextNode(node)) {
              return false;
            }
            if (node.type === 3 && node.parent && whitespace[node.parent.name] && isWhitespaceText((_a = node.value) !== null && _a !== void 0 ? _a : '')) {
              return false;
            }
            if (predicate && predicate(node)) {
              return false;
            }
          } while (node = walk$2(node, self));
        }
        return true;
      }
      walk(prev) {
        return walk$2(this, null, prev);
      }
    }

    const unescapedTextParents = Tools.makeMap('NOSCRIPT STYLE SCRIPT XMP IFRAME NOEMBED NOFRAMES PLAINTEXT', ' ');
    const containsZwsp = node => isString(node.nodeValue) && node.nodeValue.includes(ZWSP$1);
    const getTemporaryNodeSelector = tempAttrs => `${ tempAttrs.length === 0 ? '' : `${ map$3(tempAttrs, attr => `[${ attr }]`).join(',') },` }[data-mce-bogus="all"]`;
    const getTemporaryNodes = (tempAttrs, body) => body.querySelectorAll(getTemporaryNodeSelector(tempAttrs));
    const createZwspCommentWalker = body => document.createTreeWalker(body, NodeFilter.SHOW_COMMENT, node => containsZwsp(node) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP);
    const createUnescapedZwspTextWalker = body => document.createTreeWalker(body, NodeFilter.SHOW_TEXT, node => {
      if (containsZwsp(node)) {
        const parent = node.parentNode;
        return parent && has$2(unescapedTextParents, parent.nodeName) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
      } else {
        return NodeFilter.FILTER_SKIP;
      }
    });
    const hasZwspComment = body => createZwspCommentWalker(body).nextNode() !== null;
    const hasUnescapedZwspText = body => createUnescapedZwspTextWalker(body).nextNode() !== null;
    const hasTemporaryNode = (tempAttrs, body) => body.querySelector(getTemporaryNodeSelector(tempAttrs)) !== null;
    const trimTemporaryNodes = (tempAttrs, body) => {
      each$e(getTemporaryNodes(tempAttrs, body), elm => {
        const element = SugarElement.fromDom(elm);
        if (get$9(element, 'data-mce-bogus') === 'all') {
          remove$5(element);
        } else {
          each$e(tempAttrs, attr => {
            if (has$1(element, attr)) {
              remove$a(element, attr);
            }
          });
        }
      });
    };
    const emptyAllNodeValuesInWalker = walker => {
      let curr = walker.nextNode();
      while (curr !== null) {
        curr.nodeValue = null;
        curr = walker.nextNode();
      }
    };
    const emptyZwspComments = compose(emptyAllNodeValuesInWalker, createZwspCommentWalker);
    const emptyUnescapedZwspTexts = compose(emptyAllNodeValuesInWalker, createUnescapedZwspTextWalker);
    const trim$1 = (body, tempAttrs) => {
      const conditionalTrims = [
        {
          condition: curry(hasTemporaryNode, tempAttrs),
          action: curry(trimTemporaryNodes, tempAttrs)
        },
        {
          condition: hasZwspComment,
          action: emptyZwspComments
        },
        {
          condition: hasUnescapedZwspText,
          action: emptyUnescapedZwspTexts
        }
      ];
      let trimmed = body;
      let cloned = false;
      each$e(conditionalTrims, ({condition, action}) => {
        if (condition(trimmed)) {
          if (!cloned) {
            trimmed = body.cloneNode(true);
            cloned = true;
          }
          action(trimmed);
        }
      });
      return trimmed;
    };

    const cleanupBogusElements = parent => {
      const bogusElements = descendants(parent, '[data-mce-bogus]');
      each$e(bogusElements, elem => {
        const bogusValue = get$9(elem, 'data-mce-bogus');
        if (bogusValue === 'all') {
          remove$5(elem);
        } else if (isBr$5(elem)) {
          before$3(elem, SugarElement.fromText(zeroWidth));
          remove$5(elem);
        } else {
          unwrap(elem);
        }
      });
    };
    const cleanupInputNames = parent => {
      const inputs = descendants(parent, 'input');
      each$e(inputs, input => {
        remove$a(input, 'name');
      });
    };

    const trimEmptyContents = (editor, html) => {
      const blockName = getForcedRootBlock(editor);
      const emptyRegExp = new RegExp(`^(<${ blockName }[^>]*>(&nbsp;|&#160;|\\s|\u00a0|<br \\/>|)<\\/${ blockName }>[\r\n]*|<br \\/>[\r\n]*)$`);
      return html.replace(emptyRegExp, '');
    };
    const getPlainTextContent = (editor, body) => {
      const doc = editor.getDoc();
      const dos = getRootNode(SugarElement.fromDom(editor.getBody()));
      const offscreenDiv = SugarElement.fromTag('div', doc);
      set$3(offscreenDiv, 'data-mce-bogus', 'all');
      setAll(offscreenDiv, {
        position: 'fixed',
        left: '-9999999px',
        top: '0'
      });
      set$1(offscreenDiv, body.innerHTML);
      cleanupBogusElements(offscreenDiv);
      cleanupInputNames(offscreenDiv);
      const root = getContentContainer(dos);
      append$1(root, offscreenDiv);
      const content = trim$2(offscreenDiv.dom.innerText);
      remove$5(offscreenDiv);
      return content;
    };
    const getContentFromBody = (editor, args, body) => {
      let content;
      if (args.format === 'raw') {
        content = Tools.trim(trim$2(trim$1(body, editor.serializer.getTempAttrs()).innerHTML));
      } else if (args.format === 'text') {
        content = getPlainTextContent(editor, body);
      } else if (args.format === 'tree') {
        content = editor.serializer.serialize(body, args);
      } else {
        content = trimEmptyContents(editor, editor.serializer.serialize(body, args));
      }
      const shouldTrim = args.format !== 'text' && !isWsPreserveElement(SugarElement.fromDom(body));
      return shouldTrim && isString(content) ? Tools.trim(content) : content;
    };
    const getContentInternal = (editor, args) => Optional.from(editor.getBody()).fold(constant(args.format === 'tree' ? new AstNode('body', 11) : ''), body => getContentFromBody(editor, args, body));

    const makeMap$1 = Tools.makeMap;
    const Writer = settings => {
      const html = [];
      settings = settings || {};
      const indent = settings.indent;
      const indentBefore = makeMap$1(settings.indent_before || '');
      const indentAfter = makeMap$1(settings.indent_after || '');
      const encode = Entities.getEncodeFunc(settings.entity_encoding || 'raw', settings.entities);
      const htmlOutput = settings.element_format !== 'xhtml';
      return {
        start: (name, attrs, empty) => {
          if (indent && indentBefore[name] && html.length > 0) {
            const value = html[html.length - 1];
            if (value.length > 0 && value !== '\n') {
              html.push('\n');
            }
          }
          html.push('<', name);
          if (attrs) {
            for (let i = 0, l = attrs.length; i < l; i++) {
              const attr = attrs[i];
              html.push(' ', attr.name, '="', encode(attr.value, true), '"');
            }
          }
          if (!empty || htmlOutput) {
            html[html.length] = '>';
          } else {
            html[html.length] = ' />';
          }
          if (empty && indent && indentAfter[name] && html.length > 0) {
            const value = html[html.length - 1];
            if (value.length > 0 && value !== '\n') {
              html.push('\n');
            }
          }
        },
        end: name => {
          let value;
          html.push('</', name, '>');
          if (indent && indentAfter[name] && html.length > 0) {
            value = html[html.length - 1];
            if (value.length > 0 && value !== '\n') {
              html.push('\n');
            }
          }
        },
        text: (text, raw) => {
          if (text.length > 0) {
            html[html.length] = raw ? text : encode(text);
          }
        },
        cdata: text => {
          html.push('<![CDATA[', text, ']]>');
        },
        comment: text => {
          html.push('<!--', text, '-->');
        },
        pi: (name, text) => {
          if (text) {
            html.push('<?', name, ' ', encode(text), '?>');
          } else {
            html.push('<?', name, '?>');
          }
          if (indent) {
            html.push('\n');
          }
        },
        doctype: text => {
          html.push('<!DOCTYPE', text, '>', indent ? '\n' : '');
        },
        reset: () => {
          html.length = 0;
        },
        getContent: () => {
          return html.join('').replace(/\n$/, '');
        }
      };
    };

    const HtmlSerializer = (settings = {}, schema = Schema()) => {
      const writer = Writer(settings);
      settings.validate = 'validate' in settings ? settings.validate : true;
      const serialize = node => {
        const validate = settings.validate;
        const handlers = {
          3: node => {
            var _a;
            writer.text((_a = node.value) !== null && _a !== void 0 ? _a : '', node.raw);
          },
          8: node => {
            var _a;
            writer.comment((_a = node.value) !== null && _a !== void 0 ? _a : '');
          },
          7: node => {
            writer.pi(node.name, node.value);
          },
          10: node => {
            var _a;
            writer.doctype((_a = node.value) !== null && _a !== void 0 ? _a : '');
          },
          4: node => {
            var _a;
            writer.cdata((_a = node.value) !== null && _a !== void 0 ? _a : '');
          },
          11: node => {
            let tempNode = node;
            if (tempNode = tempNode.firstChild) {
              do {
                walk(tempNode);
              } while (tempNode = tempNode.next);
            }
          }
        };
        writer.reset();
        const walk = node => {
          var _a;
          const handler = handlers[node.type];
          if (!handler) {
            const name = node.name;
            const isEmpty = name in schema.getVoidElements();
            let attrs = node.attributes;
            if (validate && attrs && attrs.length > 1) {
              const sortedAttrs = [];
              sortedAttrs.map = {};
              const elementRule = schema.getElementRule(node.name);
              if (elementRule) {
                for (let i = 0, l = elementRule.attributesOrder.length; i < l; i++) {
                  const attrName = elementRule.attributesOrder[i];
                  if (attrName in attrs.map) {
                    const attrValue = attrs.map[attrName];
                    sortedAttrs.map[attrName] = attrValue;
                    sortedAttrs.push({
                      name: attrName,
                      value: attrValue
                    });
                  }
                }
                for (let i = 0, l = attrs.length; i < l; i++) {
                  const attrName = attrs[i].name;
                  if (!(attrName in sortedAttrs.map)) {
                    const attrValue = attrs.map[attrName];
                    sortedAttrs.map[attrName] = attrValue;
                    sortedAttrs.push({
                      name: attrName,
                      value: attrValue
                    });
                  }
                }
                attrs = sortedAttrs;
              }
            }
            writer.start(name, attrs, isEmpty);
            if (isNonHtmlElementRootName(name)) {
              if (isString(node.value)) {
                writer.text(node.value, true);
              }
              writer.end(name);
            } else {
              if (!isEmpty) {
                let child = node.firstChild;
                if (child) {
                  if ((name === 'pre' || name === 'textarea') && child.type === 3 && ((_a = child.value) === null || _a === void 0 ? void 0 : _a[0]) === '\n') {
                    writer.text('\n', true);
                  }
                  do {
                    walk(child);
                  } while (child = child.next);
                }
                writer.end(name);
              }
            }
          } else {
            handler(node);
          }
        };
        if (node.type === 1 && !settings.inner) {
          walk(node);
        } else if (node.type === 3) {
          handlers[3](node);
        } else {
          handlers[11](node);
        }
        return writer.getContent();
      };
      return { serialize };
    };

    const nonInheritableStyles = new Set();
    (() => {
      const nonInheritableStylesArr = [
        'margin',
        'margin-left',
        'margin-right',
        'margin-top',
        'margin-bottom',
        'padding',
        'padding-left',
        'padding-right',
        'padding-top',
        'padding-bottom',
        'border',
        'border-width',
        'border-style',
        'border-color',
        'background',
        'background-attachment',
        'background-clip',
        'background-color',
        'background-image',
        'background-origin',
        'background-position',
        'background-repeat',
        'background-size',
        'float',
        'position',
        'left',
        'right',
        'top',
        'bottom',
        'z-index',
        'display',
        'transform',
        'width',
        'max-width',
        'min-width',
        'height',
        'max-height',
        'min-height',
        'overflow',
        'overflow-x',
        'overflow-y',
        'text-overflow',
        'vertical-align',
        'transition',
        'transition-delay',
        'transition-duration',
        'transition-property',
        'transition-timing-function'
      ];
      each$e(nonInheritableStylesArr, style => {
        nonInheritableStyles.add(style);
      });
    })();
    const shorthandStyleProps = [
      'font',
      'text-decoration',
      'text-emphasis'
    ];
    const getStyleProps = (dom, node) => keys(dom.parseStyle(dom.getAttrib(node, 'style')));
    const isNonInheritableStyle = style => nonInheritableStyles.has(style);
    const hasInheritableStyles = (dom, node) => forall(getStyleProps(dom, node), style => !isNonInheritableStyle(style));
    const getLonghandStyleProps = styles => filter$5(styles, style => exists(shorthandStyleProps, prop => startsWith(style, prop)));
    const hasStyleConflict = (dom, node, parentNode) => {
      const nodeStyleProps = getStyleProps(dom, node);
      const parentNodeStyleProps = getStyleProps(dom, parentNode);
      const valueMismatch = prop => {
        var _a, _b;
        const nodeValue = (_a = dom.getStyle(node, prop)) !== null && _a !== void 0 ? _a : '';
        const parentValue = (_b = dom.getStyle(parentNode, prop)) !== null && _b !== void 0 ? _b : '';
        return isNotEmpty(nodeValue) && isNotEmpty(parentValue) && nodeValue !== parentValue;
      };
      return exists(nodeStyleProps, nodeStyleProp => {
        const propExists = props => exists(props, prop => prop === nodeStyleProp);
        if (!propExists(parentNodeStyleProps) && propExists(shorthandStyleProps)) {
          const longhandProps = getLonghandStyleProps(parentNodeStyleProps);
          return exists(longhandProps, valueMismatch);
        } else {
          return valueMismatch(nodeStyleProp);
        }
      });
    };

    const isChar = (forward, predicate, pos) => Optional.from(pos.container()).filter(isText$a).exists(text => {
      const delta = forward ? 0 : -1;
      return predicate(text.data.charAt(pos.offset() + delta));
    });
    const isBeforeSpace = curry(isChar, true, isWhiteSpace);
    const isAfterSpace = curry(isChar, false, isWhiteSpace);
    const isEmptyText = pos => {
      const container = pos.container();
      return isText$a(container) && (container.data.length === 0 || isZwsp$1(container.data) && BookmarkManager.isBookmarkNode(container.parentNode));
    };
    const matchesElementPosition = (before, predicate) => pos => getChildNodeAtRelativeOffset(before ? 0 : -1, pos).filter(predicate).isSome();
    const isImageBlock = node => isImg(node) && get$7(SugarElement.fromDom(node), 'display') === 'block';
    const isCefNode = node => isContentEditableFalse$b(node) && !isBogusAll$1(node);
    const isBeforeImageBlock = matchesElementPosition(true, isImageBlock);
    const isAfterImageBlock = matchesElementPosition(false, isImageBlock);
    const isBeforeMedia = matchesElementPosition(true, isMedia$2);
    const isAfterMedia = matchesElementPosition(false, isMedia$2);
    const isBeforeTable = matchesElementPosition(true, isTable$2);
    const isAfterTable = matchesElementPosition(false, isTable$2);
    const isBeforeContentEditableFalse = matchesElementPosition(true, isCefNode);
    const isAfterContentEditableFalse = matchesElementPosition(false, isCefNode);

    const dropLast = xs => xs.slice(0, -1);
    const parentsUntil = (start, root, predicate) => {
      if (contains(root, start)) {
        return dropLast(parents$1(start, elm => {
          return predicate(elm) || eq(elm, root);
        }));
      } else {
        return [];
      }
    };
    const parents = (start, root) => parentsUntil(start, root, never);
    const parentsAndSelf = (start, root) => [start].concat(parents(start, root));

    const navigateIgnoreEmptyTextNodes = (forward, root, from) => navigateIgnore(forward, root, from, isEmptyText);
    const isBlock$1 = schema => el => schema.isBlock(name(el));
    const getClosestBlock$1 = (root, pos, schema) => find$2(parentsAndSelf(SugarElement.fromDom(pos.container()), root), isBlock$1(schema));
    const isAtBeforeAfterBlockBoundary = (forward, root, pos, schema) => navigateIgnoreEmptyTextNodes(forward, root.dom, pos).forall(newPos => getClosestBlock$1(root, pos, schema).fold(() => !isInSameBlock(newPos, pos, root.dom), fromBlock => !isInSameBlock(newPos, pos, root.dom) && contains(fromBlock, SugarElement.fromDom(newPos.container()))));
    const isAtBlockBoundary = (forward, root, pos, schema) => getClosestBlock$1(root, pos, schema).fold(() => navigateIgnoreEmptyTextNodes(forward, root.dom, pos).forall(newPos => !isInSameBlock(newPos, pos, root.dom)), parent => navigateIgnoreEmptyTextNodes(forward, parent.dom, pos).isNone());
    const isAtStartOfBlock = curry(isAtBlockBoundary, false);
    const isAtEndOfBlock = curry(isAtBlockBoundary, true);
    const isBeforeBlock = curry(isAtBeforeAfterBlockBoundary, false);
    const isAfterBlock = curry(isAtBeforeAfterBlockBoundary, true);

    const isBr$1 = pos => getElementFromPosition(pos).exists(isBr$5);
    const findBr = (forward, root, pos, schema) => {
      const parentBlocks = filter$5(parentsAndSelf(SugarElement.fromDom(pos.container()), root), el => schema.isBlock(name(el)));
      const scope = head(parentBlocks).getOr(root);
      return fromPosition(forward, scope.dom, pos).filter(isBr$1);
    };
    const isBeforeBr$1 = (root, pos, schema) => getElementFromPosition(pos).exists(isBr$5) || findBr(true, root, pos, schema).isSome();
    const isAfterBr = (root, pos, schema) => getElementFromPrevPosition(pos).exists(isBr$5) || findBr(false, root, pos, schema).isSome();
    const findPreviousBr = curry(findBr, false);
    const findNextBr = curry(findBr, true);

    const isInMiddleOfText = pos => CaretPosition.isTextPosition(pos) && !pos.isAtStart() && !pos.isAtEnd();
    const getClosestBlock = (root, pos, schema) => {
      const parentBlocks = filter$5(parentsAndSelf(SugarElement.fromDom(pos.container()), root), el => schema.isBlock(name(el)));
      return head(parentBlocks).getOr(root);
    };
    const hasSpaceBefore = (root, pos, schema) => {
      if (isInMiddleOfText(pos)) {
        return isAfterSpace(pos);
      } else {
        return isAfterSpace(pos) || prevPosition(getClosestBlock(root, pos, schema).dom, pos).exists(isAfterSpace);
      }
    };
    const hasSpaceAfter = (root, pos, schema) => {
      if (isInMiddleOfText(pos)) {
        return isBeforeSpace(pos);
      } else {
        return isBeforeSpace(pos) || nextPosition(getClosestBlock(root, pos, schema).dom, pos).exists(isBeforeSpace);
      }
    };
    const isPreValue = value => contains$2([
      'pre',
      'pre-wrap'
    ], value);
    const isInPre = pos => getElementFromPosition(pos).bind(elm => closest$4(elm, isElement$7)).exists(elm => isPreValue(get$7(elm, 'white-space')));
    const isAtBeginningOfBody = (root, pos) => prevPosition(root.dom, pos).isNone();
    const isAtEndOfBody = (root, pos) => nextPosition(root.dom, pos).isNone();
    const isAtLineBoundary = (root, pos, schema) => isAtBeginningOfBody(root, pos) || isAtEndOfBody(root, pos) || isAtStartOfBlock(root, pos, schema) || isAtEndOfBlock(root, pos, schema) || isAfterBr(root, pos, schema) || isBeforeBr$1(root, pos, schema);
    const isCefBlock = node => isNonNullable(node) && isContentEditableFalse$b(node) && isBlockLike(node);
    const isSiblingCefBlock = (root, direction) => container => {
      return isCefBlock(new DomTreeWalker(container, root)[direction]());
    };
    const isBeforeCefBlock = (root, pos) => {
      const nextPos = nextPosition(root.dom, pos).getOr(pos);
      const isNextCefBlock = isSiblingCefBlock(root.dom, 'next');
      return pos.isAtEnd() && (isNextCefBlock(pos.container()) || isNextCefBlock(nextPos.container()));
    };
    const isAfterCefBlock = (root, pos) => {
      const prevPos = prevPosition(root.dom, pos).getOr(pos);
      const isPrevCefBlock = isSiblingCefBlock(root.dom, 'prev');
      return pos.isAtStart() && (isPrevCefBlock(pos.container()) || isPrevCefBlock(prevPos.container()));
    };
    const needsToHaveNbsp = (root, pos, schema) => {
      if (isInPre(pos)) {
        return false;
      } else {
        return isAtLineBoundary(root, pos, schema) || hasSpaceBefore(root, pos, schema) || hasSpaceAfter(root, pos, schema);
      }
    };
    const needsToBeNbspLeft = (root, pos, schema) => {
      if (isInPre(pos)) {
        return false;
      } else {
        return isAtStartOfBlock(root, pos, schema) || isBeforeBlock(root, pos, schema) || isAfterBr(root, pos, schema) || hasSpaceBefore(root, pos, schema) || isAfterCefBlock(root, pos);
      }
    };
    const leanRight = pos => {
      const container = pos.container();
      const offset = pos.offset();
      if (isText$a(container) && offset < container.data.length) {
        return CaretPosition(container, offset + 1);
      } else {
        return pos;
      }
    };
    const needsToBeNbspRight = (root, pos, schema) => {
      if (isInPre(pos)) {
        return false;
      } else {
        return isAtEndOfBlock(root, pos, schema) || isAfterBlock(root, pos, schema) || isBeforeBr$1(root, pos, schema) || hasSpaceAfter(root, pos, schema) || isBeforeCefBlock(root, pos);
      }
    };
    const needsToBeNbsp = (root, pos, schema) => needsToBeNbspLeft(root, pos, schema) || needsToBeNbspRight(root, leanRight(pos), schema);
    const isNbspAt = (text, offset) => isNbsp(text.charAt(offset));
    const isWhiteSpaceAt = (text, offset) => isWhiteSpace(text.charAt(offset));
    const hasNbsp = pos => {
      const container = pos.container();
      return isText$a(container) && contains$1(container.data, nbsp);
    };
    const normalizeNbspMiddle = text => {
      const chars = text.split('');
      return map$3(chars, (chr, i) => {
        if (isNbsp(chr) && i > 0 && i < chars.length - 1 && isContent(chars[i - 1]) && isContent(chars[i + 1])) {
          return ' ';
        } else {
          return chr;
        }
      }).join('');
    };
    const normalizeNbspAtStart = (root, node, makeNbsp, schema) => {
      const text = node.data;
      const firstPos = CaretPosition(node, 0);
      if (!makeNbsp && isNbspAt(text, 0) && !needsToBeNbsp(root, firstPos, schema)) {
        node.data = ' ' + text.slice(1);
        return true;
      } else if (makeNbsp && isWhiteSpaceAt(text, 0) && needsToBeNbspLeft(root, firstPos, schema)) {
        node.data = nbsp + text.slice(1);
        return true;
      } else {
        return false;
      }
    };
    const normalizeNbspInMiddleOfTextNode = node => {
      const text = node.data;
      const newText = normalizeNbspMiddle(text);
      if (newText !== text) {
        node.data = newText;
        return true;
      } else {
        return false;
      }
    };
    const normalizeNbspAtEnd = (root, node, makeNbsp, schema) => {
      const text = node.data;
      const lastPos = CaretPosition(node, text.length - 1);
      if (!makeNbsp && isNbspAt(text, text.length - 1) && !needsToBeNbsp(root, lastPos, schema)) {
        node.data = text.slice(0, -1) + ' ';
        return true;
      } else if (makeNbsp && isWhiteSpaceAt(text, text.length - 1) && needsToBeNbspRight(root, lastPos, schema)) {
        node.data = text.slice(0, -1) + nbsp;
        return true;
      } else {
        return false;
      }
    };
    const normalizeNbsps = (root, pos, schema) => {
      const container = pos.container();
      if (!isText$a(container)) {
        return Optional.none();
      }
      if (hasNbsp(pos)) {
        const normalized = normalizeNbspAtStart(root, container, false, schema) || normalizeNbspInMiddleOfTextNode(container) || normalizeNbspAtEnd(root, container, false, schema);
        return someIf(normalized, pos);
      } else if (needsToBeNbsp(root, pos, schema)) {
        const normalized = normalizeNbspAtStart(root, container, true, schema) || normalizeNbspAtEnd(root, container, true, schema);
        return someIf(normalized, pos);
      } else {
        return Optional.none();
      }
    };
    const normalizeNbspsInEditor = editor => {
      const root = SugarElement.fromDom(editor.getBody());
      if (editor.selection.isCollapsed()) {
        normalizeNbsps(root, CaretPosition.fromRangeStart(editor.selection.getRng()), editor.schema).each(pos => {
          editor.selection.setRng(pos.toRange());
        });
      }
    };

    const normalize$1 = (node, offset, count, schema) => {
      if (count === 0) {
        return;
      }
      const elm = SugarElement.fromDom(node);
      const root = ancestor$4(elm, el => schema.isBlock(name(el))).getOr(elm);
      const whitespace = node.data.slice(offset, offset + count);
      const isEndOfContent = offset + count >= node.data.length && needsToBeNbspRight(root, CaretPosition(node, node.data.length), schema);
      const isStartOfContent = offset === 0 && needsToBeNbspLeft(root, CaretPosition(node, 0), schema);
      node.replaceData(offset, count, normalize$4(whitespace, 4, isStartOfContent, isEndOfContent));
    };
    const normalizeWhitespaceAfter = (node, offset, schema) => {
      const content = node.data.slice(offset);
      const whitespaceCount = content.length - lTrim(content).length;
      normalize$1(node, offset, whitespaceCount, schema);
    };
    const normalizeWhitespaceBefore = (node, offset, schema) => {
      const content = node.data.slice(0, offset);
      const whitespaceCount = content.length - rTrim(content).length;
      normalize$1(node, offset - whitespaceCount, whitespaceCount, schema);
    };
    const mergeTextNodes = (prevNode, nextNode, schema, normalizeWhitespace, mergeToPrev = true) => {
      const whitespaceOffset = rTrim(prevNode.data).length;
      const newNode = mergeToPrev ? prevNode : nextNode;
      const removeNode = mergeToPrev ? nextNode : prevNode;
      if (mergeToPrev) {
        newNode.appendData(removeNode.data);
      } else {
        newNode.insertData(0, removeNode.data);
      }
      remove$5(SugarElement.fromDom(removeNode));
      if (normalizeWhitespace) {
        normalizeWhitespaceAfter(newNode, whitespaceOffset, schema);
      }
      return newNode;
    };

    const needsReposition = (pos, elm) => {
      const container = pos.container();
      const offset = pos.offset();
      return !CaretPosition.isTextPosition(pos) && container === elm.parentNode && offset > CaretPosition.before(elm).offset();
    };
    const reposition = (elm, pos) => needsReposition(pos, elm) ? CaretPosition(pos.container(), pos.offset() - 1) : pos;
    const beforeOrStartOf = node => isText$a(node) ? CaretPosition(node, 0) : CaretPosition.before(node);
    const afterOrEndOf = node => isText$a(node) ? CaretPosition(node, node.data.length) : CaretPosition.after(node);
    const getPreviousSiblingCaretPosition = elm => {
      if (isCaretCandidate$3(elm.previousSibling)) {
        return Optional.some(afterOrEndOf(elm.previousSibling));
      } else {
        return elm.previousSibling ? lastPositionIn(elm.previousSibling) : Optional.none();
      }
    };
    const getNextSiblingCaretPosition = elm => {
      if (isCaretCandidate$3(elm.nextSibling)) {
        return Optional.some(beforeOrStartOf(elm.nextSibling));
      } else {
        return elm.nextSibling ? firstPositionIn(elm.nextSibling) : Optional.none();
      }
    };
    const findCaretPositionBackwardsFromElm = (rootElement, elm) => {
      return Optional.from(elm.previousSibling ? elm.previousSibling : elm.parentNode).bind(node => prevPosition(rootElement, CaretPosition.before(node))).orThunk(() => nextPosition(rootElement, CaretPosition.after(elm)));
    };
    const findCaretPositionForwardsFromElm = (rootElement, elm) => nextPosition(rootElement, CaretPosition.after(elm)).orThunk(() => prevPosition(rootElement, CaretPosition.before(elm)));
    const findCaretPositionBackwards = (rootElement, elm) => getPreviousSiblingCaretPosition(elm).orThunk(() => getNextSiblingCaretPosition(elm)).orThunk(() => findCaretPositionBackwardsFromElm(rootElement, elm));
    const findCaretPositionForward = (rootElement, elm) => getNextSiblingCaretPosition(elm).orThunk(() => getPreviousSiblingCaretPosition(elm)).orThunk(() => findCaretPositionForwardsFromElm(rootElement, elm));
    const findCaretPosition = (forward, rootElement, elm) => forward ? findCaretPositionForward(rootElement, elm) : findCaretPositionBackwards(rootElement, elm);
    const findCaretPosOutsideElmAfterDelete = (forward, rootElement, elm) => findCaretPosition(forward, rootElement, elm).map(curry(reposition, elm));
    const setSelection$1 = (editor, forward, pos) => {
      pos.fold(() => {
        editor.focus();
      }, pos => {
        editor.selection.setRng(pos.toRange(), forward);
      });
    };
    const eqRawNode = rawNode => elm => elm.dom === rawNode;
    const isBlock = (editor, elm) => elm && has$2(editor.schema.getBlockElements(), name(elm));
    const paddEmptyBlock = (elm, preserveEmptyCaret) => {
      if (isEmpty$2(elm)) {
        const br = SugarElement.fromHtml('<br data-mce-bogus="1">');
        if (preserveEmptyCaret) {
          each$e(children$1(elm), node => {
            if (!isEmptyCaretFormatElement(node)) {
              remove$5(node);
            }
          });
        } else {
          empty(elm);
        }
        append$1(elm, br);
        return Optional.some(CaretPosition.before(br.dom));
      } else {
        return Optional.none();
      }
    };
    const deleteNormalized = (elm, afterDeletePosOpt, schema, normalizeWhitespace) => {
      const prevTextOpt = prevSibling(elm).filter(isText$b);
      const nextTextOpt = nextSibling(elm).filter(isText$b);
      remove$5(elm);
      return lift3(prevTextOpt, nextTextOpt, afterDeletePosOpt, (prev, next, pos) => {
        const prevNode = prev.dom, nextNode = next.dom;
        const offset = prevNode.data.length;
        mergeTextNodes(prevNode, nextNode, schema, normalizeWhitespace);
        return pos.container() === nextNode ? CaretPosition(prevNode, offset) : pos;
      }).orThunk(() => {
        if (normalizeWhitespace) {
          prevTextOpt.each(elm => normalizeWhitespaceBefore(elm.dom, elm.dom.length, schema));
          nextTextOpt.each(elm => normalizeWhitespaceAfter(elm.dom, 0, schema));
        }
        return afterDeletePosOpt;
      });
    };
    const isInlineElement = (editor, element) => has$2(editor.schema.getTextInlineElements(), name(element));
    const deleteElement$2 = (editor, forward, elm, moveCaret = true, preserveEmptyCaret = false) => {
      const afterDeletePos = findCaretPosOutsideElmAfterDelete(forward, editor.getBody(), elm.dom);
      const parentBlock = ancestor$4(elm, curry(isBlock, editor), eqRawNode(editor.getBody()));
      const normalizedAfterDeletePos = deleteNormalized(elm, afterDeletePos, editor.schema, isInlineElement(editor, elm));
      if (editor.dom.isEmpty(editor.getBody())) {
        editor.setContent('');
        editor.selection.setCursorLocation();
      } else {
        parentBlock.bind(elm => paddEmptyBlock(elm, preserveEmptyCaret)).fold(() => {
          if (moveCaret) {
            setSelection$1(editor, forward, normalizedAfterDeletePos);
          }
        }, paddPos => {
          if (moveCaret) {
            setSelection$1(editor, forward, Optional.some(paddPos));
          }
        });
      }
    };

    const strongRtl = /[\u0591-\u07FF\uFB1D-\uFDFF\uFE70-\uFEFC]/;
    const hasStrongRtl = text => strongRtl.test(text);

    const isInlineTarget = (editor, elm) => is$1(SugarElement.fromDom(elm), getInlineBoundarySelector(editor)) && !isTransparentBlock(editor.schema, elm) && editor.dom.isEditable(elm);
    const isRtl = element => {
      var _a;
      return DOMUtils.DOM.getStyle(element, 'direction', true) === 'rtl' || hasStrongRtl((_a = element.textContent) !== null && _a !== void 0 ? _a : '');
    };
    const findInlineParents = (isInlineTarget, rootNode, pos) => filter$5(DOMUtils.DOM.getParents(pos.container(), '*', rootNode), isInlineTarget);
    const findRootInline = (isInlineTarget, rootNode, pos) => {
      const parents = findInlineParents(isInlineTarget, rootNode, pos);
      return Optional.from(parents[parents.length - 1]);
    };
    const hasSameParentBlock = (rootNode, node1, node2) => {
      const block1 = getParentBlock$3(node1, rootNode);
      const block2 = getParentBlock$3(node2, rootNode);
      return isNonNullable(block1) && block1 === block2;
    };
    const isAtZwsp = pos => isBeforeInline(pos) || isAfterInline(pos);
    const normalizePosition = (forward, pos) => {
      const container = pos.container(), offset = pos.offset();
      if (forward) {
        if (isCaretContainerInline(container)) {
          if (isText$a(container.nextSibling)) {
            return CaretPosition(container.nextSibling, 0);
          } else {
            return CaretPosition.after(container);
          }
        } else {
          return isBeforeInline(pos) ? CaretPosition(container, offset + 1) : pos;
        }
      } else {
        if (isCaretContainerInline(container)) {
          if (isText$a(container.previousSibling)) {
            return CaretPosition(container.previousSibling, container.previousSibling.data.length);
          } else {
            return CaretPosition.before(container);
          }
        } else {
          return isAfterInline(pos) ? CaretPosition(container, offset - 1) : pos;
        }
      }
    };
    const normalizeForwards = curry(normalizePosition, true);
    const normalizeBackwards = curry(normalizePosition, false);

    const execCommandIgnoreInputEvents = (editor, command) => {
      const inputBlocker = e => e.stopImmediatePropagation();
      editor.on('beforeinput input', inputBlocker, true);
      editor.getDoc().execCommand(command);
      editor.off('beforeinput input', inputBlocker);
    };
    const execEditorDeleteCommand = editor => {
      editor.execCommand('delete');
    };
    const execNativeDeleteCommand = editor => execCommandIgnoreInputEvents(editor, 'Delete');
    const execNativeForwardDeleteCommand = editor => execCommandIgnoreInputEvents(editor, 'ForwardDelete');
    const isBeforeRoot = rootNode => elm => is$2(parent(elm), rootNode, eq);
    const isTextBlockOrListItem = element => isTextBlock$2(element) || isListItem$1(element);
    const getParentBlock$2 = (rootNode, elm) => {
      if (contains(rootNode, elm)) {
        return closest$4(elm, isTextBlockOrListItem, isBeforeRoot(rootNode));
      } else {
        return Optional.none();
      }
    };
    const paddEmptyBody = (editor, moveSelection = true) => {
      if (editor.dom.isEmpty(editor.getBody())) {
        editor.setContent('', { no_selection: !moveSelection });
      }
    };
    const willDeleteLastPositionInElement = (forward, fromPos, elm) => lift2(firstPositionIn(elm), lastPositionIn(elm), (firstPos, lastPos) => {
      const normalizedFirstPos = normalizePosition(true, firstPos);
      const normalizedLastPos = normalizePosition(false, lastPos);
      const normalizedFromPos = normalizePosition(false, fromPos);
      if (forward) {
        return nextPosition(elm, normalizedFromPos).exists(nextPos => nextPos.isEqual(normalizedLastPos) && fromPos.isEqual(normalizedFirstPos));
      } else {
        return prevPosition(elm, normalizedFromPos).exists(prevPos => prevPos.isEqual(normalizedFirstPos) && fromPos.isEqual(normalizedLastPos));
      }
    }).getOr(true);
    const freefallRtl = root => {
      const child = isComment$1(root) ? prevSibling(root) : lastChild(root);
      return child.bind(freefallRtl).orThunk(() => Optional.some(root));
    };
    const deleteRangeContents = (editor, rng, root, moveSelection = true) => {
      var _a;
      rng.deleteContents();
      const lastNode = freefallRtl(root).getOr(root);
      const lastBlock = SugarElement.fromDom((_a = editor.dom.getParent(lastNode.dom, editor.dom.isBlock)) !== null && _a !== void 0 ? _a : root.dom);
      if (lastBlock.dom === editor.getBody()) {
        paddEmptyBody(editor, moveSelection);
      } else if (isEmpty$2(lastBlock)) {
        fillWithPaddingBr(lastBlock);
        if (moveSelection) {
          editor.selection.setCursorLocation(lastBlock.dom, 0);
        }
      }
      if (!eq(root, lastBlock)) {
        const additionalCleanupNodes = is$2(parent(lastBlock), root) ? [] : siblings(lastBlock);
        each$e(additionalCleanupNodes.concat(children$1(root)), node => {
          if (!eq(node, lastBlock) && !contains(node, lastBlock) && isEmpty$2(node)) {
            remove$5(node);
          }
        });
      }
    };

    const ancestor$1 = (scope, predicate, isRoot) => ancestor$4(scope, predicate, isRoot).isSome();
    const sibling = (scope, predicate) => sibling$1(scope, predicate).isSome();
    const descendant = (scope, predicate) => descendant$2(scope, predicate).isSome();

    const isRootFromElement = root => cur => eq(root, cur);
    const getTableCells = table => descendants(table, 'td,th');
    const getTable$1 = (node, isRoot) => getClosestTable(SugarElement.fromDom(node), isRoot);
    const selectionInTableWithNestedTable = details => {
      return lift2(details.startTable, details.endTable, (startTable, endTable) => {
        const isStartTableParentOfEndTable = descendant(startTable, t => eq(t, endTable));
        const isEndTableParentOfStartTable = descendant(endTable, t => eq(t, startTable));
        return !isStartTableParentOfEndTable && !isEndTableParentOfStartTable ? details : {
          ...details,
          startTable: isStartTableParentOfEndTable ? Optional.none() : details.startTable,
          endTable: isEndTableParentOfStartTable ? Optional.none() : details.endTable,
          isSameTable: false,
          isMultiTable: false
        };
      }).getOr(details);
    };
    const adjustQuirksInDetails = details => {
      return selectionInTableWithNestedTable(details);
    };
    const getTableDetailsFromRange = (rng, isRoot) => {
      const startTable = getTable$1(rng.startContainer, isRoot);
      const endTable = getTable$1(rng.endContainer, isRoot);
      const isStartInTable = startTable.isSome();
      const isEndInTable = endTable.isSome();
      const isSameTable = lift2(startTable, endTable, eq).getOr(false);
      const isMultiTable = !isSameTable && isStartInTable && isEndInTable;
      return adjustQuirksInDetails({
        startTable,
        endTable,
        isStartInTable,
        isEndInTable,
        isSameTable,
        isMultiTable
      });
    };

    const tableCellRng = (start, end) => ({
      start,
      end
    });
    const tableSelection = (rng, table, cells) => ({
      rng,
      table,
      cells
    });
    const deleteAction = Adt.generate([
      {
        singleCellTable: [
          'rng',
          'cell'
        ]
      },
      { fullTable: ['table'] },
      {
        partialTable: [
          'cells',
          'outsideDetails'
        ]
      },
      {
        multiTable: [
          'startTableCells',
          'endTableCells',
          'betweenRng'
        ]
      }
    ]);
    const getClosestCell$1 = (container, isRoot) => closest$3(SugarElement.fromDom(container), 'td,th', isRoot);
    const isExpandedCellRng = cellRng => !eq(cellRng.start, cellRng.end);
    const getTableFromCellRng = (cellRng, isRoot) => getClosestTable(cellRng.start, isRoot).bind(startParentTable => getClosestTable(cellRng.end, isRoot).bind(endParentTable => someIf(eq(startParentTable, endParentTable), startParentTable)));
    const isSingleCellTable = (cellRng, isRoot) => !isExpandedCellRng(cellRng) && getTableFromCellRng(cellRng, isRoot).exists(table => {
      const rows = table.dom.rows;
      return rows.length === 1 && rows[0].cells.length === 1;
    });
    const getCellRng = (rng, isRoot) => {
      const startCell = getClosestCell$1(rng.startContainer, isRoot);
      const endCell = getClosestCell$1(rng.endContainer, isRoot);
      return lift2(startCell, endCell, tableCellRng);
    };
    const getCellRangeFromStartTable = isRoot => startCell => getClosestTable(startCell, isRoot).bind(table => last$3(getTableCells(table)).map(endCell => tableCellRng(startCell, endCell)));
    const getCellRangeFromEndTable = isRoot => endCell => getClosestTable(endCell, isRoot).bind(table => head(getTableCells(table)).map(startCell => tableCellRng(startCell, endCell)));
    const getTableSelectionFromCellRng = isRoot => cellRng => getTableFromCellRng(cellRng, isRoot).map(table => tableSelection(cellRng, table, getTableCells(table)));
    const getTableSelections = (cellRng, selectionDetails, rng, isRoot) => {
      if (rng.collapsed || !cellRng.forall(isExpandedCellRng)) {
        return Optional.none();
      } else if (selectionDetails.isSameTable) {
        const sameTableSelection = cellRng.bind(getTableSelectionFromCellRng(isRoot));
        return Optional.some({
          start: sameTableSelection,
          end: sameTableSelection
        });
      } else {
        const startCell = getClosestCell$1(rng.startContainer, isRoot);
        const endCell = getClosestCell$1(rng.endContainer, isRoot);
        const startTableSelection = startCell.bind(getCellRangeFromStartTable(isRoot)).bind(getTableSelectionFromCellRng(isRoot));
        const endTableSelection = endCell.bind(getCellRangeFromEndTable(isRoot)).bind(getTableSelectionFromCellRng(isRoot));
        return Optional.some({
          start: startTableSelection,
          end: endTableSelection
        });
      }
    };
    const getCellIndex = (cells, cell) => findIndex$2(cells, x => eq(x, cell));
    const getSelectedCells = tableSelection => lift2(getCellIndex(tableSelection.cells, tableSelection.rng.start), getCellIndex(tableSelection.cells, tableSelection.rng.end), (startIndex, endIndex) => tableSelection.cells.slice(startIndex, endIndex + 1));
    const isSingleCellTableContentSelected = (optCellRng, rng, isRoot) => optCellRng.exists(cellRng => isSingleCellTable(cellRng, isRoot) && hasAllContentsSelected(cellRng.start, rng));
    const unselectCells = (rng, selectionDetails) => {
      const {startTable, endTable} = selectionDetails;
      const otherContentRng = rng.cloneRange();
      startTable.each(table => otherContentRng.setStartAfter(table.dom));
      endTable.each(table => otherContentRng.setEndBefore(table.dom));
      return otherContentRng;
    };
    const handleSingleTable = (cellRng, selectionDetails, rng, isRoot) => getTableSelections(cellRng, selectionDetails, rng, isRoot).bind(({start, end}) => start.or(end)).bind(tableSelection => {
      const {isSameTable} = selectionDetails;
      const selectedCells = getSelectedCells(tableSelection).getOr([]);
      if (isSameTable && tableSelection.cells.length === selectedCells.length) {
        return Optional.some(deleteAction.fullTable(tableSelection.table));
      } else if (selectedCells.length > 0) {
        if (isSameTable) {
          return Optional.some(deleteAction.partialTable(selectedCells, Optional.none()));
        } else {
          const otherContentRng = unselectCells(rng, selectionDetails);
          return Optional.some(deleteAction.partialTable(selectedCells, Optional.some({
            ...selectionDetails,
            rng: otherContentRng
          })));
        }
      } else {
        return Optional.none();
      }
    });
    const handleMultiTable = (cellRng, selectionDetails, rng, isRoot) => getTableSelections(cellRng, selectionDetails, rng, isRoot).bind(({start, end}) => {
      const startTableSelectedCells = start.bind(getSelectedCells).getOr([]);
      const endTableSelectedCells = end.bind(getSelectedCells).getOr([]);
      if (startTableSelectedCells.length > 0 && endTableSelectedCells.length > 0) {
        const otherContentRng = unselectCells(rng, selectionDetails);
        return Optional.some(deleteAction.multiTable(startTableSelectedCells, endTableSelectedCells, otherContentRng));
      } else {
        return Optional.none();
      }
    });
    const getActionFromRange = (root, rng) => {
      const isRoot = isRootFromElement(root);
      const optCellRng = getCellRng(rng, isRoot);
      const selectionDetails = getTableDetailsFromRange(rng, isRoot);
      if (isSingleCellTableContentSelected(optCellRng, rng, isRoot)) {
        return optCellRng.map(cellRng => deleteAction.singleCellTable(rng, cellRng.start));
      } else if (selectionDetails.isMultiTable) {
        return handleMultiTable(optCellRng, selectionDetails, rng, isRoot);
      } else {
        return handleSingleTable(optCellRng, selectionDetails, rng, isRoot);
      }
    };

    const cleanCells = cells => each$e(cells, cell => {
      remove$a(cell, 'contenteditable');
      fillWithPaddingBr(cell);
    });
    const getOutsideBlock = (editor, container) => Optional.from(editor.dom.getParent(container, editor.dom.isBlock)).map(SugarElement.fromDom);
    const handleEmptyBlock = (editor, startInTable, emptyBlock) => {
      emptyBlock.each(block => {
        if (startInTable) {
          remove$5(block);
        } else {
          fillWithPaddingBr(block);
          editor.selection.setCursorLocation(block.dom, 0);
        }
      });
    };
    const deleteContentInsideCell = (editor, cell, rng, isFirstCellInSelection) => {
      const insideTableRng = rng.cloneRange();
      if (isFirstCellInSelection) {
        insideTableRng.setStart(rng.startContainer, rng.startOffset);
        insideTableRng.setEndAfter(cell.dom.lastChild);
      } else {
        insideTableRng.setStartBefore(cell.dom.firstChild);
        insideTableRng.setEnd(rng.endContainer, rng.endOffset);
      }
      deleteCellContents(editor, insideTableRng, cell, false).each(action => action());
    };
    const collapseAndRestoreCellSelection = editor => {
      const selectedCells = getCellsFromEditor(editor);
      const selectedNode = SugarElement.fromDom(editor.selection.getNode());
      if (isTableCell$3(selectedNode.dom) && isEmpty$2(selectedNode)) {
        editor.selection.setCursorLocation(selectedNode.dom, 0);
      } else {
        editor.selection.collapse(true);
      }
      if (selectedCells.length > 1 && exists(selectedCells, cell => eq(cell, selectedNode))) {
        set$3(selectedNode, 'data-mce-selected', '1');
      }
    };
    const emptySingleTableCells = (editor, cells, outsideDetails) => Optional.some(() => {
      const editorRng = editor.selection.getRng();
      const cellsToClean = outsideDetails.bind(({rng, isStartInTable}) => {
        const outsideBlock = getOutsideBlock(editor, isStartInTable ? rng.endContainer : rng.startContainer);
        rng.deleteContents();
        handleEmptyBlock(editor, isStartInTable, outsideBlock.filter(isEmpty$2));
        const endPointCell = isStartInTable ? cells[0] : cells[cells.length - 1];
        deleteContentInsideCell(editor, endPointCell, editorRng, isStartInTable);
        if (!isEmpty$2(endPointCell)) {
          return Optional.some(isStartInTable ? cells.slice(1) : cells.slice(0, -1));
        } else {
          return Optional.none();
        }
      }).getOr(cells);
      cleanCells(cellsToClean);
      collapseAndRestoreCellSelection(editor);
    });
    const emptyMultiTableCells = (editor, startTableCells, endTableCells, betweenRng) => Optional.some(() => {
      const rng = editor.selection.getRng();
      const startCell = startTableCells[0];
      const endCell = endTableCells[endTableCells.length - 1];
      deleteContentInsideCell(editor, startCell, rng, true);
      deleteContentInsideCell(editor, endCell, rng, false);
      const startTableCellsToClean = isEmpty$2(startCell) ? startTableCells : startTableCells.slice(1);
      const endTableCellsToClean = isEmpty$2(endCell) ? endTableCells : endTableCells.slice(0, -1);
      cleanCells(startTableCellsToClean.concat(endTableCellsToClean));
      betweenRng.deleteContents();
      collapseAndRestoreCellSelection(editor);
    });
    const deleteCellContents = (editor, rng, cell, moveSelection = true) => Optional.some(() => {
      deleteRangeContents(editor, rng, cell, moveSelection);
    });
    const deleteTableElement = (editor, table) => Optional.some(() => deleteElement$2(editor, false, table));
    const deleteCellRange = (editor, rootElm, rng) => getActionFromRange(rootElm, rng).bind(action => action.fold(curry(deleteCellContents, editor), curry(deleteTableElement, editor), curry(emptySingleTableCells, editor), curry(emptyMultiTableCells, editor)));
    const deleteCaptionRange = (editor, caption) => emptyElement(editor, caption);
    const deleteTableRange = (editor, rootElm, rng, startElm) => getParentCaption(rootElm, startElm).fold(() => deleteCellRange(editor, rootElm, rng), caption => deleteCaptionRange(editor, caption));
    const deleteRange$3 = (editor, startElm, selectedCells) => {
      const rootNode = SugarElement.fromDom(editor.getBody());
      const rng = editor.selection.getRng();
      return selectedCells.length !== 0 ? emptySingleTableCells(editor, selectedCells, Optional.none()) : deleteTableRange(editor, rootNode, rng, startElm);
    };
    const getParentCell = (rootElm, elm) => find$2(parentsAndSelf(elm, rootElm), isTableCell$2);
    const getParentCaption = (rootElm, elm) => find$2(parentsAndSelf(elm, rootElm), isTag('caption'));
    const deleteBetweenCells = (editor, rootElm, forward, fromCell, from) => navigate(forward, editor.getBody(), from).bind(to => getParentCell(rootElm, SugarElement.fromDom(to.getNode())).bind(toCell => eq(toCell, fromCell) ? Optional.none() : Optional.some(noop)));
    const emptyElement = (editor, elm) => Optional.some(() => {
      fillWithPaddingBr(elm);
      editor.selection.setCursorLocation(elm.dom, 0);
    });
    const isDeleteOfLastCharPos = (fromCaption, forward, from, to) => firstPositionIn(fromCaption.dom).bind(first => lastPositionIn(fromCaption.dom).map(last => forward ? from.isEqual(first) && to.isEqual(last) : from.isEqual(last) && to.isEqual(first))).getOr(true);
    const emptyCaretCaption = (editor, elm) => emptyElement(editor, elm);
    const validateCaretCaption = (rootElm, fromCaption, to) => getParentCaption(rootElm, SugarElement.fromDom(to.getNode())).fold(() => Optional.some(noop), toCaption => someIf(!eq(toCaption, fromCaption), noop));
    const deleteCaretInsideCaption = (editor, rootElm, forward, fromCaption, from) => navigate(forward, editor.getBody(), from).fold(() => Optional.some(noop), to => isDeleteOfLastCharPos(fromCaption, forward, from, to) ? emptyCaretCaption(editor, fromCaption) : validateCaretCaption(rootElm, fromCaption, to));
    const deleteCaretCells = (editor, forward, rootElm, startElm) => {
      const from = CaretPosition.fromRangeStart(editor.selection.getRng());
      return getParentCell(rootElm, startElm).bind(fromCell => isEmpty$2(fromCell) ? emptyElement(editor, fromCell) : deleteBetweenCells(editor, rootElm, forward, fromCell, from));
    };
    const deleteCaretCaption = (editor, forward, rootElm, fromCaption) => {
      const from = CaretPosition.fromRangeStart(editor.selection.getRng());
      return isEmpty$2(fromCaption) ? emptyElement(editor, fromCaption) : deleteCaretInsideCaption(editor, rootElm, forward, fromCaption, from);
    };
    const isNearTable = (forward, pos) => forward ? isBeforeTable(pos) : isAfterTable(pos);
    const isBeforeOrAfterTable = (editor, forward) => {
      const fromPos = CaretPosition.fromRangeStart(editor.selection.getRng());
      return isNearTable(forward, fromPos) || fromPosition(forward, editor.getBody(), fromPos).exists(pos => isNearTable(forward, pos));
    };
    const deleteCaret$3 = (editor, forward, startElm) => {
      const rootElm = SugarElement.fromDom(editor.getBody());
      return getParentCaption(rootElm, startElm).fold(() => deleteCaretCells(editor, forward, rootElm, startElm).orThunk(() => someIf(isBeforeOrAfterTable(editor, forward), noop)), fromCaption => deleteCaretCaption(editor, forward, rootElm, fromCaption));
    };
    const backspaceDelete$a = (editor, forward) => {
      const startElm = SugarElement.fromDom(editor.selection.getStart(true));
      const cells = getCellsFromEditor(editor);
      return editor.selection.isCollapsed() && cells.length === 0 ? deleteCaret$3(editor, forward, startElm) : deleteRange$3(editor, startElm, cells);
    };

    const getContentEditableRoot$1 = (root, node) => {
      let tempNode = node;
      while (tempNode && tempNode !== root) {
        if (isContentEditableTrue$3(tempNode) || isContentEditableFalse$b(tempNode)) {
          return tempNode;
        }
        tempNode = tempNode.parentNode;
      }
      return null;
    };

    const internalAttributesPrefixes = [
      'data-ephox-',
      'data-mce-',
      'data-alloy-',
      'data-snooker-',
      '_'
    ];
    const each$9 = Tools.each;
    const ElementUtils = editor => {
      const dom = editor.dom;
      const internalAttributes = new Set(editor.serializer.getTempAttrs());
      const compare = (node1, node2) => {
        if (node1.nodeName !== node2.nodeName || node1.nodeType !== node2.nodeType) {
          return false;
        }
        const getAttribs = node => {
          const attribs = {};
          each$9(dom.getAttribs(node), attr => {
            const name = attr.nodeName.toLowerCase();
            if (name !== 'style' && !isAttributeInternal(name)) {
              attribs[name] = dom.getAttrib(node, name);
            }
          });
          return attribs;
        };
        const compareObjects = (obj1, obj2) => {
          for (const name in obj1) {
            if (has$2(obj1, name)) {
              const value = obj2[name];
              if (isUndefined(value)) {
                return false;
              }
              if (obj1[name] !== value) {
                return false;
              }
              delete obj2[name];
            }
          }
          for (const name in obj2) {
            if (has$2(obj2, name)) {
              return false;
            }
          }
          return true;
        };
        if (isElement$6(node1) && isElement$6(node2)) {
          if (!compareObjects(getAttribs(node1), getAttribs(node2))) {
            return false;
          }
          if (!compareObjects(dom.parseStyle(dom.getAttrib(node1, 'style')), dom.parseStyle(dom.getAttrib(node2, 'style')))) {
            return false;
          }
        }
        return !isBookmarkNode$1(node1) && !isBookmarkNode$1(node2);
      };
      const isAttributeInternal = attributeName => exists(internalAttributesPrefixes, value => startsWith(attributeName, value)) || internalAttributes.has(attributeName);
      return {
        compare,
        isAttributeInternal
      };
    };

    const isHeading = node => [
      'h1',
      'h2',
      'h3',
      'h4',
      'h5',
      'h6'
    ].includes(node.name);
    const isSummary = node => node.name === 'summary';

    const traverse = (root, fn) => {
      let node = root;
      while (node = node.walk()) {
        fn(node);
      }
    };
    const matchNode$1 = (nodeFilters, attributeFilters, node, matches) => {
      const name = node.name;
      for (let ni = 0, nl = nodeFilters.length; ni < nl; ni++) {
        const filter = nodeFilters[ni];
        if (filter.name === name) {
          const match = matches.nodes[name];
          if (match) {
            match.nodes.push(node);
          } else {
            matches.nodes[name] = {
              filter,
              nodes: [node]
            };
          }
        }
      }
      if (node.attributes) {
        for (let ai = 0, al = attributeFilters.length; ai < al; ai++) {
          const filter = attributeFilters[ai];
          const attrName = filter.name;
          if (attrName in node.attributes.map) {
            const match = matches.attributes[attrName];
            if (match) {
              match.nodes.push(node);
            } else {
              matches.attributes[attrName] = {
                filter,
                nodes: [node]
              };
            }
          }
        }
      }
    };
    const findMatchingNodes = (nodeFilters, attributeFilters, node) => {
      const matches = {
        nodes: {},
        attributes: {}
      };
      if (node.firstChild) {
        traverse(node, childNode => {
          matchNode$1(nodeFilters, attributeFilters, childNode, matches);
        });
      }
      return matches;
    };
    const runFilters = (matches, args) => {
      const run = (matchRecord, filteringAttributes) => {
        each$d(matchRecord, match => {
          const nodes = from(match.nodes);
          each$e(match.filter.callbacks, callback => {
            for (let i = nodes.length - 1; i >= 0; i--) {
              const node = nodes[i];
              const valueMatches = filteringAttributes ? node.attr(match.filter.name) !== undefined : node.name === match.filter.name;
              if (!valueMatches || isNullable(node.parent)) {
                nodes.splice(i, 1);
              }
            }
            if (nodes.length > 0) {
              callback(nodes, match.filter.name, args);
            }
          });
        });
      };
      run(matches.nodes, false);
      run(matches.attributes, true);
    };
    const filter$2 = (nodeFilters, attributeFilters, node, args = {}) => {
      const matches = findMatchingNodes(nodeFilters, attributeFilters, node);
      runFilters(matches, args);
    };

    const paddEmptyNode = (settings, args, isBlock, node) => {
      const brPreferred = settings.pad_empty_with_br || args.insert;
      if (brPreferred && isBlock(node)) {
        const astNode = new AstNode('br', 1);
        if (args.insert) {
          astNode.attr('data-mce-bogus', '1');
        }
        node.empty().append(astNode);
      } else {
        node.empty().append(new AstNode('#text', 3)).value = nbsp;
      }
    };
    const isPaddedWithNbsp = node => {
      var _a;
      return hasOnlyChild(node, '#text') && ((_a = node === null || node === void 0 ? void 0 : node.firstChild) === null || _a === void 0 ? void 0 : _a.value) === nbsp;
    };
    const hasOnlyChild = (node, name) => {
      const firstChild = node === null || node === void 0 ? void 0 : node.firstChild;
      return isNonNullable(firstChild) && firstChild === node.lastChild && firstChild.name === name;
    };
    const isPadded = (schema, node) => {
      const rule = schema.getElementRule(node.name);
      return (rule === null || rule === void 0 ? void 0 : rule.paddEmpty) === true;
    };
    const isEmpty = (schema, nonEmptyElements, whitespaceElements, node) => node.isEmpty(nonEmptyElements, whitespaceElements, node => isPadded(schema, node));
    const isLineBreakNode = (node, isBlock) => isNonNullable(node) && (isBlock(node) || node.name === 'br');
    const findClosestEditingHost = scope => {
      let editableNode;
      for (let node = scope; node; node = node.parent) {
        const contentEditable = node.attr('contenteditable');
        if (contentEditable === 'false') {
          break;
        } else if (contentEditable === 'true') {
          editableNode = node;
        }
      }
      return Optional.from(editableNode);
    };

    const removeOrUnwrapInvalidNode = (node, schema, originalNodeParent = node.parent) => {
      if (schema.getSpecialElements()[node.name]) {
        node.empty().remove();
      } else {
        const children = node.children();
        for (const childNode of children) {
          if (originalNodeParent && !schema.isValidChild(originalNodeParent.name, childNode.name)) {
            removeOrUnwrapInvalidNode(childNode, schema, originalNodeParent);
          }
        }
        node.unwrap();
      }
    };
    const cleanInvalidNodes = (nodes, schema, rootNode, onCreate = noop) => {
      const textBlockElements = schema.getTextBlockElements();
      const nonEmptyElements = schema.getNonEmptyElements();
      const whitespaceElements = schema.getWhitespaceElements();
      const nonSplittableElements = Tools.makeMap('tr,td,th,tbody,thead,tfoot,table,summary');
      const fixed = new Set();
      const isSplittableElement = node => node !== rootNode && !nonSplittableElements[node.name];
      for (let ni = 0; ni < nodes.length; ni++) {
        const node = nodes[ni];
        let parent;
        let newParent;
        let tempNode;
        if (!node.parent || fixed.has(node)) {
          continue;
        }
        if (textBlockElements[node.name] && node.parent.name === 'li') {
          let sibling = node.next;
          while (sibling) {
            if (textBlockElements[sibling.name]) {
              sibling.name = 'li';
              fixed.add(sibling);
              node.parent.insert(sibling, node.parent);
            } else {
              break;
            }
            sibling = sibling.next;
          }
          node.unwrap();
          continue;
        }
        const parents = [node];
        for (parent = node.parent; parent && !schema.isValidChild(parent.name, node.name) && isSplittableElement(parent); parent = parent.parent) {
          parents.push(parent);
        }
        if (parent && parents.length > 1) {
          if (!isInvalid(schema, node, parent)) {
            parents.reverse();
            newParent = parents[0].clone();
            onCreate(newParent);
            let currentNode = newParent;
            for (let i = 0; i < parents.length - 1; i++) {
              if (schema.isValidChild(currentNode.name, parents[i].name) && i > 0) {
                tempNode = parents[i].clone();
                onCreate(tempNode);
                currentNode.append(tempNode);
              } else {
                tempNode = currentNode;
              }
              for (let childNode = parents[i].firstChild; childNode && childNode !== parents[i + 1];) {
                const nextNode = childNode.next;
                tempNode.append(childNode);
                childNode = nextNode;
              }
              currentNode = tempNode;
            }
            if (!isEmpty(schema, nonEmptyElements, whitespaceElements, newParent)) {
              parent.insert(newParent, parents[0], true);
              parent.insert(node, newParent);
            } else {
              parent.insert(node, parents[0], true);
            }
            parent = parents[0];
            if (isEmpty(schema, nonEmptyElements, whitespaceElements, parent) || hasOnlyChild(parent, 'br')) {
              parent.empty().remove();
            }
          } else {
            removeOrUnwrapInvalidNode(node, schema);
          }
        } else if (node.parent) {
          if (node.name === 'li') {
            let sibling = node.prev;
            if (sibling && (sibling.name === 'ul' || sibling.name === 'ol')) {
              sibling.append(node);
              continue;
            }
            sibling = node.next;
            if (sibling && (sibling.name === 'ul' || sibling.name === 'ol') && sibling.firstChild) {
              sibling.insert(node, sibling.firstChild, true);
              continue;
            }
            const wrapper = new AstNode('ul', 1);
            onCreate(wrapper);
            node.wrap(wrapper);
            continue;
          }
          if (schema.isValidChild(node.parent.name, 'div') && schema.isValidChild('div', node.name)) {
            const wrapper = new AstNode('div', 1);
            onCreate(wrapper);
            node.wrap(wrapper);
          } else {
            removeOrUnwrapInvalidNode(node, schema);
          }
        }
      }
    };
    const hasClosest = (node, parentName) => {
      let tempNode = node;
      while (tempNode) {
        if (tempNode.name === parentName) {
          return true;
        }
        tempNode = tempNode.parent;
      }
      return false;
    };
    const isInvalid = (schema, node, parent = node.parent) => {
      if (!parent) {
        return false;
      }
      if (schema.children[node.name] && !schema.isValidChild(parent.name, node.name)) {
        return true;
      }
      if (node.name === 'a' && hasClosest(parent, 'a')) {
        return true;
      }
      if (isSummary(parent) && isHeading(node)) {
        return !((parent === null || parent === void 0 ? void 0 : parent.firstChild) === node && (parent === null || parent === void 0 ? void 0 : parent.lastChild) === node);
      }
      return false;
    };

    const createRange = (sc, so, ec, eo) => {
      const rng = document.createRange();
      rng.setStart(sc, so);
      rng.setEnd(ec, eo);
      return rng;
    };
    const normalizeBlockSelectionRange = rng => {
      const startPos = CaretPosition.fromRangeStart(rng);
      const endPos = CaretPosition.fromRangeEnd(rng);
      const rootNode = rng.commonAncestorContainer;
      return fromPosition(false, rootNode, endPos).map(newEndPos => {
        if (!isInSameBlock(startPos, endPos, rootNode) && isInSameBlock(startPos, newEndPos, rootNode)) {
          return createRange(startPos.container(), startPos.offset(), newEndPos.container(), newEndPos.offset());
        } else {
          return rng;
        }
      }).getOr(rng);
    };
    const normalize = rng => rng.collapsed ? rng : normalizeBlockSelectionRange(rng);

    const hasOnlyOneChild$1 = node => {
      return isNonNullable(node.firstChild) && node.firstChild === node.lastChild;
    };
    const isPaddingNode = node => {
      return node.name === 'br' || node.value === nbsp;
    };
    const isPaddedEmptyBlock = (schema, node) => {
      const blockElements = schema.getBlockElements();
      return blockElements[node.name] && hasOnlyOneChild$1(node) && isPaddingNode(node.firstChild);
    };
    const isEmptyFragmentElement = (schema, node) => {
      const nonEmptyElements = schema.getNonEmptyElements();
      return isNonNullable(node) && (node.isEmpty(nonEmptyElements) || isPaddedEmptyBlock(schema, node));
    };
    const isListFragment = (schema, fragment) => {
      let firstChild = fragment.firstChild;
      let lastChild = fragment.lastChild;
      if (firstChild && firstChild.name === 'meta') {
        firstChild = firstChild.next;
      }
      if (lastChild && lastChild.attr('id') === 'mce_marker') {
        lastChild = lastChild.prev;
      }
      if (isEmptyFragmentElement(schema, lastChild)) {
        lastChild = lastChild === null || lastChild === void 0 ? void 0 : lastChild.prev;
      }
      if (!firstChild || firstChild !== lastChild) {
        return false;
      }
      return firstChild.name === 'ul' || firstChild.name === 'ol';
    };
    const cleanupDomFragment = domFragment => {
      var _a, _b;
      const firstChild = domFragment.firstChild;
      const lastChild = domFragment.lastChild;
      if (firstChild && firstChild.nodeName === 'META') {
        (_a = firstChild.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(firstChild);
      }
      if (lastChild && lastChild.id === 'mce_marker') {
        (_b = lastChild.parentNode) === null || _b === void 0 ? void 0 : _b.removeChild(lastChild);
      }
      return domFragment;
    };
    const toDomFragment = (dom, serializer, fragment) => {
      const html = serializer.serialize(fragment);
      const domFragment = dom.createFragment(html);
      return cleanupDomFragment(domFragment);
    };
    const listItems = elm => {
      var _a;
      return filter$5((_a = elm === null || elm === void 0 ? void 0 : elm.childNodes) !== null && _a !== void 0 ? _a : [], child => {
        return child.nodeName === 'LI';
      });
    };
    const isPadding = node => {
      return node.data === nbsp || isBr$6(node);
    };
    const isListItemPadded = node => {
      return isNonNullable(node === null || node === void 0 ? void 0 : node.firstChild) && node.firstChild === node.lastChild && isPadding(node.firstChild);
    };
    const isEmptyOrPadded = elm => {
      return !elm.firstChild || isListItemPadded(elm);
    };
    const trimListItems = elms => {
      return elms.length > 0 && isEmptyOrPadded(elms[elms.length - 1]) ? elms.slice(0, -1) : elms;
    };
    const getParentLi = (dom, node) => {
      const parentBlock = dom.getParent(node, dom.isBlock);
      return parentBlock && parentBlock.nodeName === 'LI' ? parentBlock : null;
    };
    const isParentBlockLi = (dom, node) => {
      return !!getParentLi(dom, node);
    };
    const getSplit = (parentNode, rng) => {
      const beforeRng = rng.cloneRange();
      const afterRng = rng.cloneRange();
      beforeRng.setStartBefore(parentNode);
      afterRng.setEndAfter(parentNode);
      return [
        beforeRng.cloneContents(),
        afterRng.cloneContents()
      ];
    };
    const findFirstIn = (node, rootNode) => {
      const caretPos = CaretPosition.before(node);
      const caretWalker = CaretWalker(rootNode);
      const newCaretPos = caretWalker.next(caretPos);
      return newCaretPos ? newCaretPos.toRange() : null;
    };
    const findLastOf = (node, rootNode) => {
      const caretPos = CaretPosition.after(node);
      const caretWalker = CaretWalker(rootNode);
      const newCaretPos = caretWalker.prev(caretPos);
      return newCaretPos ? newCaretPos.toRange() : null;
    };
    const insertMiddle = (target, elms, rootNode, rng) => {
      const parts = getSplit(target, rng);
      const parentElm = target.parentNode;
      if (parentElm) {
        parentElm.insertBefore(parts[0], target);
        Tools.each(elms, li => {
          parentElm.insertBefore(li, target);
        });
        parentElm.insertBefore(parts[1], target);
        parentElm.removeChild(target);
      }
      return findLastOf(elms[elms.length - 1], rootNode);
    };
    const insertBefore$2 = (target, elms, rootNode) => {
      const parentElm = target.parentNode;
      if (parentElm) {
        Tools.each(elms, elm => {
          parentElm.insertBefore(elm, target);
        });
      }
      return findFirstIn(target, rootNode);
    };
    const insertAfter$2 = (target, elms, rootNode, dom) => {
      dom.insertAfter(elms.reverse(), target);
      return findLastOf(elms[0], rootNode);
    };
    const insertAtCaret$1 = (serializer, dom, rng, fragment) => {
      const domFragment = toDomFragment(dom, serializer, fragment);
      const liTarget = getParentLi(dom, rng.startContainer);
      const liElms = trimListItems(listItems(domFragment.firstChild));
      const BEGINNING = 1, END = 2;
      const rootNode = dom.getRoot();
      const isAt = location => {
        const caretPos = CaretPosition.fromRangeStart(rng);
        const caretWalker = CaretWalker(dom.getRoot());
        const newPos = location === BEGINNING ? caretWalker.prev(caretPos) : caretWalker.next(caretPos);
        const newPosNode = newPos === null || newPos === void 0 ? void 0 : newPos.getNode();
        return newPosNode ? getParentLi(dom, newPosNode) !== liTarget : true;
      };
      if (!liTarget) {
        return null;
      } else if (isAt(BEGINNING)) {
        return insertBefore$2(liTarget, liElms, rootNode);
      } else if (isAt(END)) {
        return insertAfter$2(liTarget, liElms, rootNode, dom);
      } else {
        return insertMiddle(liTarget, liElms, rootNode, rng);
      }
    };

    const mergeableWrappedElements = ['pre'];
    const shouldPasteContentOnly = (dom, fragment, parentNode, root) => {
      var _a;
      const firstNode = fragment.firstChild;
      const lastNode = fragment.lastChild;
      const last = lastNode.attr('data-mce-type') === 'bookmark' ? lastNode.prev : lastNode;
      const isPastingSingleElement = firstNode === last;
      const isWrappedElement = contains$2(mergeableWrappedElements, firstNode.name);
      if (isPastingSingleElement && isWrappedElement) {
        const isContentEditable = firstNode.attr('contenteditable') !== 'false';
        const isPastingInTheSameBlockTag = ((_a = dom.getParent(parentNode, dom.isBlock)) === null || _a === void 0 ? void 0 : _a.nodeName.toLowerCase()) === firstNode.name;
        const isPastingInContentEditable = Optional.from(getContentEditableRoot$1(root, parentNode)).forall(isContentEditableTrue$3);
        return isContentEditable && isPastingInTheSameBlockTag && isPastingInContentEditable;
      } else {
        return false;
      }
    };
    const isTableCell = isTableCell$3;
    const isTableCellContentSelected = (dom, rng, cell) => {
      if (isNonNullable(cell)) {
        const endCell = dom.getParent(rng.endContainer, isTableCell);
        return cell === endCell && hasAllContentsSelected(SugarElement.fromDom(cell), rng);
      } else {
        return false;
      }
    };
    const validInsertion = (editor, value, parentNode) => {
      var _a;
      if (parentNode.getAttribute('data-mce-bogus') === 'all') {
        (_a = parentNode.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(editor.dom.createFragment(value), parentNode);
      } else {
        const node = parentNode.firstChild;
        const node2 = parentNode.lastChild;
        if (!node || node === node2 && node.nodeName === 'BR') {
          editor.dom.setHTML(parentNode, value);
        } else {
          editor.selection.setContent(value, { no_events: true });
        }
      }
    };
    const trimBrsFromTableCell = (dom, elm, schema) => {
      Optional.from(dom.getParent(elm, 'td,th')).map(SugarElement.fromDom).each(el => trimBlockTrailingBr(el, schema));
    };
    const reduceInlineTextElements = (editor, merge) => {
      const textInlineElements = editor.schema.getTextInlineElements();
      const dom = editor.dom;
      if (merge) {
        const root = editor.getBody();
        const elementUtils = ElementUtils(editor);
        Tools.each(dom.select('*[data-mce-fragment]'), node => {
          const isInline = isNonNullable(textInlineElements[node.nodeName.toLowerCase()]);
          if (isInline && hasInheritableStyles(dom, node)) {
            for (let parentNode = node.parentElement; isNonNullable(parentNode) && parentNode !== root; parentNode = parentNode.parentElement) {
              const styleConflict = hasStyleConflict(dom, node, parentNode);
              if (styleConflict) {
                break;
              }
              if (elementUtils.compare(parentNode, node)) {
                dom.remove(node, true);
                break;
              }
            }
          }
        });
      }
    };
    const markFragmentElements = fragment => {
      let node = fragment;
      while (node = node.walk()) {
        if (node.type === 1) {
          node.attr('data-mce-fragment', '1');
        }
      }
    };
    const unmarkFragmentElements = elm => {
      Tools.each(elm.getElementsByTagName('*'), elm => {
        elm.removeAttribute('data-mce-fragment');
      });
    };
    const isPartOfFragment = node => {
      return !!node.getAttribute('data-mce-fragment');
    };
    const canHaveChildren = (editor, node) => {
      return isNonNullable(node) && !editor.schema.getVoidElements()[node.nodeName];
    };
    const moveSelectionToMarker = (editor, marker) => {
      var _a, _b, _c;
      let nextRng;
      const dom = editor.dom;
      const selection = editor.selection;
      if (!marker) {
        return;
      }
      selection.scrollIntoView(marker);
      const parentEditableElm = getContentEditableRoot$1(editor.getBody(), marker);
      if (parentEditableElm && dom.getContentEditable(parentEditableElm) === 'false') {
        dom.remove(marker);
        selection.select(parentEditableElm);
        return;
      }
      let rng = dom.createRng();
      const node = marker.previousSibling;
      if (isText$a(node)) {
        rng.setStart(node, (_b = (_a = node.nodeValue) === null || _a === void 0 ? void 0 : _a.length) !== null && _b !== void 0 ? _b : 0);
        const node2 = marker.nextSibling;
        if (isText$a(node2)) {
          node.appendData(node2.data);
          (_c = node2.parentNode) === null || _c === void 0 ? void 0 : _c.removeChild(node2);
        }
      } else {
        rng.setStartBefore(marker);
        rng.setEndBefore(marker);
      }
      const findNextCaretRng = rng => {
        let caretPos = CaretPosition.fromRangeStart(rng);
        const caretWalker = CaretWalker(editor.getBody());
        caretPos = caretWalker.next(caretPos);
        return caretPos === null || caretPos === void 0 ? void 0 : caretPos.toRange();
      };
      const parentBlock = dom.getParent(marker, dom.isBlock);
      dom.remove(marker);
      if (parentBlock && dom.isEmpty(parentBlock)) {
        const isCell = isTableCell(parentBlock);
        empty(SugarElement.fromDom(parentBlock));
        rng.setStart(parentBlock, 0);
        rng.setEnd(parentBlock, 0);
        if (!isCell && !isPartOfFragment(parentBlock) && (nextRng = findNextCaretRng(rng))) {
          rng = nextRng;
          dom.remove(parentBlock);
        } else {
          dom.add(parentBlock, dom.create('br', isCell ? {} : { 'data-mce-bogus': '1' }));
        }
      }
      selection.setRng(rng);
    };
    const deleteSelectedContent = editor => {
      const dom = editor.dom;
      const rng = normalize(editor.selection.getRng());
      editor.selection.setRng(rng);
      const startCell = dom.getParent(rng.startContainer, isTableCell);
      if (isTableCellContentSelected(dom, rng, startCell)) {
        deleteCellContents(editor, rng, SugarElement.fromDom(startCell));
      } else if (rng.startContainer === rng.endContainer && rng.endOffset - rng.startOffset === 1 && isText$a(rng.startContainer.childNodes[rng.startOffset])) {
        rng.deleteContents();
      } else {
        editor.getDoc().execCommand('Delete', false);
      }
    };
    const findMarkerNode = scope => {
      for (let markerNode = scope; markerNode; markerNode = markerNode.walk()) {
        if (markerNode.attr('id') === 'mce_marker') {
          return Optional.some(markerNode);
        }
      }
      return Optional.none();
    };
    const notHeadingsInSummary = (dom, node, fragment) => {
      var _a;
      return exists(fragment.children(), isHeading) && ((_a = dom.getParent(node, dom.isBlock)) === null || _a === void 0 ? void 0 : _a.nodeName) === 'SUMMARY';
    };
    const insertHtmlAtCaret = (editor, value, details) => {
      var _a, _b;
      const selection = editor.selection;
      const dom = editor.dom;
      const parser = editor.parser;
      const merge = details.merge;
      const serializer = HtmlSerializer({ validate: true }, editor.schema);
      const bookmarkHtml = '<span id="mce_marker" data-mce-type="bookmark">&#xFEFF;</span>';
      if (!details.preserve_zwsp) {
        value = trim$2(value);
      }
      if (value.indexOf('{$caret}') === -1) {
        value += '{$caret}';
      }
      value = value.replace(/\{\$caret\}/, bookmarkHtml);
      let rng = selection.getRng();
      const caretElement = rng.startContainer;
      const body = editor.getBody();
      if (caretElement === body && selection.isCollapsed()) {
        if (dom.isBlock(body.firstChild) && canHaveChildren(editor, body.firstChild) && dom.isEmpty(body.firstChild)) {
          rng = dom.createRng();
          rng.setStart(body.firstChild, 0);
          rng.setEnd(body.firstChild, 0);
          selection.setRng(rng);
        }
      }
      if (!selection.isCollapsed()) {
        deleteSelectedContent(editor);
      }
      const parentNode = selection.getNode();
      const parserArgs = {
        context: parentNode.nodeName.toLowerCase(),
        data: details.data,
        insert: true
      };
      const fragment = parser.parse(value, parserArgs);
      if (details.paste === true && isListFragment(editor.schema, fragment) && isParentBlockLi(dom, parentNode)) {
        rng = insertAtCaret$1(serializer, dom, selection.getRng(), fragment);
        if (rng) {
          selection.setRng(rng);
        }
        return value;
      }
      if (details.paste === true && shouldPasteContentOnly(dom, fragment, parentNode, editor.getBody())) {
        (_a = fragment.firstChild) === null || _a === void 0 ? void 0 : _a.unwrap();
      }
      markFragmentElements(fragment);
      let node = fragment.lastChild;
      if (node && node.attr('id') === 'mce_marker') {
        const marker = node;
        for (node = node.prev; node; node = node.walk(true)) {
          if (node.type === 3 || !dom.isBlock(node.name)) {
            if (node.parent && editor.schema.isValidChild(node.parent.name, 'span')) {
              node.parent.insert(marker, node, node.name === 'br');
            }
            break;
          }
        }
      }
      editor._selectionOverrides.showBlockCaretContainer(parentNode);
      if (!parserArgs.invalid && !notHeadingsInSummary(dom, parentNode, fragment)) {
        value = serializer.serialize(fragment);
        validInsertion(editor, value, parentNode);
      } else {
        editor.selection.setContent(bookmarkHtml);
        let parentNode = selection.getNode();
        let tempNode;
        const rootNode = editor.getBody();
        if (isDocument$1(parentNode)) {
          parentNode = tempNode = rootNode;
        } else {
          tempNode = parentNode;
        }
        while (tempNode && tempNode !== rootNode) {
          parentNode = tempNode;
          tempNode = tempNode.parentNode;
        }
        value = parentNode === rootNode ? rootNode.innerHTML : dom.getOuterHTML(parentNode);
        const root = parser.parse(value);
        const markerNode = findMarkerNode(root);
        const editingHost = markerNode.bind(findClosestEditingHost).getOr(root);
        markerNode.each(marker => marker.replace(fragment));
        const toExtract = fragment.children();
        const parent = (_b = fragment.parent) !== null && _b !== void 0 ? _b : root;
        fragment.unwrap();
        const invalidChildren = filter$5(toExtract, node => isInvalid(editor.schema, node, parent));
        cleanInvalidNodes(invalidChildren, editor.schema, editingHost);
        filter$2(parser.getNodeFilters(), parser.getAttributeFilters(), root);
        value = serializer.serialize(root);
        if (parentNode === rootNode) {
          dom.setHTML(rootNode, value);
        } else {
          dom.setOuterHTML(parentNode, value);
        }
      }
      reduceInlineTextElements(editor, merge);
      moveSelectionToMarker(editor, dom.get('mce_marker'));
      unmarkFragmentElements(editor.getBody());
      trimBrsFromTableCell(dom, selection.getStart(), editor.schema);
      updateCaret(editor.schema, editor.getBody(), selection.getStart());
      return value;
    };

    const isTreeNode = content => content instanceof AstNode;

    const moveSelection = editor => {
      if (hasFocus(editor)) {
        firstPositionIn(editor.getBody()).each(pos => {
          const node = pos.getNode();
          const caretPos = isTable$2(node) ? firstPositionIn(node).getOr(pos) : pos;
          editor.selection.setRng(caretPos.toRange());
        });
      }
    };
    const setEditorHtml = (editor, html, noSelection) => {
      editor.dom.setHTML(editor.getBody(), html);
      if (noSelection !== true) {
        moveSelection(editor);
      }
    };
    const setContentString = (editor, body, content, args) => {
      content = trim$2(content);
      if (content.length === 0 || /^\s+$/.test(content)) {
        const padd = '<br data-mce-bogus="1">';
        if (body.nodeName === 'TABLE') {
          content = '<tr><td>' + padd + '</td></tr>';
        } else if (/^(UL|OL)$/.test(body.nodeName)) {
          content = '<li>' + padd + '</li>';
        }
        const forcedRootBlockName = getForcedRootBlock(editor);
        if (editor.schema.isValidChild(body.nodeName.toLowerCase(), forcedRootBlockName.toLowerCase())) {
          content = padd;
          content = editor.dom.createHTML(forcedRootBlockName, getForcedRootBlockAttrs(editor), content);
        } else if (!content) {
          content = padd;
        }
        setEditorHtml(editor, content, args.no_selection);
        return {
          content,
          html: content
        };
      } else {
        if (args.format !== 'raw') {
          content = HtmlSerializer({ validate: false }, editor.schema).serialize(editor.parser.parse(content, {
            isRootContent: true,
            insert: true
          }));
        }
        const trimmedHtml = isWsPreserveElement(SugarElement.fromDom(body)) ? content : Tools.trim(content);
        setEditorHtml(editor, trimmedHtml, args.no_selection);
        return {
          content: trimmedHtml,
          html: trimmedHtml
        };
      }
    };
    const setContentTree = (editor, body, content, args) => {
      filter$2(editor.parser.getNodeFilters(), editor.parser.getAttributeFilters(), content);
      const html = HtmlSerializer({ validate: false }, editor.schema).serialize(content);
      const trimmedHtml = trim$2(isWsPreserveElement(SugarElement.fromDom(body)) ? html : Tools.trim(html));
      setEditorHtml(editor, trimmedHtml, args.no_selection);
      return {
        content,
        html: trimmedHtml
      };
    };
    const setContentInternal = (editor, content, args) => {
      return Optional.from(editor.getBody()).map(body => {
        if (isTreeNode(content)) {
          return setContentTree(editor, body, content, args);
        } else {
          return setContentString(editor, body, content, args);
        }
      }).getOr({
        content,
        html: isTreeNode(args.content) ? '' : args.content
      });
    };

    const ensureIsRoot = isRoot => isFunction(isRoot) ? isRoot : never;
    const ancestor = (scope, transform, isRoot) => {
      let element = scope.dom;
      const stop = ensureIsRoot(isRoot);
      while (element.parentNode) {
        element = element.parentNode;
        const el = SugarElement.fromDom(element);
        const transformed = transform(el);
        if (transformed.isSome()) {
          return transformed;
        } else if (stop(el)) {
          break;
        }
      }
      return Optional.none();
    };
    const closest$1 = (scope, transform, isRoot) => {
      const current = transform(scope);
      const stop = ensureIsRoot(isRoot);
      return current.orThunk(() => stop(scope) ? Optional.none() : ancestor(scope, transform, stop));
    };

    const isEq$3 = isEq$5;
    const matchesUnInheritedFormatSelector = (ed, node, name) => {
      const formatList = ed.formatter.get(name);
      if (formatList) {
        for (let i = 0; i < formatList.length; i++) {
          const format = formatList[i];
          if (isSelectorFormat(format) && format.inherit === false && ed.dom.is(node, format.selector)) {
            return true;
          }
        }
      }
      return false;
    };
    const matchParents = (editor, node, name, vars, similar) => {
      const root = editor.dom.getRoot();
      if (node === root) {
        return false;
      }
      const matchedNode = editor.dom.getParent(node, elm => {
        if (matchesUnInheritedFormatSelector(editor, elm, name)) {
          return true;
        }
        return elm.parentNode === root || !!matchNode(editor, elm, name, vars, true);
      });
      return !!matchNode(editor, matchedNode, name, vars, similar);
    };
    const matchName = (dom, node, format) => {
      if (isInlineFormat(format) && isEq$3(node, format.inline)) {
        return true;
      }
      if (isBlockFormat(format) && isEq$3(node, format.block)) {
        return true;
      }
      if (isSelectorFormat(format)) {
        return isElement$6(node) && dom.is(node, format.selector);
      }
      return false;
    };
    const matchItems = (dom, node, format, itemName, similar, vars) => {
      const items = format[itemName];
      const matchAttributes = itemName === 'attributes';
      if (isFunction(format.onmatch)) {
        return format.onmatch(node, format, itemName);
      }
      if (items) {
        if (!isArrayLike(items)) {
          for (const key in items) {
            if (has$2(items, key)) {
              const value = matchAttributes ? dom.getAttrib(node, key) : getStyle(dom, node, key);
              const expectedValue = replaceVars(items[key], vars);
              const isEmptyValue = isNullable(value) || isEmpty$3(value);
              if (isEmptyValue && isNullable(expectedValue)) {
                continue;
              }
              if (similar && isEmptyValue && !format.exact) {
                return false;
              }
              if ((!similar || format.exact) && !isEq$3(value, normalizeStyleValue(expectedValue, key))) {
                return false;
              }
            }
          }
        } else {
          for (let i = 0; i < items.length; i++) {
            if (matchAttributes ? dom.getAttrib(node, items[i]) : getStyle(dom, node, items[i])) {
              return true;
            }
          }
        }
      }
      return true;
    };
    const matchNode = (ed, node, name, vars, similar) => {
      const formatList = ed.formatter.get(name);
      const dom = ed.dom;
      if (formatList && isElement$6(node)) {
        for (let i = 0; i < formatList.length; i++) {
          const format = formatList[i];
          if (matchName(ed.dom, node, format) && matchItems(dom, node, format, 'attributes', similar, vars) && matchItems(dom, node, format, 'styles', similar, vars)) {
            const classes = format.classes;
            if (classes) {
              for (let x = 0; x < classes.length; x++) {
                if (!ed.dom.hasClass(node, replaceVars(classes[x], vars))) {
                  return;
                }
              }
            }
            return format;
          }
        }
      }
      return undefined;
    };
    const match$2 = (editor, name, vars, node, similar) => {
      if (node) {
        return matchParents(editor, node, name, vars, similar);
      }
      node = editor.selection.getNode();
      if (matchParents(editor, node, name, vars, similar)) {
        return true;
      }
      const startNode = editor.selection.getStart();
      if (startNode !== node) {
        if (matchParents(editor, startNode, name, vars, similar)) {
          return true;
        }
      }
      return false;
    };
    const matchAll = (editor, names, vars) => {
      const matchedFormatNames = [];
      const checkedMap = {};
      const startElement = editor.selection.getStart();
      editor.dom.getParent(startElement, node => {
        for (let i = 0; i < names.length; i++) {
          const name = names[i];
          if (!checkedMap[name] && matchNode(editor, node, name, vars)) {
            checkedMap[name] = true;
            matchedFormatNames.push(name);
          }
        }
      }, editor.dom.getRoot());
      return matchedFormatNames;
    };
    const closest = (editor, names) => {
      const isRoot = elm => eq(elm, SugarElement.fromDom(editor.getBody()));
      const match = (elm, name) => matchNode(editor, elm.dom, name) ? Optional.some(name) : Optional.none();
      return Optional.from(editor.selection.getStart(true)).bind(rawElm => closest$1(SugarElement.fromDom(rawElm), elm => findMap(names, name => match(elm, name)), isRoot)).getOrNull();
    };
    const canApply = (editor, name) => {
      const formatList = editor.formatter.get(name);
      const dom = editor.dom;
      if (formatList && editor.selection.isEditable()) {
        const startNode = editor.selection.getStart();
        const parents = getParents$2(dom, startNode);
        for (let x = formatList.length - 1; x >= 0; x--) {
          const format = formatList[x];
          if (!isSelectorFormat(format)) {
            return true;
          }
          for (let i = parents.length - 1; i >= 0; i--) {
            if (dom.is(parents[i], format.selector)) {
              return true;
            }
          }
        }
      }
      return false;
    };
    const matchAllOnNode = (editor, node, formatNames) => foldl(formatNames, (acc, name) => {
      const matchSimilar = isVariableFormatName(editor, name);
      if (editor.formatter.matchNode(node, name, {}, matchSimilar)) {
        return acc.concat([name]);
      } else {
        return acc;
      }
    }, []);

    const ZWSP = ZWSP$1;
    const importNode = (ownerDocument, node) => {
      return ownerDocument.importNode(node, true);
    };
    const findFirstTextNode = node => {
      if (node) {
        const walker = new DomTreeWalker(node, node);
        for (let tempNode = walker.current(); tempNode; tempNode = walker.next()) {
          if (isText$a(tempNode)) {
            return tempNode;
          }
        }
      }
      return null;
    };
    const createCaretContainer = fill => {
      const caretContainer = SugarElement.fromTag('span');
      setAll$1(caretContainer, {
        'id': CARET_ID,
        'data-mce-bogus': '1',
        'data-mce-type': 'format-caret'
      });
      if (fill) {
        append$1(caretContainer, SugarElement.fromText(ZWSP));
      }
      return caretContainer;
    };
    const trimZwspFromCaretContainer = caretContainerNode => {
      const textNode = findFirstTextNode(caretContainerNode);
      if (textNode && textNode.data.charAt(0) === ZWSP) {
        textNode.deleteData(0, 1);
      }
      return textNode;
    };
    const removeCaretContainerNode = (editor, node, moveCaret) => {
      const dom = editor.dom, selection = editor.selection;
      if (isCaretContainerEmpty(node)) {
        deleteElement$2(editor, false, SugarElement.fromDom(node), moveCaret, true);
      } else {
        const rng = selection.getRng();
        const block = dom.getParent(node, dom.isBlock);
        const startContainer = rng.startContainer;
        const startOffset = rng.startOffset;
        const endContainer = rng.endContainer;
        const endOffset = rng.endOffset;
        const textNode = trimZwspFromCaretContainer(node);
        dom.remove(node, true);
        if (startContainer === textNode && startOffset > 0) {
          rng.setStart(textNode, startOffset - 1);
        }
        if (endContainer === textNode && endOffset > 0) {
          rng.setEnd(textNode, endOffset - 1);
        }
        if (block && dom.isEmpty(block)) {
          fillWithPaddingBr(SugarElement.fromDom(block));
        }
        selection.setRng(rng);
      }
    };
    const removeCaretContainer = (editor, node, moveCaret) => {
      const dom = editor.dom, selection = editor.selection;
      if (!node) {
        node = getParentCaretContainer(editor.getBody(), selection.getStart());
        if (!node) {
          while (node = dom.get(CARET_ID)) {
            removeCaretContainerNode(editor, node, moveCaret);
          }
        }
      } else {
        removeCaretContainerNode(editor, node, moveCaret);
      }
    };
    const insertCaretContainerNode = (editor, caretContainer, formatNode) => {
      var _a, _b;
      const dom = editor.dom;
      const block = dom.getParent(formatNode, curry(isTextBlock$1, editor.schema));
      if (block && dom.isEmpty(block)) {
        (_a = formatNode.parentNode) === null || _a === void 0 ? void 0 : _a.replaceChild(caretContainer, formatNode);
      } else {
        removeTrailingBr(SugarElement.fromDom(formatNode));
        if (dom.isEmpty(formatNode)) {
          (_b = formatNode.parentNode) === null || _b === void 0 ? void 0 : _b.replaceChild(caretContainer, formatNode);
        } else {
          dom.insertAfter(caretContainer, formatNode);
        }
      }
    };
    const appendNode = (parentNode, node) => {
      parentNode.appendChild(node);
      return node;
    };
    const insertFormatNodesIntoCaretContainer = (formatNodes, caretContainer) => {
      var _a;
      const innerMostFormatNode = foldr(formatNodes, (parentNode, formatNode) => {
        return appendNode(parentNode, formatNode.cloneNode(false));
      }, caretContainer);
      const doc = (_a = innerMostFormatNode.ownerDocument) !== null && _a !== void 0 ? _a : document;
      return appendNode(innerMostFormatNode, doc.createTextNode(ZWSP));
    };
    const cleanFormatNode = (editor, caretContainer, formatNode, name, vars, similar) => {
      const formatter = editor.formatter;
      const dom = editor.dom;
      const validFormats = filter$5(keys(formatter.get()), formatName => formatName !== name && !contains$1(formatName, 'removeformat'));
      const matchedFormats = matchAllOnNode(editor, formatNode, validFormats);
      const uniqueFormats = filter$5(matchedFormats, fmtName => !areSimilarFormats(editor, fmtName, name));
      if (uniqueFormats.length > 0) {
        const clonedFormatNode = formatNode.cloneNode(false);
        dom.add(caretContainer, clonedFormatNode);
        formatter.remove(name, vars, clonedFormatNode, similar);
        dom.remove(clonedFormatNode);
        return Optional.some(clonedFormatNode);
      } else {
        return Optional.none();
      }
    };
    const applyCaretFormat = (editor, name, vars) => {
      let caretContainer;
      const selection = editor.selection;
      const formatList = editor.formatter.get(name);
      if (!formatList) {
        return;
      }
      const selectionRng = selection.getRng();
      let offset = selectionRng.startOffset;
      const container = selectionRng.startContainer;
      const text = container.nodeValue;
      caretContainer = getParentCaretContainer(editor.getBody(), selection.getStart());
      const wordcharRegex = /[^\s\u00a0\u00ad\u200b\ufeff]/;
      if (text && offset > 0 && offset < text.length && wordcharRegex.test(text.charAt(offset)) && wordcharRegex.test(text.charAt(offset - 1))) {
        const bookmark = selection.getBookmark();
        selectionRng.collapse(true);
        let rng = expandRng(editor.dom, selectionRng, formatList);
        rng = split(rng);
        editor.formatter.apply(name, vars, rng);
        selection.moveToBookmark(bookmark);
      } else {
        let textNode = caretContainer ? findFirstTextNode(caretContainer) : null;
        if (!caretContainer || (textNode === null || textNode === void 0 ? void 0 : textNode.data) !== ZWSP) {
          caretContainer = importNode(editor.getDoc(), createCaretContainer(true).dom);
          textNode = caretContainer.firstChild;
          selectionRng.insertNode(caretContainer);
          offset = 1;
          editor.formatter.apply(name, vars, caretContainer);
        } else {
          editor.formatter.apply(name, vars, caretContainer);
        }
        selection.setCursorLocation(textNode, offset);
      }
    };
    const removeCaretFormat = (editor, name, vars, similar) => {
      const dom = editor.dom;
      const selection = editor.selection;
      let hasContentAfter = false;
      const formatList = editor.formatter.get(name);
      if (!formatList) {
        return;
      }
      const rng = selection.getRng();
      const container = rng.startContainer;
      const offset = rng.startOffset;
      let node = container;
      if (isText$a(container)) {
        if (offset !== container.data.length) {
          hasContentAfter = true;
        }
        node = node.parentNode;
      }
      const parents = [];
      let formatNode;
      while (node) {
        if (matchNode(editor, node, name, vars, similar)) {
          formatNode = node;
          break;
        }
        if (node.nextSibling) {
          hasContentAfter = true;
        }
        parents.push(node);
        node = node.parentNode;
      }
      if (!formatNode) {
        return;
      }
      if (hasContentAfter) {
        const bookmark = selection.getBookmark();
        rng.collapse(true);
        let expandedRng = expandRng(dom, rng, formatList, true);
        expandedRng = split(expandedRng);
        editor.formatter.remove(name, vars, expandedRng, similar);
        selection.moveToBookmark(bookmark);
      } else {
        const caretContainer = getParentCaretContainer(editor.getBody(), formatNode);
        const parentsAfter = isNonNullable(caretContainer) ? dom.getParents(formatNode.parentNode, always, caretContainer) : [];
        const newCaretContainer = createCaretContainer(false).dom;
        insertCaretContainerNode(editor, newCaretContainer, caretContainer !== null && caretContainer !== void 0 ? caretContainer : formatNode);
        const cleanedFormatNode = cleanFormatNode(editor, newCaretContainer, formatNode, name, vars, similar);
        const caretTextNode = insertFormatNodesIntoCaretContainer([
          ...parents,
          ...cleanedFormatNode.toArray(),
          ...parentsAfter
        ], newCaretContainer);
        if (caretContainer) {
          removeCaretContainerNode(editor, caretContainer, isNonNullable(caretContainer));
        }
        selection.setCursorLocation(caretTextNode, 1);
        if (dom.isEmpty(formatNode)) {
          dom.remove(formatNode);
        }
      }
    };
    const disableCaretContainer = (editor, keyCode, moveCaret) => {
      const selection = editor.selection, body = editor.getBody();
      removeCaretContainer(editor, null, moveCaret);
      if ((keyCode === 8 || keyCode === 46) && selection.isCollapsed() && selection.getStart().innerHTML === ZWSP) {
        removeCaretContainer(editor, getParentCaretContainer(body, selection.getStart()), true);
      }
      if (keyCode === 37 || keyCode === 39) {
        removeCaretContainer(editor, getParentCaretContainer(body, selection.getStart()), true);
      }
    };
    const endsWithNbsp = element => isText$a(element) && endsWith(element.data, nbsp);
    const setup$v = editor => {
      editor.on('mouseup keydown', e => {
        disableCaretContainer(editor, e.keyCode, endsWithNbsp(editor.selection.getRng().endContainer));
      });
    };
    const createCaretFormat = formatNodes => {
      const caretContainer = createCaretContainer(false);
      const innerMost = insertFormatNodesIntoCaretContainer(formatNodes, caretContainer.dom);
      return {
        caretContainer,
        caretPosition: CaretPosition(innerMost, 0)
      };
    };
    const replaceWithCaretFormat = (targetNode, formatNodes) => {
      const {caretContainer, caretPosition} = createCaretFormat(formatNodes);
      before$3(SugarElement.fromDom(targetNode), caretContainer);
      remove$5(SugarElement.fromDom(targetNode));
      return caretPosition;
    };
    const createCaretFormatAtStart$1 = (rng, formatNodes) => {
      const {caretContainer, caretPosition} = createCaretFormat(formatNodes);
      rng.insertNode(caretContainer.dom);
      return caretPosition;
    };
    const isFormatElement = (editor, element) => {
      if (isCaretNode(element.dom)) {
        return false;
      }
      const inlineElements = editor.schema.getTextInlineElements();
      return has$2(inlineElements, name(element)) && !isCaretNode(element.dom) && !isBogus$2(element.dom);
    };

    const postProcessHooks = {};
    const isPre = matchNodeNames(['pre']);
    const addPostProcessHook = (name, hook) => {
      const hooks = postProcessHooks[name];
      if (!hooks) {
        postProcessHooks[name] = [];
      }
      postProcessHooks[name].push(hook);
    };
    const postProcess$1 = (name, editor) => {
      if (has$2(postProcessHooks, name)) {
        each$e(postProcessHooks[name], hook => {
          hook(editor);
        });
      }
    };
    addPostProcessHook('pre', editor => {
      const rng = editor.selection.getRng();
      const hasPreSibling = blocks => pre => {
        const prev = pre.previousSibling;
        return isPre(prev) && contains$2(blocks, prev);
      };
      const joinPre = (pre1, pre2) => {
        const sPre2 = SugarElement.fromDom(pre2);
        const doc = documentOrOwner(sPre2).dom;
        remove$5(sPre2);
        append(SugarElement.fromDom(pre1), [
          SugarElement.fromTag('br', doc),
          SugarElement.fromTag('br', doc),
          ...children$1(sPre2)
        ]);
      };
      if (!rng.collapsed) {
        const blocks = editor.selection.getSelectedBlocks();
        const preBlocks = filter$5(filter$5(blocks, isPre), hasPreSibling(blocks));
        each$e(preBlocks, pre => {
          joinPre(pre.previousSibling, pre);
        });
      }
    });

    const listItemStyles = [
      'fontWeight',
      'fontStyle',
      'color',
      'fontSize',
      'fontFamily'
    ];
    const hasListStyles = fmt => isObject(fmt.styles) && exists(keys(fmt.styles), name => contains$2(listItemStyles, name));
    const findExpandedListItemFormat = formats => find$2(formats, fmt => isInlineFormat(fmt) && fmt.inline === 'span' && hasListStyles(fmt));
    const getExpandedListItemFormat = (formatter, format) => {
      const formatList = formatter.get(format);
      return isArray$1(formatList) ? findExpandedListItemFormat(formatList) : Optional.none();
    };
    const isRngStartAtStartOfElement = (rng, elm) => prevPosition(elm, CaretPosition.fromRangeStart(rng)).isNone();
    const isRngEndAtEndOfElement = (rng, elm) => {
      return nextPosition(elm, CaretPosition.fromRangeEnd(rng)).exists(pos => !isBr$6(pos.getNode()) || nextPosition(elm, pos).isSome()) === false;
    };
    const isEditableListItem = dom => elm => isListItem$2(elm) && dom.isEditable(elm);
    const getFullySelectedBlocks = selection => {
      const blocks = selection.getSelectedBlocks();
      const rng = selection.getRng();
      if (selection.isCollapsed()) {
        return [];
      }
      if (blocks.length === 1) {
        return isRngStartAtStartOfElement(rng, blocks[0]) && isRngEndAtEndOfElement(rng, blocks[0]) ? blocks : [];
      } else {
        const first = head(blocks).filter(elm => isRngStartAtStartOfElement(rng, elm)).toArray();
        const last = last$3(blocks).filter(elm => isRngEndAtEndOfElement(rng, elm)).toArray();
        const middle = blocks.slice(1, -1);
        return first.concat(middle).concat(last);
      }
    };
    const getFullySelectedListItems = selection => filter$5(getFullySelectedBlocks(selection), isEditableListItem(selection.dom));
    const getPartiallySelectedListItems = selection => filter$5(selection.getSelectedBlocks(), isEditableListItem(selection.dom));

    const each$8 = Tools.each;
    const isElementNode = node => isElement$6(node) && !isBookmarkNode$1(node) && !isCaretNode(node) && !isBogus$2(node);
    const findElementSibling = (node, siblingName) => {
      for (let sibling = node; sibling; sibling = sibling[siblingName]) {
        if (isText$a(sibling) && isNotEmpty(sibling.data)) {
          return node;
        }
        if (isElement$6(sibling) && !isBookmarkNode$1(sibling)) {
          return sibling;
        }
      }
      return node;
    };
    const mergeSiblingsNodes = (editor, prev, next) => {
      const elementUtils = ElementUtils(editor);
      const isPrevEditable = isHTMLElement(prev) && editor.dom.isEditable(prev);
      const isNextEditable = isHTMLElement(next) && editor.dom.isEditable(next);
      if (isPrevEditable && isNextEditable) {
        const prevSibling = findElementSibling(prev, 'previousSibling');
        const nextSibling = findElementSibling(next, 'nextSibling');
        if (elementUtils.compare(prevSibling, nextSibling)) {
          for (let sibling = prevSibling.nextSibling; sibling && sibling !== nextSibling;) {
            const tmpSibling = sibling;
            sibling = sibling.nextSibling;
            prevSibling.appendChild(tmpSibling);
          }
          editor.dom.remove(nextSibling);
          Tools.each(Tools.grep(nextSibling.childNodes), node => {
            prevSibling.appendChild(node);
          });
          return prevSibling;
        }
      }
      return next;
    };
    const mergeSiblings = (editor, format, vars, node) => {
      var _a;
      if (node && format.merge_siblings !== false) {
        const newNode = (_a = mergeSiblingsNodes(editor, getNonWhiteSpaceSibling(node), node)) !== null && _a !== void 0 ? _a : node;
        mergeSiblingsNodes(editor, newNode, getNonWhiteSpaceSibling(newNode, true));
      }
    };
    const clearChildStyles = (dom, format, node) => {
      if (format.clear_child_styles) {
        const selector = format.links ? '*:not(a)' : '*';
        each$8(dom.select(selector, node), childNode => {
          if (isElementNode(childNode) && dom.isEditable(childNode)) {
            each$8(format.styles, (_value, name) => {
              dom.setStyle(childNode, name, '');
            });
          }
        });
      }
    };
    const processChildElements = (node, filter, process) => {
      each$8(node.childNodes, node => {
        if (isElementNode(node)) {
          if (filter(node)) {
            process(node);
          }
          if (node.hasChildNodes()) {
            processChildElements(node, filter, process);
          }
        }
      });
    };
    const unwrapEmptySpan = (dom, node) => {
      if (node.nodeName === 'SPAN' && dom.getAttribs(node).length === 0) {
        dom.remove(node, true);
      }
    };
    const hasStyle = (dom, name) => node => !!(node && getStyle(dom, node, name));
    const applyStyle = (dom, name, value) => node => {
      dom.setStyle(node, name, value);
      if (node.getAttribute('style') === '') {
        node.removeAttribute('style');
      }
      unwrapEmptySpan(dom, node);
    };

    const removeResult = Adt.generate([
      { keep: [] },
      { rename: ['name'] },
      { removed: [] }
    ]);
    const MCE_ATTR_RE = /^(src|href|style)$/;
    const each$7 = Tools.each;
    const isEq$2 = isEq$5;
    const isTableCellOrRow = node => /^(TR|TH|TD)$/.test(node.nodeName);
    const isChildOfInlineParent = (dom, node, parent) => dom.isChildOf(node, parent) && node !== parent && !dom.isBlock(parent);
    const getContainer = (ed, rng, start) => {
      let container = rng[start ? 'startContainer' : 'endContainer'];
      let offset = rng[start ? 'startOffset' : 'endOffset'];
      if (isElement$6(container)) {
        const lastIdx = container.childNodes.length - 1;
        if (!start && offset) {
          offset--;
        }
        container = container.childNodes[offset > lastIdx ? lastIdx : offset];
      }
      if (isText$a(container) && start && offset >= container.data.length) {
        container = new DomTreeWalker(container, ed.getBody()).next() || container;
      }
      if (isText$a(container) && !start && offset === 0) {
        container = new DomTreeWalker(container, ed.getBody()).prev() || container;
      }
      return container;
    };
    const normalizeTableSelection = (node, start) => {
      const prop = start ? 'firstChild' : 'lastChild';
      const childNode = node[prop];
      if (isTableCellOrRow(node) && childNode) {
        if (node.nodeName === 'TR') {
          return childNode[prop] || childNode;
        } else {
          return childNode;
        }
      }
      return node;
    };
    const wrap$1 = (dom, node, name, attrs) => {
      var _a;
      const wrapper = dom.create(name, attrs);
      (_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(wrapper, node);
      wrapper.appendChild(node);
      return wrapper;
    };
    const wrapWithSiblings = (dom, node, next, name, attrs) => {
      const start = SugarElement.fromDom(node);
      const wrapper = SugarElement.fromDom(dom.create(name, attrs));
      const siblings = next ? nextSiblings(start) : prevSiblings(start);
      append(wrapper, siblings);
      if (next) {
        before$3(start, wrapper);
        prepend(wrapper, start);
      } else {
        after$4(start, wrapper);
        append$1(wrapper, start);
      }
      return wrapper.dom;
    };
    const isColorFormatAndAnchor = (node, format) => format.links && node.nodeName === 'A';
    const removeNode = (ed, node, format) => {
      const parentNode = node.parentNode;
      let rootBlockElm;
      const dom = ed.dom;
      const forcedRootBlock = getForcedRootBlock(ed);
      if (isBlockFormat(format)) {
        if (parentNode === dom.getRoot()) {
          if (!format.list_block || !isEq$2(node, format.list_block)) {
            each$e(from(node.childNodes), node => {
              if (isValid(ed, forcedRootBlock, node.nodeName.toLowerCase())) {
                if (!rootBlockElm) {
                  rootBlockElm = wrap$1(dom, node, forcedRootBlock);
                  dom.setAttribs(rootBlockElm, getForcedRootBlockAttrs(ed));
                } else {
                  rootBlockElm.appendChild(node);
                }
              } else {
                rootBlockElm = null;
              }
            });
          }
        }
      }
      if (isMixedFormat(format) && !isEq$2(format.inline, node)) {
        return;
      }
      dom.remove(node, true);
    };
    const processFormatAttrOrStyle = (name, value, vars) => {
      if (isNumber(name)) {
        return {
          name: value,
          value: null
        };
      } else {
        return {
          name,
          value: replaceVars(value, vars)
        };
      }
    };
    const removeEmptyStyleAttributeIfNeeded = (dom, elm) => {
      if (dom.getAttrib(elm, 'style') === '') {
        elm.removeAttribute('style');
        elm.removeAttribute('data-mce-style');
      }
    };
    const removeStyles = (dom, elm, format, vars, compareNode) => {
      let stylesModified = false;
      each$7(format.styles, (value, name) => {
        const {
          name: styleName,
          value: styleValue
        } = processFormatAttrOrStyle(name, value, vars);
        const normalizedStyleValue = normalizeStyleValue(styleValue, styleName);
        if (format.remove_similar || isNull(styleValue) || !isElement$6(compareNode) || isEq$2(getStyle(dom, compareNode, styleName), normalizedStyleValue)) {
          dom.setStyle(elm, styleName, '');
        }
        stylesModified = true;
      });
      if (stylesModified) {
        removeEmptyStyleAttributeIfNeeded(dom, elm);
      }
    };
    const removeListStyleFormats = (editor, name, vars) => {
      if (name === 'removeformat') {
        each$e(getPartiallySelectedListItems(editor.selection), li => {
          each$e(listItemStyles, name => editor.dom.setStyle(li, name, ''));
          removeEmptyStyleAttributeIfNeeded(editor.dom, li);
        });
      } else {
        getExpandedListItemFormat(editor.formatter, name).each(liFmt => {
          each$e(getPartiallySelectedListItems(editor.selection), li => removeStyles(editor.dom, li, liFmt, vars, null));
        });
      }
    };
    const removeNodeFormatInternal = (ed, format, vars, node, compareNode) => {
      const dom = ed.dom;
      const elementUtils = ElementUtils(ed);
      const schema = ed.schema;
      if (isInlineFormat(format) && isTransparentElementName(schema, format.inline) && isTransparentBlock(schema, node) && node.parentElement === ed.getBody()) {
        removeNode(ed, node, format);
        return removeResult.removed();
      }
      if (!format.ceFalseOverride && node && dom.getContentEditableParent(node) === 'false') {
        return removeResult.keep();
      }
      if (node && !matchName(dom, node, format) && !isColorFormatAndAnchor(node, format)) {
        return removeResult.keep();
      }
      const elm = node;
      const preserveAttributes = format.preserve_attributes;
      if (isInlineFormat(format) && format.remove === 'all' && isArray$1(preserveAttributes)) {
        const attrsToPreserve = filter$5(dom.getAttribs(elm), attr => contains$2(preserveAttributes, attr.name.toLowerCase()));
        dom.removeAllAttribs(elm);
        each$e(attrsToPreserve, attr => dom.setAttrib(elm, attr.name, attr.value));
        if (attrsToPreserve.length > 0) {
          return removeResult.rename('span');
        }
      }
      if (format.remove !== 'all') {
        removeStyles(dom, elm, format, vars, compareNode);
        each$7(format.attributes, (value, name) => {
          const {
            name: attrName,
            value: attrValue
          } = processFormatAttrOrStyle(name, value, vars);
          if (format.remove_similar || isNull(attrValue) || !isElement$6(compareNode) || isEq$2(dom.getAttrib(compareNode, attrName), attrValue)) {
            if (attrName === 'class') {
              const currentValue = dom.getAttrib(elm, attrName);
              if (currentValue) {
                let valueOut = '';
                each$e(currentValue.split(/\s+/), cls => {
                  if (/mce\-\w+/.test(cls)) {
                    valueOut += (valueOut ? ' ' : '') + cls;
                  }
                });
                if (valueOut) {
                  dom.setAttrib(elm, attrName, valueOut);
                  return;
                }
              }
            }
            if (MCE_ATTR_RE.test(attrName)) {
              elm.removeAttribute('data-mce-' + attrName);
            }
            if (attrName === 'style' && matchNodeNames(['li'])(elm) && dom.getStyle(elm, 'list-style-type') === 'none') {
              elm.removeAttribute(attrName);
              dom.setStyle(elm, 'list-style-type', 'none');
              return;
            }
            if (attrName === 'class') {
              elm.removeAttribute('className');
            }
            elm.removeAttribute(attrName);
          }
        });
        each$7(format.classes, value => {
          value = replaceVars(value, vars);
          if (!isElement$6(compareNode) || dom.hasClass(compareNode, value)) {
            dom.removeClass(elm, value);
          }
        });
        const attrs = dom.getAttribs(elm);
        for (let i = 0; i < attrs.length; i++) {
          const attrName = attrs[i].nodeName;
          if (!elementUtils.isAttributeInternal(attrName)) {
            return removeResult.keep();
          }
        }
      }
      if (format.remove !== 'none') {
        removeNode(ed, elm, format);
        return removeResult.removed();
      }
      return removeResult.keep();
    };
    const findFormatRoot = (editor, container, name, vars, similar) => {
      let formatRoot;
      if (container.parentNode) {
        each$e(getParents$2(editor.dom, container.parentNode).reverse(), parent => {
          if (!formatRoot && isElement$6(parent) && parent.id !== '_start' && parent.id !== '_end') {
            const format = matchNode(editor, parent, name, vars, similar);
            if (format && format.split !== false) {
              formatRoot = parent;
            }
          }
        });
      }
      return formatRoot;
    };
    const removeNodeFormatFromClone = (editor, format, vars, clone) => removeNodeFormatInternal(editor, format, vars, clone, clone).fold(constant(clone), newName => {
      const fragment = editor.dom.createFragment();
      fragment.appendChild(clone);
      return editor.dom.rename(clone, newName);
    }, constant(null));
    const wrapAndSplit = (editor, formatList, formatRoot, container, target, split, format, vars) => {
      var _a, _b;
      let lastClone;
      let firstClone;
      const dom = editor.dom;
      if (formatRoot) {
        const formatRootParent = formatRoot.parentNode;
        for (let parent = container.parentNode; parent && parent !== formatRootParent; parent = parent.parentNode) {
          let clone = dom.clone(parent, false);
          for (let i = 0; i < formatList.length; i++) {
            clone = removeNodeFormatFromClone(editor, formatList[i], vars, clone);
            if (clone === null) {
              break;
            }
          }
          if (clone) {
            if (lastClone) {
              clone.appendChild(lastClone);
            }
            if (!firstClone) {
              firstClone = clone;
            }
            lastClone = clone;
          }
        }
        if (split && (!format.mixed || !dom.isBlock(formatRoot))) {
          container = (_a = dom.split(formatRoot, container)) !== null && _a !== void 0 ? _a : container;
        }
        if (lastClone && firstClone) {
          (_b = target.parentNode) === null || _b === void 0 ? void 0 : _b.insertBefore(lastClone, target);
          firstClone.appendChild(target);
          if (isInlineFormat(format)) {
            mergeSiblings(editor, format, vars, lastClone);
          }
        }
      }
      return container;
    };
    const removeFormatInternal = (ed, name, vars, node, similar) => {
      const formatList = ed.formatter.get(name);
      const format = formatList[0];
      const dom = ed.dom;
      const selection = ed.selection;
      const splitToFormatRoot = container => {
        const formatRoot = findFormatRoot(ed, container, name, vars, similar);
        return wrapAndSplit(ed, formatList, formatRoot, container, container, true, format, vars);
      };
      const isRemoveBookmarkNode = node => isBookmarkNode$1(node) && isElement$6(node) && (node.id === '_start' || node.id === '_end');
      const removeFormatOnNode = node => exists(formatList, fmt => removeNodeFormat(ed, fmt, vars, node, node));
      const process = node => {
        const children = from(node.childNodes);
        const removed = removeFormatOnNode(node);
        const currentNodeMatches = removed || exists(formatList, f => matchName(dom, node, f));
        const parentNode = node.parentNode;
        if (!currentNodeMatches && isNonNullable(parentNode) && shouldExpandToSelector(format)) {
          removeFormatOnNode(parentNode);
        }
        if (format.deep) {
          if (children.length) {
            for (let i = 0; i < children.length; i++) {
              process(children[i]);
            }
          }
        }
        const textDecorations = [
          'underline',
          'line-through',
          'overline'
        ];
        each$e(textDecorations, decoration => {
          if (isElement$6(node) && ed.dom.getStyle(node, 'text-decoration') === decoration && node.parentNode && getTextDecoration(dom, node.parentNode) === decoration) {
            removeNodeFormat(ed, {
              deep: false,
              exact: true,
              inline: 'span',
              styles: { textDecoration: decoration }
            }, undefined, node);
          }
        });
      };
      const unwrap = start => {
        const node = dom.get(start ? '_start' : '_end');
        if (node) {
          let out = node[start ? 'firstChild' : 'lastChild'];
          if (isRemoveBookmarkNode(out)) {
            out = out[start ? 'firstChild' : 'lastChild'];
          }
          if (isText$a(out) && out.data.length === 0) {
            out = start ? node.previousSibling || node.nextSibling : node.nextSibling || node.previousSibling;
          }
          dom.remove(node, true);
          return out;
        } else {
          return null;
        }
      };
      const removeRngStyle = rng => {
        let startContainer;
        let endContainer;
        let expandedRng = expandRng(dom, rng, formatList, rng.collapsed);
        if (format.split) {
          expandedRng = split(expandedRng);
          startContainer = getContainer(ed, expandedRng, true);
          endContainer = getContainer(ed, expandedRng);
          if (startContainer !== endContainer) {
            startContainer = normalizeTableSelection(startContainer, true);
            endContainer = normalizeTableSelection(endContainer, false);
            if (isChildOfInlineParent(dom, startContainer, endContainer)) {
              const marker = Optional.from(startContainer.firstChild).getOr(startContainer);
              splitToFormatRoot(wrapWithSiblings(dom, marker, true, 'span', {
                'id': '_start',
                'data-mce-type': 'bookmark'
              }));
              unwrap(true);
              return;
            }
            if (isChildOfInlineParent(dom, endContainer, startContainer)) {
              const marker = Optional.from(endContainer.lastChild).getOr(endContainer);
              splitToFormatRoot(wrapWithSiblings(dom, marker, false, 'span', {
                'id': '_end',
                'data-mce-type': 'bookmark'
              }));
              unwrap(false);
              return;
            }
            startContainer = wrap$1(dom, startContainer, 'span', {
              'id': '_start',
              'data-mce-type': 'bookmark'
            });
            endContainer = wrap$1(dom, endContainer, 'span', {
              'id': '_end',
              'data-mce-type': 'bookmark'
            });
            const newRng = dom.createRng();
            newRng.setStartAfter(startContainer);
            newRng.setEndBefore(endContainer);
            walk$3(dom, newRng, nodes => {
              each$e(nodes, n => {
                if (!isBookmarkNode$1(n) && !isBookmarkNode$1(n.parentNode)) {
                  splitToFormatRoot(n);
                }
              });
            });
            splitToFormatRoot(startContainer);
            splitToFormatRoot(endContainer);
            startContainer = unwrap(true);
            endContainer = unwrap();
          } else {
            startContainer = endContainer = splitToFormatRoot(startContainer);
          }
          expandedRng.startContainer = startContainer.parentNode ? startContainer.parentNode : startContainer;
          expandedRng.startOffset = dom.nodeIndex(startContainer);
          expandedRng.endContainer = endContainer.parentNode ? endContainer.parentNode : endContainer;
          expandedRng.endOffset = dom.nodeIndex(endContainer) + 1;
        }
        walk$3(dom, expandedRng, nodes => {
          each$e(nodes, process);
        });
      };
      if (node) {
        if (isNode(node)) {
          const rng = dom.createRng();
          rng.setStartBefore(node);
          rng.setEndAfter(node);
          removeRngStyle(rng);
        } else {
          removeRngStyle(node);
        }
        fireFormatRemove(ed, name, node, vars);
        return;
      }
      if (!selection.isCollapsed() || !isInlineFormat(format) || getCellsFromEditor(ed).length) {
        preserveSelection(ed, () => runOnRanges(ed, removeRngStyle), startNode => isInlineFormat(format) && match$2(ed, name, vars, startNode));
        ed.nodeChanged();
      } else {
        removeCaretFormat(ed, name, vars, similar);
      }
      removeListStyleFormats(ed, name, vars);
      fireFormatRemove(ed, name, node, vars);
    };
    const removeFormat$1 = (ed, name, vars, node, similar) => {
      if (node || ed.selection.isEditable()) {
        removeFormatInternal(ed, name, vars, node, similar);
      }
    };
    const removeNodeFormat = (editor, format, vars, node, compareNode) => {
      return removeNodeFormatInternal(editor, format, vars, node, compareNode).fold(never, newName => {
        editor.dom.rename(node, newName);
        return true;
      }, always);
    };

    const each$6 = Tools.each;
    const mergeTextDecorationsAndColor = (dom, format, vars, node) => {
      const processTextDecorationsAndColor = n => {
        if (isHTMLElement(n) && isElement$6(n.parentNode) && dom.isEditable(n)) {
          const parentTextDecoration = getTextDecoration(dom, n.parentNode);
          if (dom.getStyle(n, 'color') && parentTextDecoration) {
            dom.setStyle(n, 'text-decoration', parentTextDecoration);
          } else if (dom.getStyle(n, 'text-decoration') === parentTextDecoration) {
            dom.setStyle(n, 'text-decoration', null);
          }
        }
      };
      if (format.styles && (format.styles.color || format.styles.textDecoration)) {
        Tools.walk(node, processTextDecorationsAndColor, 'childNodes');
        processTextDecorationsAndColor(node);
      }
    };
    const mergeBackgroundColorAndFontSize = (dom, format, vars, node) => {
      if (format.styles && format.styles.backgroundColor) {
        const hasFontSize = hasStyle(dom, 'fontSize');
        processChildElements(node, elm => hasFontSize(elm) && dom.isEditable(elm), applyStyle(dom, 'backgroundColor', replaceVars(format.styles.backgroundColor, vars)));
      }
    };
    const mergeSubSup = (dom, format, vars, node) => {
      if (isInlineFormat(format) && (format.inline === 'sub' || format.inline === 'sup')) {
        const hasFontSize = hasStyle(dom, 'fontSize');
        processChildElements(node, elm => hasFontSize(elm) && dom.isEditable(elm), applyStyle(dom, 'fontSize', ''));
        const inverseTagDescendants = filter$5(dom.select(format.inline === 'sup' ? 'sub' : 'sup', node), dom.isEditable);
        dom.remove(inverseTagDescendants, true);
      }
    };
    const mergeWithChildren = (editor, formatList, vars, node) => {
      each$6(formatList, format => {
        if (isInlineFormat(format)) {
          each$6(editor.dom.select(format.inline, node), child => {
            if (isElementNode(child)) {
              removeNodeFormat(editor, format, vars, child, format.exact ? child : null);
            }
          });
        }
        clearChildStyles(editor.dom, format, node);
      });
    };
    const mergeWithParents = (editor, format, name, vars, node) => {
      const parentNode = node.parentNode;
      if (matchNode(editor, parentNode, name, vars)) {
        if (removeNodeFormat(editor, format, vars, node)) {
          return;
        }
      }
      if (format.merge_with_parents && parentNode) {
        editor.dom.getParent(parentNode, parent => {
          if (matchNode(editor, parent, name, vars)) {
            removeNodeFormat(editor, format, vars, node);
            return true;
          } else {
            return false;
          }
        });
      }
    };

    const each$5 = Tools.each;
    const canFormatBR = (editor, format, node, parentName) => {
      if (canFormatEmptyLines(editor) && isInlineFormat(format) && node.parentNode) {
        const validBRParentElements = getTextRootBlockElements(editor.schema);
        const hasCaretNodeSibling = sibling(SugarElement.fromDom(node), sibling => isCaretNode(sibling.dom));
        return hasNonNullableKey(validBRParentElements, parentName) && isEmpty$2(SugarElement.fromDom(node.parentNode), false) && !hasCaretNodeSibling;
      } else {
        return false;
      }
    };
    const applyStyles = (dom, elm, format, vars) => {
      each$5(format.styles, (value, name) => {
        dom.setStyle(elm, name, replaceVars(value, vars));
      });
      if (format.styles) {
        const styleVal = dom.getAttrib(elm, 'style');
        if (styleVal) {
          dom.setAttrib(elm, 'data-mce-style', styleVal);
        }
      }
    };
    const applyFormatAction = (ed, name, vars, node) => {
      const formatList = ed.formatter.get(name);
      const format = formatList[0];
      const isCollapsed = !node && ed.selection.isCollapsed();
      const dom = ed.dom;
      const selection = ed.selection;
      const setElementFormat = (elm, fmt = format) => {
        if (isFunction(fmt.onformat)) {
          fmt.onformat(elm, fmt, vars, node);
        }
        applyStyles(dom, elm, fmt, vars);
        each$5(fmt.attributes, (value, name) => {
          dom.setAttrib(elm, name, replaceVars(value, vars));
        });
        each$5(fmt.classes, value => {
          const newValue = replaceVars(value, vars);
          if (!dom.hasClass(elm, newValue)) {
            dom.addClass(elm, newValue);
          }
        });
      };
      const applyNodeStyle = (formatList, node) => {
        let found = false;
        each$5(formatList, format => {
          if (!isSelectorFormat(format)) {
            return false;
          }
          if (dom.getContentEditable(node) === 'false' && !format.ceFalseOverride) {
            return true;
          }
          if (isNonNullable(format.collapsed) && format.collapsed !== isCollapsed) {
            return true;
          }
          if (dom.is(node, format.selector) && !isCaretNode(node)) {
            setElementFormat(node, format);
            found = true;
            return false;
          }
          return true;
        });
        return found;
      };
      const createWrapElement = wrapName => {
        if (isString(wrapName)) {
          const wrapElm = dom.create(wrapName);
          setElementFormat(wrapElm);
          return wrapElm;
        } else {
          return null;
        }
      };
      const applyRngStyle = (dom, rng, nodeSpecific) => {
        const newWrappers = [];
        let contentEditable = true;
        const wrapName = format.inline || format.block;
        const wrapElm = createWrapElement(wrapName);
        const isMatchingWrappingBlock = node => isWrappingBlockFormat(format) && matchNode(ed, node, name, vars);
        const canRenameBlock = (node, parentName, isEditableDescendant) => {
          const isValidBlockFormatForNode = isNonWrappingBlockFormat(format) && isTextBlock$1(ed.schema, node) && isValid(ed, parentName, wrapName);
          return isEditableDescendant && isValidBlockFormatForNode;
        };
        const canWrapNode = (node, parentName, isEditableDescendant, isWrappableNoneditableElm) => {
          const nodeName = node.nodeName.toLowerCase();
          const isValidWrapNode = isValid(ed, wrapName, nodeName) && isValid(ed, parentName, wrapName);
          const isZwsp = !nodeSpecific && isText$a(node) && isZwsp$1(node.data);
          const isCaret = isCaretNode(node);
          const isCorrectFormatForNode = !isInlineFormat(format) || !dom.isBlock(node);
          return (isEditableDescendant || isWrappableNoneditableElm) && isValidWrapNode && !isZwsp && !isCaret && isCorrectFormatForNode;
        };
        walk$3(dom, rng, nodes => {
          let currentWrapElm;
          const process = node => {
            let hasContentEditableState = false;
            let lastContentEditable = contentEditable;
            let isWrappableNoneditableElm = false;
            const parentNode = node.parentNode;
            const parentName = parentNode.nodeName.toLowerCase();
            const contentEditableValue = dom.getContentEditable(node);
            if (isNonNullable(contentEditableValue)) {
              lastContentEditable = contentEditable;
              contentEditable = contentEditableValue === 'true';
              hasContentEditableState = true;
              isWrappableNoneditableElm = isWrappableNoneditable(ed, node);
            }
            const isEditableDescendant = contentEditable && !hasContentEditableState;
            if (isBr$6(node) && !canFormatBR(ed, format, node, parentName)) {
              currentWrapElm = null;
              if (isBlockFormat(format)) {
                dom.remove(node);
              }
              return;
            }
            if (isMatchingWrappingBlock(node)) {
              currentWrapElm = null;
              return;
            }
            if (canRenameBlock(node, parentName, isEditableDescendant)) {
              const elm = dom.rename(node, wrapName);
              setElementFormat(elm);
              newWrappers.push(elm);
              currentWrapElm = null;
              return;
            }
            if (isSelectorFormat(format)) {
              let found = applyNodeStyle(formatList, node);
              if (!found && isNonNullable(parentNode) && shouldExpandToSelector(format)) {
                found = applyNodeStyle(formatList, parentNode);
              }
              if (!isInlineFormat(format) || found) {
                currentWrapElm = null;
                return;
              }
            }
            if (isNonNullable(wrapElm) && canWrapNode(node, parentName, isEditableDescendant, isWrappableNoneditableElm)) {
              if (!currentWrapElm) {
                currentWrapElm = dom.clone(wrapElm, false);
                parentNode.insertBefore(currentWrapElm, node);
                newWrappers.push(currentWrapElm);
              }
              if (isWrappableNoneditableElm && hasContentEditableState) {
                contentEditable = lastContentEditable;
              }
              currentWrapElm.appendChild(node);
            } else {
              currentWrapElm = null;
              each$e(from(node.childNodes), process);
              if (hasContentEditableState) {
                contentEditable = lastContentEditable;
              }
              currentWrapElm = null;
            }
          };
          each$e(nodes, process);
        });
        if (format.links === true) {
          each$e(newWrappers, node => {
            const process = node => {
              if (node.nodeName === 'A') {
                setElementFormat(node, format);
              }
              each$e(from(node.childNodes), process);
            };
            process(node);
          });
        }
        each$e(newWrappers, node => {
          const getChildCount = node => {
            let count = 0;
            each$e(node.childNodes, node => {
              if (!isEmptyTextNode$1(node) && !isBookmarkNode$1(node)) {
                count++;
              }
            });
            return count;
          };
          const mergeStyles = node => {
            const childElement = find$2(node.childNodes, isElementNode$1).filter(child => dom.getContentEditable(child) !== 'false' && matchName(dom, child, format));
            return childElement.map(child => {
              const clone = dom.clone(child, false);
              setElementFormat(clone);
              dom.replace(clone, node, true);
              dom.remove(child, true);
              return clone;
            }).getOr(node);
          };
          const childCount = getChildCount(node);
          if ((newWrappers.length > 1 || !dom.isBlock(node)) && childCount === 0) {
            dom.remove(node, true);
            return;
          }
          if (isInlineFormat(format) || isBlockFormat(format) && format.wrapper) {
            if (!format.exact && childCount === 1) {
              node = mergeStyles(node);
            }
            mergeWithChildren(ed, formatList, vars, node);
            mergeWithParents(ed, format, name, vars, node);
            mergeBackgroundColorAndFontSize(dom, format, vars, node);
            mergeTextDecorationsAndColor(dom, format, vars, node);
            mergeSubSup(dom, format, vars, node);
            mergeSiblings(ed, format, vars, node);
          }
        });
      };
      const targetNode = isNode(node) ? node : selection.getNode();
      if (dom.getContentEditable(targetNode) === 'false' && !isWrappableNoneditable(ed, targetNode)) {
        node = targetNode;
        applyNodeStyle(formatList, node);
        fireFormatApply(ed, name, node, vars);
        return;
      }
      if (format) {
        if (node) {
          if (isNode(node)) {
            if (!applyNodeStyle(formatList, node)) {
              const rng = dom.createRng();
              rng.setStartBefore(node);
              rng.setEndAfter(node);
              applyRngStyle(dom, expandRng(dom, rng, formatList), true);
            }
          } else {
            applyRngStyle(dom, node, true);
          }
        } else {
          if (!isCollapsed || !isInlineFormat(format) || getCellsFromEditor(ed).length) {
            selection.setRng(normalize(selection.getRng()));
            preserveSelection(ed, () => {
              runOnRanges(ed, (selectionRng, fake) => {
                const expandedRng = fake ? selectionRng : expandRng(dom, selectionRng, formatList);
                applyRngStyle(dom, expandedRng, false);
              });
            }, always);
            ed.nodeChanged();
          } else {
            applyCaretFormat(ed, name, vars);
          }
          getExpandedListItemFormat(ed.formatter, name).each(liFmt => {
            each$e(getFullySelectedListItems(ed.selection), li => applyStyles(dom, li, liFmt, vars));
          });
        }
        postProcess$1(name, ed);
      }
      fireFormatApply(ed, name, node, vars);
    };
    const applyFormat$1 = (editor, name, vars, node) => {
      if (node || editor.selection.isEditable()) {
        applyFormatAction(editor, name, vars, node);
      }
    };

    const hasVars = value => has$2(value, 'vars');
    const setup$u = (registeredFormatListeners, editor) => {
      registeredFormatListeners.set({});
      editor.on('NodeChange', e => {
        updateAndFireChangeCallbacks(editor, e.element, registeredFormatListeners.get());
      });
      editor.on('FormatApply FormatRemove', e => {
        const element = Optional.from(e.node).map(nodeOrRange => isNode(nodeOrRange) ? nodeOrRange : nodeOrRange.startContainer).bind(node => isElement$6(node) ? Optional.some(node) : Optional.from(node.parentElement)).getOrThunk(() => fallbackElement(editor));
        updateAndFireChangeCallbacks(editor, element, registeredFormatListeners.get());
      });
    };
    const fallbackElement = editor => editor.selection.getStart();
    const matchingNode = (editor, parents, format, similar, vars) => {
      const isMatchingNode = node => {
        const matchingFormat = editor.formatter.matchNode(node, format, vars !== null && vars !== void 0 ? vars : {}, similar);
        return !isUndefined(matchingFormat);
      };
      const isUnableToMatch = node => {
        if (matchesUnInheritedFormatSelector(editor, node, format)) {
          return true;
        } else {
          if (!similar) {
            return isNonNullable(editor.formatter.matchNode(node, format, vars, true));
          } else {
            return false;
          }
        }
      };
      return findUntil$1(parents, isMatchingNode, isUnableToMatch);
    };
    const getParents = (editor, elm) => {
      const element = elm !== null && elm !== void 0 ? elm : fallbackElement(editor);
      return filter$5(getParents$2(editor.dom, element), node => isElement$6(node) && !isBogus$2(node));
    };
    const updateAndFireChangeCallbacks = (editor, elm, registeredCallbacks) => {
      const parents = getParents(editor, elm);
      each$d(registeredCallbacks, (data, format) => {
        const runIfChanged = spec => {
          const match = matchingNode(editor, parents, format, spec.similar, hasVars(spec) ? spec.vars : undefined);
          const isSet = match.isSome();
          if (spec.state.get() !== isSet) {
            spec.state.set(isSet);
            const node = match.getOr(elm);
            if (hasVars(spec)) {
              spec.callback(isSet, {
                node,
                format,
                parents
              });
            } else {
              each$e(spec.callbacks, callback => callback(isSet, {
                node,
                format,
                parents
              }));
            }
          }
        };
        each$e([
          data.withSimilar,
          data.withoutSimilar
        ], runIfChanged);
        each$e(data.withVars, runIfChanged);
      });
    };
    const addListeners = (editor, registeredFormatListeners, formats, callback, similar, vars) => {
      const formatChangeItems = registeredFormatListeners.get();
      each$e(formats.split(','), format => {
        const group = get$a(formatChangeItems, format).getOrThunk(() => {
          const base = {
            withSimilar: {
              state: Cell(false),
              similar: true,
              callbacks: []
            },
            withoutSimilar: {
              state: Cell(false),
              similar: false,
              callbacks: []
            },
            withVars: []
          };
          formatChangeItems[format] = base;
          return base;
        });
        const getCurrent = () => {
          const parents = getParents(editor);
          return matchingNode(editor, parents, format, similar, vars).isSome();
        };
        if (isUndefined(vars)) {
          const toAppendTo = similar ? group.withSimilar : group.withoutSimilar;
          toAppendTo.callbacks.push(callback);
          if (toAppendTo.callbacks.length === 1) {
            toAppendTo.state.set(getCurrent());
          }
        } else {
          group.withVars.push({
            state: Cell(getCurrent()),
            similar,
            vars,
            callback
          });
        }
      });
      registeredFormatListeners.set(formatChangeItems);
    };
    const removeListeners = (registeredFormatListeners, formats, callback) => {
      const formatChangeItems = registeredFormatListeners.get();
      each$e(formats.split(','), format => get$a(formatChangeItems, format).each(group => {
        formatChangeItems[format] = {
          withSimilar: {
            ...group.withSimilar,
            callbacks: filter$5(group.withSimilar.callbacks, cb => cb !== callback)
          },
          withoutSimilar: {
            ...group.withoutSimilar,
            callbacks: filter$5(group.withoutSimilar.callbacks, cb => cb !== callback)
          },
          withVars: filter$5(group.withVars, item => item.callback !== callback)
        };
      }));
      registeredFormatListeners.set(formatChangeItems);
    };
    const formatChangedInternal = (editor, registeredFormatListeners, formats, callback, similar, vars) => {
      addListeners(editor, registeredFormatListeners, formats, callback, similar, vars);
      return { unbind: () => removeListeners(registeredFormatListeners, formats, callback) };
    };

    const toggle = (editor, name, vars, node) => {
      const fmt = editor.formatter.get(name);
      if (fmt) {
        if (match$2(editor, name, vars, node) && (!('toggle' in fmt[0]) || fmt[0].toggle)) {
          removeFormat$1(editor, name, vars, node);
        } else {
          applyFormat$1(editor, name, vars, node);
        }
      }
    };

    const explode$1 = Tools.explode;
    const create$8 = () => {
      const filters = {};
      const addFilter = (name, callback) => {
        each$e(explode$1(name), name => {
          if (!has$2(filters, name)) {
            filters[name] = {
              name,
              callbacks: []
            };
          }
          filters[name].callbacks.push(callback);
        });
      };
      const getFilters = () => values(filters);
      const removeFilter = (name, callback) => {
        each$e(explode$1(name), name => {
          if (has$2(filters, name)) {
            if (isNonNullable(callback)) {
              const filter = filters[name];
              const newCallbacks = filter$5(filter.callbacks, c => c !== callback);
              if (newCallbacks.length > 0) {
                filter.callbacks = newCallbacks;
              } else {
                delete filters[name];
              }
            } else {
              delete filters[name];
            }
          }
        });
      };
      return {
        addFilter,
        getFilters,
        removeFilter
      };
    };

    const removeAttrs = (node, names) => {
      each$e(names, name => {
        node.attr(name, null);
      });
    };
    const addFontToSpansFilter = (domParser, styles, fontSizes) => {
      domParser.addNodeFilter('font', nodes => {
        each$e(nodes, node => {
          const props = styles.parse(node.attr('style'));
          const color = node.attr('color');
          const face = node.attr('face');
          const size = node.attr('size');
          if (color) {
            props.color = color;
          }
          if (face) {
            props['font-family'] = face;
          }
          if (size) {
            toInt(size).each(num => {
              props['font-size'] = fontSizes[num - 1];
            });
          }
          node.name = 'span';
          node.attr('style', styles.serialize(props));
          removeAttrs(node, [
            'color',
            'face',
            'size'
          ]);
        });
      });
    };
    const addStrikeFilter = (domParser, schema, styles) => {
      domParser.addNodeFilter('strike', nodes => {
        const convertToSTag = schema.type !== 'html4';
        each$e(nodes, node => {
          if (convertToSTag) {
            node.name = 's';
          } else {
            const props = styles.parse(node.attr('style'));
            props['text-decoration'] = 'line-through';
            node.name = 'span';
            node.attr('style', styles.serialize(props));
          }
        });
      });
    };
    const addFilters = (domParser, settings, schema) => {
      var _a;
      const styles = Styles();
      if (settings.convert_fonts_to_spans) {
        addFontToSpansFilter(domParser, styles, Tools.explode((_a = settings.font_size_legacy_values) !== null && _a !== void 0 ? _a : ''));
      }
      addStrikeFilter(domParser, schema, styles);
    };
    const register$5 = (domParser, settings, schema) => {
      if (settings.inline_styles) {
        addFilters(domParser, settings, schema);
      }
    };

    const addNodeFilter = (settings, htmlParser, schema) => {
      htmlParser.addNodeFilter('br', (nodes, _, args) => {
        const blockElements = Tools.extend({}, schema.getBlockElements());
        const nonEmptyElements = schema.getNonEmptyElements();
        const whitespaceElements = schema.getWhitespaceElements();
        blockElements.body = 1;
        const isBlock = node => node.name in blockElements || isTransparentAstBlock(schema, node);
        for (let i = 0, l = nodes.length; i < l; i++) {
          let node = nodes[i];
          let parent = node.parent;
          if (parent && isBlock(parent) && node === parent.lastChild) {
            let prev = node.prev;
            while (prev) {
              const prevName = prev.name;
              if (prevName !== 'span' || prev.attr('data-mce-type') !== 'bookmark') {
                if (prevName === 'br') {
                  node = null;
                }
                break;
              }
              prev = prev.prev;
            }
            if (node) {
              node.remove();
              if (isEmpty(schema, nonEmptyElements, whitespaceElements, parent)) {
                const elementRule = schema.getElementRule(parent.name);
                if (elementRule) {
                  if (elementRule.removeEmpty) {
                    parent.remove();
                  } else if (elementRule.paddEmpty) {
                    paddEmptyNode(settings, args, isBlock, parent);
                  }
                }
              }
            }
          } else {
            let lastParent = node;
            while (parent && parent.firstChild === lastParent && parent.lastChild === lastParent) {
              lastParent = parent;
              if (blockElements[parent.name]) {
                break;
              }
              parent = parent.parent;
            }
            if (lastParent === parent) {
              const textNode = new AstNode('#text', 3);
              textNode.value = nbsp;
              node.replace(textNode);
            }
          }
        }
      });
    };

    const blobUriToBlob = url => fetch(url).then(res => res.ok ? res.blob() : Promise.reject()).catch(() => Promise.reject({
      message: `Cannot convert ${ url } to Blob. Resource might not exist or is inaccessible.`,
      uriType: 'blob'
    }));
    const extractBase64Data = data => {
      const matches = /([a-z0-9+\/=\s]+)/i.exec(data);
      return matches ? matches[1] : '';
    };
    const parseDataUri = uri => {
      const [type, ...rest] = uri.split(',');
      const data = rest.join(',');
      const matches = /data:([^/]+\/[^;]+)(;.+)?/.exec(type);
      if (matches) {
        const base64Encoded = matches[2] === ';base64';
        const extractedData = base64Encoded ? extractBase64Data(data) : decodeURIComponent(data);
        return Optional.some({
          type: matches[1],
          data: extractedData,
          base64Encoded
        });
      } else {
        return Optional.none();
      }
    };
    const buildBlob = (type, data, base64Encoded = true) => {
      let str = data;
      if (base64Encoded) {
        try {
          str = atob(data);
        } catch (e) {
          return Optional.none();
        }
      }
      const arr = new Uint8Array(str.length);
      for (let i = 0; i < arr.length; i++) {
        arr[i] = str.charCodeAt(i);
      }
      return Optional.some(new Blob([arr], { type }));
    };
    const dataUriToBlob = uri => {
      return new Promise((resolve, reject) => {
        parseDataUri(uri).bind(({type, data, base64Encoded}) => buildBlob(type, data, base64Encoded)).fold(() => reject('Invalid data URI'), resolve);
      });
    };
    const uriToBlob = url => {
      if (startsWith(url, 'blob:')) {
        return blobUriToBlob(url);
      } else if (startsWith(url, 'data:')) {
        return dataUriToBlob(url);
      } else {
        return Promise.reject('Unknown URI format');
      }
    };
    const blobToDataUri = blob => {
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        reader.onloadend = () => {
          resolve(reader.result);
        };
        reader.onerror = () => {
          var _a;
          reject((_a = reader.error) === null || _a === void 0 ? void 0 : _a.message);
        };
        reader.readAsDataURL(blob);
      });
    };

    let count$1 = 0;
    const uniqueId$1 = prefix => {
      return (prefix || 'blobid') + count$1++;
    };
    const processDataUri = (dataUri, base64Only, generateBlobInfo) => {
      return parseDataUri(dataUri).bind(({data, type, base64Encoded}) => {
        if (base64Only && !base64Encoded) {
          return Optional.none();
        } else {
          const base64 = base64Encoded ? data : btoa(data);
          return generateBlobInfo(base64, type);
        }
      });
    };
    const createBlobInfo$1 = (blobCache, blob, base64) => {
      const blobInfo = blobCache.create(uniqueId$1(), blob, base64);
      blobCache.add(blobInfo);
      return blobInfo;
    };
    const dataUriToBlobInfo = (blobCache, dataUri, base64Only = false) => {
      return processDataUri(dataUri, base64Only, (base64, type) => Optional.from(blobCache.getByData(base64, type)).orThunk(() => buildBlob(type, base64).map(blob => createBlobInfo$1(blobCache, blob, base64))));
    };
    const imageToBlobInfo = (blobCache, imageSrc) => {
      const invalidDataUri = () => Promise.reject('Invalid data URI');
      if (startsWith(imageSrc, 'blob:')) {
        const blobInfo = blobCache.getByUri(imageSrc);
        if (isNonNullable(blobInfo)) {
          return Promise.resolve(blobInfo);
        } else {
          return uriToBlob(imageSrc).then(blob => {
            return blobToDataUri(blob).then(dataUri => {
              return processDataUri(dataUri, false, base64 => {
                return Optional.some(createBlobInfo$1(blobCache, blob, base64));
              }).getOrThunk(invalidDataUri);
            });
          });
        }
      } else if (startsWith(imageSrc, 'data:')) {
        return dataUriToBlobInfo(blobCache, imageSrc).fold(invalidDataUri, blobInfo => Promise.resolve(blobInfo));
      } else {
        return Promise.reject('Unknown image data format');
      }
    };

    const isBogusImage = img => isNonNullable(img.attr('data-mce-bogus'));
    const isInternalImageSource = img => img.attr('src') === Env.transparentSrc || isNonNullable(img.attr('data-mce-placeholder'));
    const registerBase64ImageFilter = (parser, settings) => {
      const {blob_cache: blobCache} = settings;
      if (blobCache) {
        const processImage = img => {
          const inputSrc = img.attr('src');
          if (isInternalImageSource(img) || isBogusImage(img) || isNullable(inputSrc)) {
            return;
          }
          dataUriToBlobInfo(blobCache, inputSrc, true).each(blobInfo => {
            img.attr('src', blobInfo.blobUri());
          });
        };
        parser.addAttributeFilter('src', nodes => each$e(nodes, processImage));
      }
    };
    const isMimeType = (mime, type) => startsWith(mime, `${ type }/`);
    const createSafeEmbed = (mime, src, width, height, sandboxIframes) => {
      let name;
      if (isUndefined(mime)) {
        name = 'iframe';
      } else if (isMimeType(mime, 'image')) {
        name = 'img';
      } else if (isMimeType(mime, 'video')) {
        name = 'video';
      } else if (isMimeType(mime, 'audio')) {
        name = 'audio';
      } else {
        name = 'iframe';
      }
      const embed = new AstNode(name, 1);
      embed.attr(name === 'audio' ? { src } : {
        src,
        width,
        height
      });
      if (name === 'audio' || name === 'video') {
        embed.attr('controls', '');
      }
      if (name === 'iframe' && sandboxIframes) {
        embed.attr('sandbox', '');
      }
      return embed;
    };
    const register$4 = (parser, settings) => {
      const schema = parser.schema;
      if (settings.remove_trailing_brs) {
        addNodeFilter(settings, parser, schema);
      }
      parser.addAttributeFilter('href', nodes => {
        let i = nodes.length;
        const appendRel = rel => {
          const parts = rel.split(' ').filter(p => p.length > 0);
          return parts.concat(['noopener']).sort().join(' ');
        };
        const addNoOpener = rel => {
          const newRel = rel ? Tools.trim(rel) : '';
          if (!/\b(noopener)\b/g.test(newRel)) {
            return appendRel(newRel);
          } else {
            return newRel;
          }
        };
        if (!settings.allow_unsafe_link_target) {
          while (i--) {
            const node = nodes[i];
            if (node.name === 'a' && node.attr('target') === '_blank') {
              node.attr('rel', addNoOpener(node.attr('rel')));
            }
          }
        }
      });
      if (!settings.allow_html_in_named_anchor) {
        parser.addAttributeFilter('id,name', nodes => {
          let i = nodes.length, sibling, prevSibling, parent, node;
          while (i--) {
            node = nodes[i];
            if (node.name === 'a' && node.firstChild && !node.attr('href')) {
              parent = node.parent;
              sibling = node.lastChild;
              while (sibling && parent) {
                prevSibling = sibling.prev;
                parent.insert(sibling, node);
                sibling = prevSibling;
              }
            }
          }
        });
      }
      if (settings.fix_list_elements) {
        parser.addNodeFilter('ul,ol', nodes => {
          let i = nodes.length, node, parentNode;
          while (i--) {
            node = nodes[i];
            parentNode = node.parent;
            if (parentNode && (parentNode.name === 'ul' || parentNode.name === 'ol')) {
              if (node.prev && node.prev.name === 'li') {
                node.prev.append(node);
              } else {
                const li = new AstNode('li', 1);
                li.attr('style', 'list-style-type: none');
                node.wrap(li);
              }
            }
          }
        });
      }
      const validClasses = schema.getValidClasses();
      if (settings.validate && validClasses) {
        parser.addAttributeFilter('class', nodes => {
          var _a;
          let i = nodes.length;
          while (i--) {
            const node = nodes[i];
            const clazz = (_a = node.attr('class')) !== null && _a !== void 0 ? _a : '';
            const classList = Tools.explode(clazz, ' ');
            let classValue = '';
            for (let ci = 0; ci < classList.length; ci++) {
              const className = classList[ci];
              let valid = false;
              let validClassesMap = validClasses['*'];
              if (validClassesMap && validClassesMap[className]) {
                valid = true;
              }
              validClassesMap = validClasses[node.name];
              if (!valid && validClassesMap && validClassesMap[className]) {
                valid = true;
              }
              if (valid) {
                if (classValue) {
                  classValue += ' ';
                }
                classValue += className;
              }
            }
            if (!classValue.length) {
              classValue = null;
            }
            node.attr('class', classValue);
          }
        });
      }
      registerBase64ImageFilter(parser, settings);
      if (settings.convert_unsafe_embeds) {
        parser.addNodeFilter('object,embed', nodes => each$e(nodes, node => {
          node.replace(createSafeEmbed(node.attr('type'), node.name === 'object' ? node.attr('data') : node.attr('src'), node.attr('width'), node.attr('height'), settings.sandbox_iframes));
        }));
      }
      if (settings.sandbox_iframes) {
        parser.addNodeFilter('iframe', nodes => each$e(nodes, node => node.attr('sandbox', '')));
      }
    };

    /*! @license DOMPurify 3.1.7 | (c) Cure53 and other contributors | Released under the Apache license 2.0 and Mozilla Public License 2.0 | github.com/cure53/DOMPurify/blob/3.1.7/LICENSE */

    const {
      entries,
      setPrototypeOf,
      isFrozen,
      getPrototypeOf,
      getOwnPropertyDescriptor
    } = Object;
    let {
      freeze,
      seal,
      create: create$7
    } = Object; // eslint-disable-line import/no-mutable-exports
    let {
      apply,
      construct
    } = typeof Reflect !== 'undefined' && Reflect;
    if (!freeze) {
      freeze = function freeze(x) {
        return x;
      };
    }
    if (!seal) {
      seal = function seal(x) {
        return x;
      };
    }
    if (!apply) {
      apply = function apply(fun, thisValue, args) {
        return fun.apply(thisValue, args);
      };
    }
    if (!construct) {
      construct = function construct(Func, args) {
        return new Func(...args);
      };
    }
    const arrayForEach = unapply(Array.prototype.forEach);
    const arrayPop = unapply(Array.prototype.pop);
    const arrayPush = unapply(Array.prototype.push);
    const stringToLowerCase = unapply(String.prototype.toLowerCase);
    const stringToString = unapply(String.prototype.toString);
    const stringMatch = unapply(String.prototype.match);
    const stringReplace = unapply(String.prototype.replace);
    const stringIndexOf = unapply(String.prototype.indexOf);
    const stringTrim = unapply(String.prototype.trim);
    const objectHasOwnProperty = unapply(Object.prototype.hasOwnProperty);
    const regExpTest = unapply(RegExp.prototype.test);
    const typeErrorCreate = unconstruct(TypeError);

    /**
     * Creates a new function that calls the given function with a specified thisArg and arguments.
     *
     * @param {Function} func - The function to be wrapped and called.
     * @returns {Function} A new function that calls the given function with a specified thisArg and arguments.
     */
    function unapply(func) {
      return function (thisArg) {
        for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
          args[_key - 1] = arguments[_key];
        }
        return apply(func, thisArg, args);
      };
    }

    /**
     * Creates a new function that constructs an instance of the given constructor function with the provided arguments.
     *
     * @param {Function} func - The constructor function to be wrapped and called.
     * @returns {Function} A new function that constructs an instance of the given constructor function with the provided arguments.
     */
    function unconstruct(func) {
      return function () {
        for (var _len2 = arguments.length, args = new Array(_len2), _key2 = 0; _key2 < _len2; _key2++) {
          args[_key2] = arguments[_key2];
        }
        return construct(func, args);
      };
    }

    /**
     * Add properties to a lookup table
     *
     * @param {Object} set - The set to which elements will be added.
     * @param {Array} array - The array containing elements to be added to the set.
     * @param {Function} transformCaseFunc - An optional function to transform the case of each element before adding to the set.
     * @returns {Object} The modified set with added elements.
     */
    function addToSet(set, array) {
      let transformCaseFunc = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : stringToLowerCase;
      if (setPrototypeOf) {
        // Make 'in' and truthy checks like Boolean(set.constructor)
        // independent of any properties defined on Object.prototype.
        // Prevent prototype setters from intercepting set as a this value.
        setPrototypeOf(set, null);
      }
      let l = array.length;
      while (l--) {
        let element = array[l];
        if (typeof element === 'string') {
          const lcElement = transformCaseFunc(element);
          if (lcElement !== element) {
            // Config presets (e.g. tags.js, attrs.js) are immutable.
            if (!isFrozen(array)) {
              array[l] = lcElement;
            }
            element = lcElement;
          }
        }
        set[element] = true;
      }
      return set;
    }

    /**
     * Clean up an array to harden against CSPP
     *
     * @param {Array} array - The array to be cleaned.
     * @returns {Array} The cleaned version of the array
     */
    function cleanArray(array) {
      for (let index = 0; index < array.length; index++) {
        const isPropertyExist = objectHasOwnProperty(array, index);
        if (!isPropertyExist) {
          array[index] = null;
        }
      }
      return array;
    }

    /**
     * Shallow clone an object
     *
     * @param {Object} object - The object to be cloned.
     * @returns {Object} A new object that copies the original.
     */
    function clone(object) {
      const newObject = create$7(null);
      for (const [property, value] of entries(object)) {
        const isPropertyExist = objectHasOwnProperty(object, property);
        if (isPropertyExist) {
          if (Array.isArray(value)) {
            newObject[property] = cleanArray(value);
          } else if (value && typeof value === 'object' && value.constructor === Object) {
            newObject[property] = clone(value);
          } else {
            newObject[property] = value;
          }
        }
      }
      return newObject;
    }

    /**
     * This method automatically checks if the prop is function or getter and behaves accordingly.
     *
     * @param {Object} object - The object to look up the getter function in its prototype chain.
     * @param {String} prop - The property name for which to find the getter function.
     * @returns {Function} The getter function found in the prototype chain or a fallback function.
     */
    function lookupGetter(object, prop) {
      while (object !== null) {
        const desc = getOwnPropertyDescriptor(object, prop);
        if (desc) {
          if (desc.get) {
            return unapply(desc.get);
          }
          if (typeof desc.value === 'function') {
            return unapply(desc.value);
          }
        }
        object = getPrototypeOf(object);
      }
      function fallbackValue() {
        return null;
      }
      return fallbackValue;
    }

    const html$1 = freeze(['a', 'abbr', 'acronym', 'address', 'area', 'article', 'aside', 'audio', 'b', 'bdi', 'bdo', 'big', 'blink', 'blockquote', 'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code', 'col', 'colgroup', 'content', 'data', 'datalist', 'dd', 'decorator', 'del', 'details', 'dfn', 'dialog', 'dir', 'div', 'dl', 'dt', 'element', 'em', 'fieldset', 'figcaption', 'figure', 'font', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'img', 'input', 'ins', 'kbd', 'label', 'legend', 'li', 'main', 'map', 'mark', 'marquee', 'menu', 'menuitem', 'meter', 'nav', 'nobr', 'ol', 'optgroup', 'option', 'output', 'p', 'picture', 'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'section', 'select', 'shadow', 'small', 'source', 'spacer', 'span', 'strike', 'strong', 'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'template', 'textarea', 'tfoot', 'th', 'thead', 'time', 'tr', 'track', 'tt', 'u', 'ul', 'var', 'video', 'wbr']);

    // SVG
    const svg$1 = freeze(['svg', 'a', 'altglyph', 'altglyphdef', 'altglyphitem', 'animatecolor', 'animatemotion', 'animatetransform', 'circle', 'clippath', 'defs', 'desc', 'ellipse', 'filter', 'font', 'g', 'glyph', 'glyphref', 'hkern', 'image', 'line', 'lineargradient', 'marker', 'mask', 'metadata', 'mpath', 'path', 'pattern', 'polygon', 'polyline', 'radialgradient', 'rect', 'stop', 'style', 'switch', 'symbol', 'text', 'textpath', 'title', 'tref', 'tspan', 'view', 'vkern']);
    const svgFilters = freeze(['feBlend', 'feColorMatrix', 'feComponentTransfer', 'feComposite', 'feConvolveMatrix', 'feDiffuseLighting', 'feDisplacementMap', 'feDistantLight', 'feDropShadow', 'feFlood', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feImage', 'feMerge', 'feMergeNode', 'feMorphology', 'feOffset', 'fePointLight', 'feSpecularLighting', 'feSpotLight', 'feTile', 'feTurbulence']);

    // List of SVG elements that are disallowed by default.
    // We still need to know them so that we can do namespace
    // checks properly in case one wants to add them to
    // allow-list.
    const svgDisallowed = freeze(['animate', 'color-profile', 'cursor', 'discard', 'font-face', 'font-face-format', 'font-face-name', 'font-face-src', 'font-face-uri', 'foreignobject', 'hatch', 'hatchpath', 'mesh', 'meshgradient', 'meshpatch', 'meshrow', 'missing-glyph', 'script', 'set', 'solidcolor', 'unknown', 'use']);
    const mathMl$1 = freeze(['math', 'menclose', 'merror', 'mfenced', 'mfrac', 'mglyph', 'mi', 'mlabeledtr', 'mmultiscripts', 'mn', 'mo', 'mover', 'mpadded', 'mphantom', 'mroot', 'mrow', 'ms', 'mspace', 'msqrt', 'mstyle', 'msub', 'msup', 'msubsup', 'mtable', 'mtd', 'mtext', 'mtr', 'munder', 'munderover', 'mprescripts']);

    // Similarly to SVG, we want to know all MathML elements,
    // even those that we disallow by default.
    const mathMlDisallowed = freeze(['maction', 'maligngroup', 'malignmark', 'mlongdiv', 'mscarries', 'mscarry', 'msgroup', 'mstack', 'msline', 'msrow', 'semantics', 'annotation', 'annotation-xml', 'mprescripts', 'none']);
    const text = freeze(['#text']);

    const html = freeze(['accept', 'action', 'align', 'alt', 'autocapitalize', 'autocomplete', 'autopictureinpicture', 'autoplay', 'background', 'bgcolor', 'border', 'capture', 'cellpadding', 'cellspacing', 'checked', 'cite', 'class', 'clear', 'color', 'cols', 'colspan', 'controls', 'controlslist', 'coords', 'crossorigin', 'datetime', 'decoding', 'default', 'dir', 'disabled', 'disablepictureinpicture', 'disableremoteplayback', 'download', 'draggable', 'enctype', 'enterkeyhint', 'face', 'for', 'headers', 'height', 'hidden', 'high', 'href', 'hreflang', 'id', 'inputmode', 'integrity', 'ismap', 'kind', 'label', 'lang', 'list', 'loading', 'loop', 'low', 'max', 'maxlength', 'media', 'method', 'min', 'minlength', 'multiple', 'muted', 'name', 'nonce', 'noshade', 'novalidate', 'nowrap', 'open', 'optimum', 'pattern', 'placeholder', 'playsinline', 'popover', 'popovertarget', 'popovertargetaction', 'poster', 'preload', 'pubdate', 'radiogroup', 'readonly', 'rel', 'required', 'rev', 'reversed', 'role', 'rows', 'rowspan', 'spellcheck', 'scope', 'selected', 'shape', 'size', 'sizes', 'span', 'srclang', 'start', 'src', 'srcset', 'step', 'style', 'summary', 'tabindex', 'title', 'translate', 'type', 'usemap', 'valign', 'value', 'width', 'wrap', 'xmlns', 'slot']);
    const svg = freeze(['accent-height', 'accumulate', 'additive', 'alignment-baseline', 'amplitude', 'ascent', 'attributename', 'attributetype', 'azimuth', 'basefrequency', 'baseline-shift', 'begin', 'bias', 'by', 'class', 'clip', 'clippathunits', 'clip-path', 'clip-rule', 'color', 'color-interpolation', 'color-interpolation-filters', 'color-profile', 'color-rendering', 'cx', 'cy', 'd', 'dx', 'dy', 'diffuseconstant', 'direction', 'display', 'divisor', 'dur', 'edgemode', 'elevation', 'end', 'exponent', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'filterunits', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'fx', 'fy', 'g1', 'g2', 'glyph-name', 'glyphref', 'gradientunits', 'gradienttransform', 'height', 'href', 'id', 'image-rendering', 'in', 'in2', 'intercept', 'k', 'k1', 'k2', 'k3', 'k4', 'kerning', 'keypoints', 'keysplines', 'keytimes', 'lang', 'lengthadjust', 'letter-spacing', 'kernelmatrix', 'kernelunitlength', 'lighting-color', 'local', 'marker-end', 'marker-mid', 'marker-start', 'markerheight', 'markerunits', 'markerwidth', 'maskcontentunits', 'maskunits', 'max', 'mask', 'media', 'method', 'mode', 'min', 'name', 'numoctaves', 'offset', 'operator', 'opacity', 'order', 'orient', 'orientation', 'origin', 'overflow', 'paint-order', 'path', 'pathlength', 'patterncontentunits', 'patterntransform', 'patternunits', 'points', 'preservealpha', 'preserveaspectratio', 'primitiveunits', 'r', 'rx', 'ry', 'radius', 'refx', 'refy', 'repeatcount', 'repeatdur', 'restart', 'result', 'rotate', 'scale', 'seed', 'shape-rendering', 'slope', 'specularconstant', 'specularexponent', 'spreadmethod', 'startoffset', 'stddeviation', 'stitchtiles', 'stop-color', 'stop-opacity', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke', 'stroke-width', 'style', 'surfacescale', 'systemlanguage', 'tabindex', 'tablevalues', 'targetx', 'targety', 'transform', 'transform-origin', 'text-anchor', 'text-decoration', 'text-rendering', 'textlength', 'type', 'u1', 'u2', 'unicode', 'values', 'viewbox', 'visibility', 'version', 'vert-adv-y', 'vert-origin-x', 'vert-origin-y', 'width', 'word-spacing', 'wrap', 'writing-mode', 'xchannelselector', 'ychannelselector', 'x', 'x1', 'x2', 'xmlns', 'y', 'y1', 'y2', 'z', 'zoomandpan']);
    const mathMl = freeze(['accent', 'accentunder', 'align', 'bevelled', 'close', 'columnsalign', 'columnlines', 'columnspan', 'denomalign', 'depth', 'dir', 'display', 'displaystyle', 'encoding', 'fence', 'frame', 'height', 'href', 'id', 'largeop', 'length', 'linethickness', 'lspace', 'lquote', 'mathbackground', 'mathcolor', 'mathsize', 'mathvariant', 'maxsize', 'minsize', 'movablelimits', 'notation', 'numalign', 'open', 'rowalign', 'rowlines', 'rowspacing', 'rowspan', 'rspace', 'rquote', 'scriptlevel', 'scriptminsize', 'scriptsizemultiplier', 'selection', 'separator', 'separators', 'stretchy', 'subscriptshift', 'supscriptshift', 'symmetric', 'voffset', 'width', 'xmlns']);
    const xml = freeze(['xlink:href', 'xml:id', 'xlink:title', 'xml:space', 'xmlns:xlink']);

    // eslint-disable-next-line unicorn/better-regex
    const MUSTACHE_EXPR = seal(/\{\{[\w\W]*|[\w\W]*\}\}/gm); // Specify template detection regex for SAFE_FOR_TEMPLATES mode
    const ERB_EXPR = seal(/<%[\w\W]*|[\w\W]*%>/gm);
    const TMPLIT_EXPR = seal(/\${[\w\W]*}/gm);
    const DATA_ATTR = seal(/^data-[\-\w.\u00B7-\uFFFF]/); // eslint-disable-line no-useless-escape
    const ARIA_ATTR = seal(/^aria-[\-\w]+$/); // eslint-disable-line no-useless-escape
    const IS_ALLOWED_URI = seal(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|sms|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i // eslint-disable-line no-useless-escape
    );
    const IS_SCRIPT_OR_DATA = seal(/^(?:\w+script|data):/i);
    const ATTR_WHITESPACE = seal(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g // eslint-disable-line no-control-regex
    );
    const DOCTYPE_NAME = seal(/^html$/i);
    const CUSTOM_ELEMENT = seal(/^[a-z][.\w]*(-[.\w]+)+$/i);

    var EXPRESSIONS = /*#__PURE__*/Object.freeze({
      __proto__: null,
      MUSTACHE_EXPR: MUSTACHE_EXPR,
      ERB_EXPR: ERB_EXPR,
      TMPLIT_EXPR: TMPLIT_EXPR,
      DATA_ATTR: DATA_ATTR,
      ARIA_ATTR: ARIA_ATTR,
      IS_ALLOWED_URI: IS_ALLOWED_URI,
      IS_SCRIPT_OR_DATA: IS_SCRIPT_OR_DATA,
      ATTR_WHITESPACE: ATTR_WHITESPACE,
      DOCTYPE_NAME: DOCTYPE_NAME,
      CUSTOM_ELEMENT: CUSTOM_ELEMENT
    });

    // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
    const NODE_TYPE = {
      element: 1,
      attribute: 2,
      text: 3,
      cdataSection: 4,
      entityReference: 5,
      // Deprecated
      entityNode: 6,
      // Deprecated
      progressingInstruction: 7,
      comment: 8,
      document: 9,
      documentType: 10,
      documentFragment: 11,
      notation: 12 // Deprecated
    };
    const getGlobal = function getGlobal() {
      return typeof window === 'undefined' ? null : window;
    };

    /**
     * Creates a no-op policy for internal use only.
     * Don't export this function outside this module!
     * @param {TrustedTypePolicyFactory} trustedTypes The policy factory.
     * @param {HTMLScriptElement} purifyHostElement The Script element used to load DOMPurify (to determine policy name suffix).
     * @return {TrustedTypePolicy} The policy created (or null, if Trusted Types
     * are not supported or creating the policy failed).
     */
    const _createTrustedTypesPolicy = function _createTrustedTypesPolicy(trustedTypes, purifyHostElement) {
      if (typeof trustedTypes !== 'object' || typeof trustedTypes.createPolicy !== 'function') {
        return null;
      }

      // Allow the callers to control the unique policy name
      // by adding a data-tt-policy-suffix to the script element with the DOMPurify.
      // Policy creation with duplicate names throws in Trusted Types.
      let suffix = null;
      const ATTR_NAME = 'data-tt-policy-suffix';
      if (purifyHostElement && purifyHostElement.hasAttribute(ATTR_NAME)) {
        suffix = purifyHostElement.getAttribute(ATTR_NAME);
      }
      const policyName = 'dompurify' + (suffix ? '#' + suffix : '');
      try {
        return trustedTypes.createPolicy(policyName, {
          createHTML(html) {
            return html;
          },
          createScriptURL(scriptUrl) {
            return scriptUrl;
          }
        });
      } catch (_) {
        // Policy creation failed (most likely another DOMPurify script has
        // already run). Skip creating the policy, as this will only cause errors
        // if TT are enforced.
        console.warn('TrustedTypes policy ' + policyName + ' could not be created.');
        return null;
      }
    };
    function createDOMPurify() {
      let window = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : getGlobal();
      const DOMPurify = root => createDOMPurify(root);

      /**
       * Version label, exposed for easier checks
       * if DOMPurify is up to date or not
       */
      DOMPurify.version = '3.1.7';

      /**
       * Array of elements that DOMPurify removed during sanitation.
       * Empty if nothing was removed.
       */
      DOMPurify.removed = [];
      if (!window || !window.document || window.document.nodeType !== NODE_TYPE.document) {
        // Not running in a browser, provide a factory function
        // so that you can pass your own Window
        DOMPurify.isSupported = false;
        return DOMPurify;
      }
      let {
        document
      } = window;
      const originalDocument = document;
      const currentScript = originalDocument.currentScript;
      const {
        DocumentFragment,
        HTMLTemplateElement,
        Node,
        Element,
        NodeFilter,
        NamedNodeMap = window.NamedNodeMap || window.MozNamedAttrMap,
        HTMLFormElement,
        DOMParser,
        trustedTypes
      } = window;
      const ElementPrototype = Element.prototype;
      const cloneNode = lookupGetter(ElementPrototype, 'cloneNode');
      const remove = lookupGetter(ElementPrototype, 'remove');
      const getNextSibling = lookupGetter(ElementPrototype, 'nextSibling');
      const getChildNodes = lookupGetter(ElementPrototype, 'childNodes');
      const getParentNode = lookupGetter(ElementPrototype, 'parentNode');

      // As per issue #47, the web-components registry is inherited by a
      // new document created via createHTMLDocument. As per the spec
      // (http://w3c.github.io/webcomponents/spec/custom/#creating-and-passing-registries)
      // a new empty registry is used when creating a template contents owner
      // document, so we use that as our parent document to ensure nothing
      // is inherited.
      if (typeof HTMLTemplateElement === 'function') {
        const template = document.createElement('template');
        if (template.content && template.content.ownerDocument) {
          document = template.content.ownerDocument;
        }
      }
      let trustedTypesPolicy;
      let emptyHTML = '';
      const {
        implementation,
        createNodeIterator,
        createDocumentFragment,
        getElementsByTagName
      } = document;
      const {
        importNode
      } = originalDocument;
      let hooks = {};

      /**
       * Expose whether this browser supports running the full DOMPurify.
       */
      DOMPurify.isSupported = typeof entries === 'function' && typeof getParentNode === 'function' && implementation && implementation.createHTMLDocument !== undefined;
      const {
        MUSTACHE_EXPR,
        ERB_EXPR,
        TMPLIT_EXPR,
        DATA_ATTR,
        ARIA_ATTR,
        IS_SCRIPT_OR_DATA,
        ATTR_WHITESPACE,
        CUSTOM_ELEMENT
      } = EXPRESSIONS;
      let {
        IS_ALLOWED_URI: IS_ALLOWED_URI$1
      } = EXPRESSIONS;

      /**
       * We consider the elements and attributes below to be safe. Ideally
       * don't add any new ones but feel free to remove unwanted ones.
       */

      /* allowed element names */
      let ALLOWED_TAGS = null;
      const DEFAULT_ALLOWED_TAGS = addToSet({}, [...html$1, ...svg$1, ...svgFilters, ...mathMl$1, ...text]);

      /* Allowed attribute names */
      let ALLOWED_ATTR = null;
      const DEFAULT_ALLOWED_ATTR = addToSet({}, [...html, ...svg, ...mathMl, ...xml]);

      /*
       * Configure how DOMPUrify should handle custom elements and their attributes as well as customized built-in elements.
       * @property {RegExp|Function|null} tagNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any custom elements)
       * @property {RegExp|Function|null} attributeNameCheck one of [null, regexPattern, predicate]. Default: `null` (disallow any attributes not on the allow list)
       * @property {boolean} allowCustomizedBuiltInElements allow custom elements derived from built-ins if they pass CUSTOM_ELEMENT_HANDLING.tagNameCheck. Default: `false`.
       */
      let CUSTOM_ELEMENT_HANDLING = Object.seal(create$7(null, {
        tagNameCheck: {
          writable: true,
          configurable: false,
          enumerable: true,
          value: null
        },
        attributeNameCheck: {
          writable: true,
          configurable: false,
          enumerable: true,
          value: null
        },
        allowCustomizedBuiltInElements: {
          writable: true,
          configurable: false,
          enumerable: true,
          value: false
        }
      }));

      /* Explicitly forbidden tags (overrides ALLOWED_TAGS/ADD_TAGS) */
      let FORBID_TAGS = null;

      /* Explicitly forbidden attributes (overrides ALLOWED_ATTR/ADD_ATTR) */
      let FORBID_ATTR = null;

      /* Decide if ARIA attributes are okay */
      let ALLOW_ARIA_ATTR = true;

      /* Decide if custom data attributes are okay */
      let ALLOW_DATA_ATTR = true;

      /* Decide if unknown protocols are okay */
      let ALLOW_UNKNOWN_PROTOCOLS = false;

      /* Decide if self-closing tags in attributes are allowed.
       * Usually removed due to a mXSS issue in jQuery 3.0 */
      let ALLOW_SELF_CLOSE_IN_ATTR = true;

      /* Output should be safe for common template engines.
       * This means, DOMPurify removes data attributes, mustaches and ERB
       */
      let SAFE_FOR_TEMPLATES = false;

      /* Output should be safe even for XML used within HTML and alike.
       * This means, DOMPurify removes comments when containing risky content.
       */
      let SAFE_FOR_XML = true;

      /* Decide if document with <html>... should be returned */
      let WHOLE_DOCUMENT = false;

      /* Track whether config is already set on this instance of DOMPurify. */
      let SET_CONFIG = false;

      /* Decide if all elements (e.g. style, script) must be children of
       * document.body. By default, browsers might move them to document.head */
      let FORCE_BODY = false;

      /* Decide if a DOM `HTMLBodyElement` should be returned, instead of a html
       * string (or a TrustedHTML object if Trusted Types are supported).
       * If `WHOLE_DOCUMENT` is enabled a `HTMLHtmlElement` will be returned instead
       */
      let RETURN_DOM = false;

      /* Decide if a DOM `DocumentFragment` should be returned, instead of a html
       * string  (or a TrustedHTML object if Trusted Types are supported) */
      let RETURN_DOM_FRAGMENT = false;

      /* Try to return a Trusted Type object instead of a string, return a string in
       * case Trusted Types are not supported  */
      let RETURN_TRUSTED_TYPE = false;

      /* Output should be free from DOM clobbering attacks?
       * This sanitizes markups named with colliding, clobberable built-in DOM APIs.
       */
      let SANITIZE_DOM = true;

      /* Achieve full DOM Clobbering protection by isolating the namespace of named
       * properties and JS variables, mitigating attacks that abuse the HTML/DOM spec rules.
       *
       * HTML/DOM spec rules that enable DOM Clobbering:
       *   - Named Access on Window (§7.3.3)
       *   - DOM Tree Accessors (§3.1.5)
       *   - Form Element Parent-Child Relations (§4.10.3)
       *   - Iframe srcdoc / Nested WindowProxies (§4.8.5)
       *   - HTMLCollection (§4.2.10.2)
       *
       * Namespace isolation is implemented by prefixing `id` and `name` attributes
       * with a constant string, i.e., `user-content-`
       */
      let SANITIZE_NAMED_PROPS = false;
      const SANITIZE_NAMED_PROPS_PREFIX = 'user-content-';

      /* Keep element content when removing element? */
      let KEEP_CONTENT = true;

      /* If a `Node` is passed to sanitize(), then performs sanitization in-place instead
       * of importing it into a new Document and returning a sanitized copy */
      let IN_PLACE = false;

      /* Allow usage of profiles like html, svg and mathMl */
      let USE_PROFILES = {};

      /* Tags to ignore content of when KEEP_CONTENT is true */
      let FORBID_CONTENTS = null;
      const DEFAULT_FORBID_CONTENTS = addToSet({}, ['annotation-xml', 'audio', 'colgroup', 'desc', 'foreignobject', 'head', 'iframe', 'math', 'mi', 'mn', 'mo', 'ms', 'mtext', 'noembed', 'noframes', 'noscript', 'plaintext', 'script', 'style', 'svg', 'template', 'thead', 'title', 'video', 'xmp']);

      /* Tags that are safe for data: URIs */
      let DATA_URI_TAGS = null;
      const DEFAULT_DATA_URI_TAGS = addToSet({}, ['audio', 'video', 'img', 'source', 'image', 'track']);

      /* Attributes safe for values like "javascript:" */
      let URI_SAFE_ATTRIBUTES = null;
      const DEFAULT_URI_SAFE_ATTRIBUTES = addToSet({}, ['alt', 'class', 'for', 'id', 'label', 'name', 'pattern', 'placeholder', 'role', 'summary', 'title', 'value', 'style', 'xmlns']);
      const MATHML_NAMESPACE = 'http://www.w3.org/1998/Math/MathML';
      const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
      const HTML_NAMESPACE = 'http://www.w3.org/1999/xhtml';
      /* Document namespace */
      let NAMESPACE = HTML_NAMESPACE;
      let IS_EMPTY_INPUT = false;

      /* Allowed XHTML+XML namespaces */
      let ALLOWED_NAMESPACES = null;
      const DEFAULT_ALLOWED_NAMESPACES = addToSet({}, [MATHML_NAMESPACE, SVG_NAMESPACE, HTML_NAMESPACE], stringToString);

      /* Parsing of strict XHTML documents */
      let PARSER_MEDIA_TYPE = null;
      const SUPPORTED_PARSER_MEDIA_TYPES = ['application/xhtml+xml', 'text/html'];
      const DEFAULT_PARSER_MEDIA_TYPE = 'text/html';
      let transformCaseFunc = null;

      /* Keep a reference to config to pass to hooks */
      let CONFIG = null;

      /* Ideally, do not touch anything below this line */
      /* ______________________________________________ */

      const formElement = document.createElement('form');
      const isRegexOrFunction = function isRegexOrFunction(testValue) {
        return testValue instanceof RegExp || testValue instanceof Function;
      };

      /**
       * _parseConfig
       *
       * @param  {Object} cfg optional config literal
       */
      // eslint-disable-next-line complexity
      const _parseConfig = function _parseConfig() {
        let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        if (CONFIG && CONFIG === cfg) {
          return;
        }

        /* Shield configuration object from tampering */
        if (!cfg || typeof cfg !== 'object') {
          cfg = {};
        }

        /* Shield configuration object from prototype pollution */
        cfg = clone(cfg);
        PARSER_MEDIA_TYPE =
        // eslint-disable-next-line unicorn/prefer-includes
        SUPPORTED_PARSER_MEDIA_TYPES.indexOf(cfg.PARSER_MEDIA_TYPE) === -1 ? DEFAULT_PARSER_MEDIA_TYPE : cfg.PARSER_MEDIA_TYPE;

        // HTML tags and attributes are not case-sensitive, converting to lowercase. Keeping XHTML as is.
        transformCaseFunc = PARSER_MEDIA_TYPE === 'application/xhtml+xml' ? stringToString : stringToLowerCase;

        /* Set configuration parameters */
        ALLOWED_TAGS = objectHasOwnProperty(cfg, 'ALLOWED_TAGS') ? addToSet({}, cfg.ALLOWED_TAGS, transformCaseFunc) : DEFAULT_ALLOWED_TAGS;
        ALLOWED_ATTR = objectHasOwnProperty(cfg, 'ALLOWED_ATTR') ? addToSet({}, cfg.ALLOWED_ATTR, transformCaseFunc) : DEFAULT_ALLOWED_ATTR;
        ALLOWED_NAMESPACES = objectHasOwnProperty(cfg, 'ALLOWED_NAMESPACES') ? addToSet({}, cfg.ALLOWED_NAMESPACES, stringToString) : DEFAULT_ALLOWED_NAMESPACES;
        URI_SAFE_ATTRIBUTES = objectHasOwnProperty(cfg, 'ADD_URI_SAFE_ATTR') ? addToSet(clone(DEFAULT_URI_SAFE_ATTRIBUTES),
        // eslint-disable-line indent
        cfg.ADD_URI_SAFE_ATTR,
        // eslint-disable-line indent
        transformCaseFunc // eslint-disable-line indent
        ) // eslint-disable-line indent
        : DEFAULT_URI_SAFE_ATTRIBUTES;
        DATA_URI_TAGS = objectHasOwnProperty(cfg, 'ADD_DATA_URI_TAGS') ? addToSet(clone(DEFAULT_DATA_URI_TAGS),
        // eslint-disable-line indent
        cfg.ADD_DATA_URI_TAGS,
        // eslint-disable-line indent
        transformCaseFunc // eslint-disable-line indent
        ) // eslint-disable-line indent
        : DEFAULT_DATA_URI_TAGS;
        FORBID_CONTENTS = objectHasOwnProperty(cfg, 'FORBID_CONTENTS') ? addToSet({}, cfg.FORBID_CONTENTS, transformCaseFunc) : DEFAULT_FORBID_CONTENTS;
        FORBID_TAGS = objectHasOwnProperty(cfg, 'FORBID_TAGS') ? addToSet({}, cfg.FORBID_TAGS, transformCaseFunc) : {};
        FORBID_ATTR = objectHasOwnProperty(cfg, 'FORBID_ATTR') ? addToSet({}, cfg.FORBID_ATTR, transformCaseFunc) : {};
        USE_PROFILES = objectHasOwnProperty(cfg, 'USE_PROFILES') ? cfg.USE_PROFILES : false;
        ALLOW_ARIA_ATTR = cfg.ALLOW_ARIA_ATTR !== false; // Default true
        ALLOW_DATA_ATTR = cfg.ALLOW_DATA_ATTR !== false; // Default true
        ALLOW_UNKNOWN_PROTOCOLS = cfg.ALLOW_UNKNOWN_PROTOCOLS || false; // Default false
        ALLOW_SELF_CLOSE_IN_ATTR = cfg.ALLOW_SELF_CLOSE_IN_ATTR !== false; // Default true
        SAFE_FOR_TEMPLATES = cfg.SAFE_FOR_TEMPLATES || false; // Default false
        SAFE_FOR_XML = cfg.SAFE_FOR_XML !== false; // Default true
        WHOLE_DOCUMENT = cfg.WHOLE_DOCUMENT || false; // Default false
        RETURN_DOM = cfg.RETURN_DOM || false; // Default false
        RETURN_DOM_FRAGMENT = cfg.RETURN_DOM_FRAGMENT || false; // Default false
        RETURN_TRUSTED_TYPE = cfg.RETURN_TRUSTED_TYPE || false; // Default false
        FORCE_BODY = cfg.FORCE_BODY || false; // Default false
        SANITIZE_DOM = cfg.SANITIZE_DOM !== false; // Default true
        SANITIZE_NAMED_PROPS = cfg.SANITIZE_NAMED_PROPS || false; // Default false
        KEEP_CONTENT = cfg.KEEP_CONTENT !== false; // Default true
        IN_PLACE = cfg.IN_PLACE || false; // Default false
        IS_ALLOWED_URI$1 = cfg.ALLOWED_URI_REGEXP || IS_ALLOWED_URI;
        NAMESPACE = cfg.NAMESPACE || HTML_NAMESPACE;
        CUSTOM_ELEMENT_HANDLING = cfg.CUSTOM_ELEMENT_HANDLING || {};
        if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck)) {
          CUSTOM_ELEMENT_HANDLING.tagNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.tagNameCheck;
        }
        if (cfg.CUSTOM_ELEMENT_HANDLING && isRegexOrFunction(cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)) {
          CUSTOM_ELEMENT_HANDLING.attributeNameCheck = cfg.CUSTOM_ELEMENT_HANDLING.attributeNameCheck;
        }
        if (cfg.CUSTOM_ELEMENT_HANDLING && typeof cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements === 'boolean') {
          CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements = cfg.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements;
        }
        if (SAFE_FOR_TEMPLATES) {
          ALLOW_DATA_ATTR = false;
        }
        if (RETURN_DOM_FRAGMENT) {
          RETURN_DOM = true;
        }

        /* Parse profile info */
        if (USE_PROFILES) {
          ALLOWED_TAGS = addToSet({}, text);
          ALLOWED_ATTR = [];
          if (USE_PROFILES.html === true) {
            addToSet(ALLOWED_TAGS, html$1);
            addToSet(ALLOWED_ATTR, html);
          }
          if (USE_PROFILES.svg === true) {
            addToSet(ALLOWED_TAGS, svg$1);
            addToSet(ALLOWED_ATTR, svg);
            addToSet(ALLOWED_ATTR, xml);
          }
          if (USE_PROFILES.svgFilters === true) {
            addToSet(ALLOWED_TAGS, svgFilters);
            addToSet(ALLOWED_ATTR, svg);
            addToSet(ALLOWED_ATTR, xml);
          }
          if (USE_PROFILES.mathMl === true) {
            addToSet(ALLOWED_TAGS, mathMl$1);
            addToSet(ALLOWED_ATTR, mathMl);
            addToSet(ALLOWED_ATTR, xml);
          }
        }

        /* Merge configuration parameters */
        if (cfg.ADD_TAGS) {
          if (ALLOWED_TAGS === DEFAULT_ALLOWED_TAGS) {
            ALLOWED_TAGS = clone(ALLOWED_TAGS);
          }
          addToSet(ALLOWED_TAGS, cfg.ADD_TAGS, transformCaseFunc);
        }
        if (cfg.ADD_ATTR) {
          if (ALLOWED_ATTR === DEFAULT_ALLOWED_ATTR) {
            ALLOWED_ATTR = clone(ALLOWED_ATTR);
          }
          addToSet(ALLOWED_ATTR, cfg.ADD_ATTR, transformCaseFunc);
        }
        if (cfg.ADD_URI_SAFE_ATTR) {
          addToSet(URI_SAFE_ATTRIBUTES, cfg.ADD_URI_SAFE_ATTR, transformCaseFunc);
        }
        if (cfg.FORBID_CONTENTS) {
          if (FORBID_CONTENTS === DEFAULT_FORBID_CONTENTS) {
            FORBID_CONTENTS = clone(FORBID_CONTENTS);
          }
          addToSet(FORBID_CONTENTS, cfg.FORBID_CONTENTS, transformCaseFunc);
        }

        /* Add #text in case KEEP_CONTENT is set to true */
        if (KEEP_CONTENT) {
          ALLOWED_TAGS['#text'] = true;
        }

        /* Add html, head and body to ALLOWED_TAGS in case WHOLE_DOCUMENT is true */
        if (WHOLE_DOCUMENT) {
          addToSet(ALLOWED_TAGS, ['html', 'head', 'body']);
        }

        /* Add tbody to ALLOWED_TAGS in case tables are permitted, see #286, #365 */
        if (ALLOWED_TAGS.table) {
          addToSet(ALLOWED_TAGS, ['tbody']);
          delete FORBID_TAGS.tbody;
        }
        if (cfg.TRUSTED_TYPES_POLICY) {
          if (typeof cfg.TRUSTED_TYPES_POLICY.createHTML !== 'function') {
            throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createHTML" hook.');
          }
          if (typeof cfg.TRUSTED_TYPES_POLICY.createScriptURL !== 'function') {
            throw typeErrorCreate('TRUSTED_TYPES_POLICY configuration option must provide a "createScriptURL" hook.');
          }

          // Overwrite existing TrustedTypes policy.
          trustedTypesPolicy = cfg.TRUSTED_TYPES_POLICY;

          // Sign local variables required by `sanitize`.
          emptyHTML = trustedTypesPolicy.createHTML('');
        } else {
          // Uninitialized policy, attempt to initialize the internal dompurify policy.
          if (trustedTypesPolicy === undefined) {
            trustedTypesPolicy = _createTrustedTypesPolicy(trustedTypes, currentScript);
          }

          // If creating the internal policy succeeded sign internal variables.
          if (trustedTypesPolicy !== null && typeof emptyHTML === 'string') {
            emptyHTML = trustedTypesPolicy.createHTML('');
          }
        }

        // Prevent further manipulation of configuration.
        // Not available in IE8, Safari 5, etc.
        if (freeze) {
          freeze(cfg);
        }
        CONFIG = cfg;
      };
      const MATHML_TEXT_INTEGRATION_POINTS = addToSet({}, ['mi', 'mo', 'mn', 'ms', 'mtext']);
      const HTML_INTEGRATION_POINTS = addToSet({}, ['annotation-xml']);

      // Certain elements are allowed in both SVG and HTML
      // namespace. We need to specify them explicitly
      // so that they don't get erroneously deleted from
      // HTML namespace.
      const COMMON_SVG_AND_HTML_ELEMENTS = addToSet({}, ['title', 'style', 'font', 'a', 'script']);

      /* Keep track of all possible SVG and MathML tags
       * so that we can perform the namespace checks
       * correctly. */
      const ALL_SVG_TAGS = addToSet({}, [...svg$1, ...svgFilters, ...svgDisallowed]);
      const ALL_MATHML_TAGS = addToSet({}, [...mathMl$1, ...mathMlDisallowed]);

      /**
       * @param  {Element} element a DOM element whose namespace is being checked
       * @returns {boolean} Return false if the element has a
       *  namespace that a spec-compliant parser would never
       *  return. Return true otherwise.
       */
      const _checkValidNamespace = function _checkValidNamespace(element) {
        let parent = getParentNode(element);

        // In JSDOM, if we're inside shadow DOM, then parentNode
        // can be null. We just simulate parent in this case.
        if (!parent || !parent.tagName) {
          parent = {
            namespaceURI: NAMESPACE,
            tagName: 'template'
          };
        }
        const tagName = stringToLowerCase(element.tagName);
        const parentTagName = stringToLowerCase(parent.tagName);
        if (!ALLOWED_NAMESPACES[element.namespaceURI]) {
          return false;
        }
        if (element.namespaceURI === SVG_NAMESPACE) {
          // The only way to switch from HTML namespace to SVG
          // is via <svg>. If it happens via any other tag, then
          // it should be killed.
          if (parent.namespaceURI === HTML_NAMESPACE) {
            return tagName === 'svg';
          }

          // The only way to switch from MathML to SVG is via`
          // svg if parent is either <annotation-xml> or MathML
          // text integration points.
          if (parent.namespaceURI === MATHML_NAMESPACE) {
            return tagName === 'svg' && (parentTagName === 'annotation-xml' || MATHML_TEXT_INTEGRATION_POINTS[parentTagName]);
          }

          // We only allow elements that are defined in SVG
          // spec. All others are disallowed in SVG namespace.
          return Boolean(ALL_SVG_TAGS[tagName]);
        }
        if (element.namespaceURI === MATHML_NAMESPACE) {
          // The only way to switch from HTML namespace to MathML
          // is via <math>. If it happens via any other tag, then
          // it should be killed.
          if (parent.namespaceURI === HTML_NAMESPACE) {
            return tagName === 'math';
          }

          // The only way to switch from SVG to MathML is via
          // <math> and HTML integration points
          if (parent.namespaceURI === SVG_NAMESPACE) {
            return tagName === 'math' && HTML_INTEGRATION_POINTS[parentTagName];
          }

          // We only allow elements that are defined in MathML
          // spec. All others are disallowed in MathML namespace.
          return Boolean(ALL_MATHML_TAGS[tagName]);
        }
        if (element.namespaceURI === HTML_NAMESPACE) {
          // The only way to switch from SVG to HTML is via
          // HTML integration points, and from MathML to HTML
          // is via MathML text integration points
          if (parent.namespaceURI === SVG_NAMESPACE && !HTML_INTEGRATION_POINTS[parentTagName]) {
            return false;
          }
          if (parent.namespaceURI === MATHML_NAMESPACE && !MATHML_TEXT_INTEGRATION_POINTS[parentTagName]) {
            return false;
          }

          // We disallow tags that are specific for MathML
          // or SVG and should never appear in HTML namespace
          return !ALL_MATHML_TAGS[tagName] && (COMMON_SVG_AND_HTML_ELEMENTS[tagName] || !ALL_SVG_TAGS[tagName]);
        }

        // For XHTML and XML documents that support custom namespaces
        if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && ALLOWED_NAMESPACES[element.namespaceURI]) {
          return true;
        }

        // The code should never reach this place (this means
        // that the element somehow got namespace that is not
        // HTML, SVG, MathML or allowed via ALLOWED_NAMESPACES).
        // Return false just in case.
        return false;
      };

      /**
       * _forceRemove
       *
       * @param  {Node} node a DOM node
       */
      const _forceRemove = function _forceRemove(node) {
        arrayPush(DOMPurify.removed, {
          element: node
        });
        try {
          // eslint-disable-next-line unicorn/prefer-dom-node-remove
          getParentNode(node).removeChild(node);
        } catch (_) {
          remove(node);
        }
      };

      /**
       * _removeAttribute
       *
       * @param  {String} name an Attribute name
       * @param  {Node} node a DOM node
       */
      const _removeAttribute = function _removeAttribute(name, node) {
        try {
          arrayPush(DOMPurify.removed, {
            attribute: node.getAttributeNode(name),
            from: node
          });
        } catch (_) {
          arrayPush(DOMPurify.removed, {
            attribute: null,
            from: node
          });
        }
        node.removeAttribute(name);

        // We void attribute values for unremovable "is"" attributes
        if (name === 'is' && !ALLOWED_ATTR[name]) {
          if (RETURN_DOM || RETURN_DOM_FRAGMENT) {
            try {
              _forceRemove(node);
            } catch (_) {}
          } else {
            try {
              node.setAttribute(name, '');
            } catch (_) {}
          }
        }
      };

      /**
       * _initDocument
       *
       * @param  {String} dirty a string of dirty markup
       * @return {Document} a DOM, filled with the dirty markup
       */
      const _initDocument = function _initDocument(dirty) {
        /* Create a HTML document */
        let doc = null;
        let leadingWhitespace = null;
        if (FORCE_BODY) {
          dirty = '<remove></remove>' + dirty;
        } else {
          /* If FORCE_BODY isn't used, leading whitespace needs to be preserved manually */
          const matches = stringMatch(dirty, /^[\r\n\t ]+/);
          leadingWhitespace = matches && matches[0];
        }
        if (PARSER_MEDIA_TYPE === 'application/xhtml+xml' && NAMESPACE === HTML_NAMESPACE) {
          // Root of XHTML doc must contain xmlns declaration (see https://www.w3.org/TR/xhtml1/normative.html#strict)
          dirty = '<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>' + dirty + '</body></html>';
        }
        const dirtyPayload = trustedTypesPolicy ? trustedTypesPolicy.createHTML(dirty) : dirty;
        /*
         * Use the DOMParser API by default, fallback later if needs be
         * DOMParser not work for svg when has multiple root element.
         */
        if (NAMESPACE === HTML_NAMESPACE) {
          try {
            doc = new DOMParser().parseFromString(dirtyPayload, PARSER_MEDIA_TYPE);
          } catch (_) {}
        }

        /* Use createHTMLDocument in case DOMParser is not available */
        if (!doc || !doc.documentElement) {
          doc = implementation.createDocument(NAMESPACE, 'template', null);
          try {
            doc.documentElement.innerHTML = IS_EMPTY_INPUT ? emptyHTML : dirtyPayload;
          } catch (_) {
            // Syntax error if dirtyPayload is invalid xml
          }
        }
        const body = doc.body || doc.documentElement;
        if (dirty && leadingWhitespace) {
          body.insertBefore(document.createTextNode(leadingWhitespace), body.childNodes[0] || null);
        }

        /* Work on whole document or just its body */
        if (NAMESPACE === HTML_NAMESPACE) {
          return getElementsByTagName.call(doc, WHOLE_DOCUMENT ? 'html' : 'body')[0];
        }
        return WHOLE_DOCUMENT ? doc.documentElement : body;
      };

      /**
       * Creates a NodeIterator object that you can use to traverse filtered lists of nodes or elements in a document.
       *
       * @param  {Node} root The root element or node to start traversing on.
       * @return {NodeIterator} The created NodeIterator
       */
      const _createNodeIterator = function _createNodeIterator(root) {
        return createNodeIterator.call(root.ownerDocument || root, root,
        // eslint-disable-next-line no-bitwise
        NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT | NodeFilter.SHOW_PROCESSING_INSTRUCTION | NodeFilter.SHOW_CDATA_SECTION, null);
      };

      /**
       * _isClobbered
       *
       * @param  {Node} elm element to check for clobbering attacks
       * @return {Boolean} true if clobbered, false if safe
       */
      const _isClobbered = function _isClobbered(elm) {
        return elm instanceof HTMLFormElement && (typeof elm.nodeName !== 'string' || typeof elm.textContent !== 'string' || typeof elm.removeChild !== 'function' || !(elm.attributes instanceof NamedNodeMap) || typeof elm.removeAttribute !== 'function' || typeof elm.setAttribute !== 'function' || typeof elm.namespaceURI !== 'string' || typeof elm.insertBefore !== 'function' || typeof elm.hasChildNodes !== 'function');
      };

      /**
       * Checks whether the given object is a DOM node.
       *
       * @param  {Node} object object to check whether it's a DOM node
       * @return {Boolean} true is object is a DOM node
       */
      const _isNode = function _isNode(object) {
        return typeof Node === 'function' && object instanceof Node;
      };

      /**
       * _executeHook
       * Execute user configurable hooks
       *
       * @param  {String} entryPoint  Name of the hook's entry point
       * @param  {Node} currentNode node to work on with the hook
       * @param  {Object} data additional hook parameters
       */
      const _executeHook = function _executeHook(entryPoint, currentNode, data) {
        if (!hooks[entryPoint]) {
          return;
        }
        arrayForEach(hooks[entryPoint], hook => {
          hook.call(DOMPurify, currentNode, data, CONFIG);
        });
      };

      /**
       * _sanitizeElements
       *
       * @protect nodeName
       * @protect textContent
       * @protect removeChild
       *
       * @param   {Node} currentNode to check for permission to exist
       * @return  {Boolean} true if node was killed, false if left alive
       */
      const _sanitizeElements = function _sanitizeElements(currentNode) {
        let content = null;

        /* Execute a hook if present */
        _executeHook('beforeSanitizeElements', currentNode, null);

        /* Check if element is clobbered or can clobber */
        if (_isClobbered(currentNode)) {
          _forceRemove(currentNode);
          return true;
        }

        /* Now let's check the element's type and name */
        const tagName = transformCaseFunc(currentNode.nodeName);

        /* Execute a hook if present */
        _executeHook('uponSanitizeElement', currentNode, {
          tagName,
          allowedTags: ALLOWED_TAGS
        });

        /* Detect mXSS attempts abusing namespace confusion */
        if (currentNode.hasChildNodes() && !_isNode(currentNode.firstElementChild) && regExpTest(/<[/\w]/g, currentNode.innerHTML) && regExpTest(/<[/\w]/g, currentNode.textContent)) {
          _forceRemove(currentNode);
          return true;
        }

        /* Remove any occurrence of processing instructions */
        if (currentNode.nodeType === NODE_TYPE.progressingInstruction) {
          _forceRemove(currentNode);
          return true;
        }

        /* Remove any kind of possibly harmful comments */
        if (SAFE_FOR_XML && currentNode.nodeType === NODE_TYPE.comment && regExpTest(/<[/\w]/g, currentNode.data)) {
          _forceRemove(currentNode);
          return true;
        }

        /* Remove element if anything forbids its presence */
        if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
          /* Check if we have a custom element to handle */
          if (!FORBID_TAGS[tagName] && _isBasicCustomElement(tagName)) {
            if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, tagName)) {
              return false;
            }
            if (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(tagName)) {
              return false;
            }
          }

          /* Keep content except for bad-listed elements */
          if (KEEP_CONTENT && !FORBID_CONTENTS[tagName]) {
            const parentNode = getParentNode(currentNode) || currentNode.parentNode;
            const childNodes = getChildNodes(currentNode) || currentNode.childNodes;
            if (childNodes && parentNode) {
              const childCount = childNodes.length;
              for (let i = childCount - 1; i >= 0; --i) {
                const childClone = cloneNode(childNodes[i], true);
                childClone.__removalCount = (currentNode.__removalCount || 0) + 1;
                parentNode.insertBefore(childClone, getNextSibling(currentNode));
              }
            }
          }
          _forceRemove(currentNode);
          return true;
        }

        /* Check whether element has a valid namespace */
        if (currentNode instanceof Element && !_checkValidNamespace(currentNode)) {
          _forceRemove(currentNode);
          return true;
        }

        /* Make sure that older browsers don't get fallback-tag mXSS */
        if ((tagName === 'noscript' || tagName === 'noembed' || tagName === 'noframes') && regExpTest(/<\/no(script|embed|frames)/i, currentNode.innerHTML)) {
          _forceRemove(currentNode);
          return true;
        }

        /* Sanitize element content to be template-safe */
        if (SAFE_FOR_TEMPLATES && currentNode.nodeType === NODE_TYPE.text) {
          /* Get the element's text content */
          content = currentNode.textContent;
          arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
            content = stringReplace(content, expr, ' ');
          });
          if (currentNode.textContent !== content) {
            arrayPush(DOMPurify.removed, {
              element: currentNode.cloneNode()
            });
            currentNode.textContent = content;
          }
        }

        /* Execute a hook if present */
        _executeHook('afterSanitizeElements', currentNode, null);
        return false;
      };

      /**
       * _isValidAttribute
       *
       * @param  {string} lcTag Lowercase tag name of containing element.
       * @param  {string} lcName Lowercase attribute name.
       * @param  {string} value Attribute value.
       * @return {Boolean} Returns true if `value` is valid, otherwise false.
       */
      // eslint-disable-next-line complexity
      const _isValidAttribute = function _isValidAttribute(lcTag, lcName, value) {
        /* Make sure attribute cannot clobber */
        if (SANITIZE_DOM && (lcName === 'id' || lcName === 'name') && (value in document || value in formElement)) {
          return false;
        }

        /* Allow valid data-* attributes: At least one character after "-"
            (https://html.spec.whatwg.org/multipage/dom.html#embedding-custom-non-visible-data-with-the-data-*-attributes)
            XML-compatible (https://html.spec.whatwg.org/multipage/infrastructure.html#xml-compatible and http://www.w3.org/TR/xml/#d0e804)
            We don't need to check the value; it's always URI safe. */
        if (ALLOW_DATA_ATTR && !FORBID_ATTR[lcName] && regExpTest(DATA_ATTR, lcName)) ; else if (ALLOW_ARIA_ATTR && regExpTest(ARIA_ATTR, lcName)) ; else if (!ALLOWED_ATTR[lcName] || FORBID_ATTR[lcName]) {
          if (
          // First condition does a very basic check if a) it's basically a valid custom element tagname AND
          // b) if the tagName passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
          // and c) if the attribute name passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.attributeNameCheck
          _isBasicCustomElement(lcTag) && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, lcTag) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(lcTag)) && (CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.attributeNameCheck, lcName) || CUSTOM_ELEMENT_HANDLING.attributeNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.attributeNameCheck(lcName)) ||
          // Alternative, second condition checks if it's an `is`-attribute, AND
          // the value passes whatever the user has configured for CUSTOM_ELEMENT_HANDLING.tagNameCheck
          lcName === 'is' && CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements && (CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof RegExp && regExpTest(CUSTOM_ELEMENT_HANDLING.tagNameCheck, value) || CUSTOM_ELEMENT_HANDLING.tagNameCheck instanceof Function && CUSTOM_ELEMENT_HANDLING.tagNameCheck(value))) ; else {
            return false;
          }
          /* Check value is safe. First, is attr inert? If so, is safe */
        } else if (URI_SAFE_ATTRIBUTES[lcName]) ; else if (regExpTest(IS_ALLOWED_URI$1, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if ((lcName === 'src' || lcName === 'xlink:href' || lcName === 'href') && lcTag !== 'script' && stringIndexOf(value, 'data:') === 0 && DATA_URI_TAGS[lcTag]) ; else if (ALLOW_UNKNOWN_PROTOCOLS && !regExpTest(IS_SCRIPT_OR_DATA, stringReplace(value, ATTR_WHITESPACE, ''))) ; else if (value) {
          return false;
        } else ;
        return true;
      };

      /**
       * _isBasicCustomElement
       * checks if at least one dash is included in tagName, and it's not the first char
       * for more sophisticated checking see https://github.com/sindresorhus/validate-element-name
       *
       * @param {string} tagName name of the tag of the node to sanitize
       * @returns {boolean} Returns true if the tag name meets the basic criteria for a custom element, otherwise false.
       */
      const _isBasicCustomElement = function _isBasicCustomElement(tagName) {
        return tagName !== 'annotation-xml' && stringMatch(tagName, CUSTOM_ELEMENT);
      };

      /**
       * _sanitizeAttributes
       *
       * @protect attributes
       * @protect nodeName
       * @protect removeAttribute
       * @protect setAttribute
       *
       * @param  {Node} currentNode to sanitize
       */
      const _sanitizeAttributes = function _sanitizeAttributes(currentNode) {
        /* Execute a hook if present */
        _executeHook('beforeSanitizeAttributes', currentNode, null);
        const {
          attributes
        } = currentNode;

        /* Check if we have attributes; if not we might have a text node */
        if (!attributes) {
          return;
        }
        const hookEvent = {
          attrName: '',
          attrValue: '',
          keepAttr: true,
          allowedAttributes: ALLOWED_ATTR
        };
        let l = attributes.length;

        /* Go backwards over all attributes; safely remove bad ones */
        while (l--) {
          const attr = attributes[l];
          const {
            name,
            namespaceURI,
            value: attrValue
          } = attr;
          const lcName = transformCaseFunc(name);
          let value = name === 'value' ? attrValue : stringTrim(attrValue);
          const initValue = value;

          /* Execute a hook if present */
          hookEvent.attrName = lcName;
          hookEvent.attrValue = value;
          hookEvent.keepAttr = true;
          hookEvent.forceKeepAttr = undefined; // Allows developers to see this is a property they can set
          _executeHook('uponSanitizeAttribute', currentNode, hookEvent);
          value = hookEvent.attrValue;

          /* Did the hooks approve of the attribute? */
          if (hookEvent.forceKeepAttr) {
            continue;
          }

          /* Did the hooks approve of the attribute? */
          if (!hookEvent.keepAttr) {
            _removeAttribute(name, currentNode);
            continue;
          }

          /* Work around a security issue in jQuery 3.0 */
          if (!ALLOW_SELF_CLOSE_IN_ATTR && regExpTest(/\/>/i, value)) {
            _removeAttribute(name, currentNode);
            continue;
          }

          /* Sanitize attribute content to be template-safe */
          if (SAFE_FOR_TEMPLATES) {
            arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
              value = stringReplace(value, expr, ' ');
            });
          }

          /* Is `value` valid for this attribute? */
          const lcTag = transformCaseFunc(currentNode.nodeName);
          if (!_isValidAttribute(lcTag, lcName, value)) {
            _removeAttribute(name, currentNode);
            continue;
          }

          /* Full DOM Clobbering protection via namespace isolation,
           * Prefix id and name attributes with `user-content-`
           */
          if (SANITIZE_NAMED_PROPS && (lcName === 'id' || lcName === 'name')) {
            // Remove the attribute with this value
            _removeAttribute(name, currentNode);

            // Prefix the value and later re-create the attribute with the sanitized value
            value = SANITIZE_NAMED_PROPS_PREFIX + value;
          }

          /* Work around a security issue with comments inside attributes */
          if (SAFE_FOR_XML && regExpTest(/((--!?|])>)|<\/(style|title)/i, value)) {
            _removeAttribute(name, currentNode);
            continue;
          }

          /* Handle attributes that require Trusted Types */
          if (trustedTypesPolicy && typeof trustedTypes === 'object' && typeof trustedTypes.getAttributeType === 'function') {
            if (namespaceURI) ; else {
              switch (trustedTypes.getAttributeType(lcTag, lcName)) {
                case 'TrustedHTML':
                  {
                    value = trustedTypesPolicy.createHTML(value);
                    break;
                  }
                case 'TrustedScriptURL':
                  {
                    value = trustedTypesPolicy.createScriptURL(value);
                    break;
                  }
              }
            }
          }

          /* Handle invalid data-* attribute set by try-catching it */
          if (value !== initValue) {
            try {
              if (namespaceURI) {
                currentNode.setAttributeNS(namespaceURI, name, value);
              } else {
                /* Fallback to setAttribute() for browser-unrecognized namespaces e.g. "x-schema". */
                currentNode.setAttribute(name, value);
              }
              if (_isClobbered(currentNode)) {
                _forceRemove(currentNode);
              } else {
                arrayPop(DOMPurify.removed);
              }
            } catch (_) {}
          }
        }

        /* Execute a hook if present */
        _executeHook('afterSanitizeAttributes', currentNode, null);
      };

      /**
       * _sanitizeShadowDOM
       *
       * @param  {DocumentFragment} fragment to iterate over recursively
       */
      const _sanitizeShadowDOM = function _sanitizeShadowDOM(fragment) {
        let shadowNode = null;
        const shadowIterator = _createNodeIterator(fragment);

        /* Execute a hook if present */
        _executeHook('beforeSanitizeShadowDOM', fragment, null);
        while (shadowNode = shadowIterator.nextNode()) {
          /* Execute a hook if present */
          _executeHook('uponSanitizeShadowNode', shadowNode, null);

          /* Sanitize tags and elements */
          if (_sanitizeElements(shadowNode)) {
            continue;
          }

          /* Deep shadow DOM detected */
          if (shadowNode.content instanceof DocumentFragment) {
            _sanitizeShadowDOM(shadowNode.content);
          }

          /* Check attributes, sanitize if necessary */
          _sanitizeAttributes(shadowNode);
        }

        /* Execute a hook if present */
        _executeHook('afterSanitizeShadowDOM', fragment, null);
      };

      /**
       * Sanitize
       * Public method providing core sanitation functionality
       *
       * @param {String|Node} dirty string or DOM node
       * @param {Object} cfg object
       */
      // eslint-disable-next-line complexity
      DOMPurify.sanitize = function (dirty) {
        let cfg = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
        let body = null;
        let importedNode = null;
        let currentNode = null;
        let returnNode = null;
        /* Make sure we have a string to sanitize.
          DO NOT return early, as this will return the wrong type if
          the user has requested a DOM object rather than a string */
        IS_EMPTY_INPUT = !dirty;
        if (IS_EMPTY_INPUT) {
          dirty = '<!-->';
        }

        /* Stringify, in case dirty is an object */
        if (typeof dirty !== 'string' && !_isNode(dirty)) {
          if (typeof dirty.toString === 'function') {
            dirty = dirty.toString();
            if (typeof dirty !== 'string') {
              throw typeErrorCreate('dirty is not a string, aborting');
            }
          } else {
            throw typeErrorCreate('toString is not a function');
          }
        }

        /* Return dirty HTML if DOMPurify cannot run */
        if (!DOMPurify.isSupported) {
          return dirty;
        }

        /* Assign config vars */
        if (!SET_CONFIG) {
          _parseConfig(cfg);
        }

        /* Clean up removed elements */
        DOMPurify.removed = [];

        /* Check if dirty is correctly typed for IN_PLACE */
        if (typeof dirty === 'string') {
          IN_PLACE = false;
        }
        if (IN_PLACE) {
          /* Do some early pre-sanitization to avoid unsafe root nodes */
          if (dirty.nodeName) {
            const tagName = transformCaseFunc(dirty.nodeName);
            if (!ALLOWED_TAGS[tagName] || FORBID_TAGS[tagName]) {
              throw typeErrorCreate('root node is forbidden and cannot be sanitized in-place');
            }
          }
        } else if (dirty instanceof Node) {
          /* If dirty is a DOM element, append to an empty document to avoid
             elements being stripped by the parser */
          body = _initDocument('<!---->');
          importedNode = body.ownerDocument.importNode(dirty, true);
          if (importedNode.nodeType === NODE_TYPE.element && importedNode.nodeName === 'BODY') {
            /* Node is already a body, use as is */
            body = importedNode;
          } else if (importedNode.nodeName === 'HTML') {
            body = importedNode;
          } else {
            // eslint-disable-next-line unicorn/prefer-dom-node-append
            body.appendChild(importedNode);
          }
        } else {
          /* Exit directly if we have nothing to do */
          if (!RETURN_DOM && !SAFE_FOR_TEMPLATES && !WHOLE_DOCUMENT &&
          // eslint-disable-next-line unicorn/prefer-includes
          dirty.indexOf('<') === -1) {
            return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(dirty) : dirty;
          }

          /* Initialize the document to work on */
          body = _initDocument(dirty);

          /* Check we have a DOM node from the data */
          if (!body) {
            return RETURN_DOM ? null : RETURN_TRUSTED_TYPE ? emptyHTML : '';
          }
        }

        /* Remove first element node (ours) if FORCE_BODY is set */
        if (body && FORCE_BODY) {
          _forceRemove(body.firstChild);
        }

        /* Get node iterator */
        const nodeIterator = _createNodeIterator(IN_PLACE ? dirty : body);

        /* Now start iterating over the created document */
        while (currentNode = nodeIterator.nextNode()) {
          /* Sanitize tags and elements */
          if (_sanitizeElements(currentNode)) {
            continue;
          }

          /* Shadow DOM detected, sanitize it */
          if (currentNode.content instanceof DocumentFragment) {
            _sanitizeShadowDOM(currentNode.content);
          }

          /* Check attributes, sanitize if necessary */
          _sanitizeAttributes(currentNode);
        }

        /* If we sanitized `dirty` in-place, return it. */
        if (IN_PLACE) {
          return dirty;
        }

        /* Return sanitized string or DOM */
        if (RETURN_DOM) {
          if (RETURN_DOM_FRAGMENT) {
            returnNode = createDocumentFragment.call(body.ownerDocument);
            while (body.firstChild) {
              // eslint-disable-next-line unicorn/prefer-dom-node-append
              returnNode.appendChild(body.firstChild);
            }
          } else {
            returnNode = body;
          }
          if (ALLOWED_ATTR.shadowroot || ALLOWED_ATTR.shadowrootmode) {
            /*
              AdoptNode() is not used because internal state is not reset
              (e.g. the past names map of a HTMLFormElement), this is safe
              in theory but we would rather not risk another attack vector.
              The state that is cloned by importNode() is explicitly defined
              by the specs.
            */
            returnNode = importNode.call(originalDocument, returnNode, true);
          }
          return returnNode;
        }
        let serializedHTML = WHOLE_DOCUMENT ? body.outerHTML : body.innerHTML;

        /* Serialize doctype if allowed */
        if (WHOLE_DOCUMENT && ALLOWED_TAGS['!doctype'] && body.ownerDocument && body.ownerDocument.doctype && body.ownerDocument.doctype.name && regExpTest(DOCTYPE_NAME, body.ownerDocument.doctype.name)) {
          serializedHTML = '<!DOCTYPE ' + body.ownerDocument.doctype.name + '>\n' + serializedHTML;
        }

        /* Sanitize final string template-safe */
        if (SAFE_FOR_TEMPLATES) {
          arrayForEach([MUSTACHE_EXPR, ERB_EXPR, TMPLIT_EXPR], expr => {
            serializedHTML = stringReplace(serializedHTML, expr, ' ');
          });
        }
        return trustedTypesPolicy && RETURN_TRUSTED_TYPE ? trustedTypesPolicy.createHTML(serializedHTML) : serializedHTML;
      };

      /**
       * Public method to set the configuration once
       * setConfig
       *
       * @param {Object} cfg configuration object
       */
      DOMPurify.setConfig = function () {
        let cfg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
        _parseConfig(cfg);
        SET_CONFIG = true;
      };

      /**
       * Public method to remove the configuration
       * clearConfig
       *
       */
      DOMPurify.clearConfig = function () {
        CONFIG = null;
        SET_CONFIG = false;
      };

      /**
       * Public method to check if an attribute value is valid.
       * Uses last set config, if any. Otherwise, uses config defaults.
       * isValidAttribute
       *
       * @param  {String} tag Tag name of containing element.
       * @param  {String} attr Attribute name.
       * @param  {String} value Attribute value.
       * @return {Boolean} Returns true if `value` is valid. Otherwise, returns false.
       */
      DOMPurify.isValidAttribute = function (tag, attr, value) {
        /* Initialize shared config vars if necessary. */
        if (!CONFIG) {
          _parseConfig({});
        }
        const lcTag = transformCaseFunc(tag);
        const lcName = transformCaseFunc(attr);
        return _isValidAttribute(lcTag, lcName, value);
      };

      /**
       * AddHook
       * Public method to add DOMPurify hooks
       *
       * @param {String} entryPoint entry point for the hook to add
       * @param {Function} hookFunction function to execute
       */
      DOMPurify.addHook = function (entryPoint, hookFunction) {
        if (typeof hookFunction !== 'function') {
          return;
        }
        hooks[entryPoint] = hooks[entryPoint] || [];
        arrayPush(hooks[entryPoint], hookFunction);
      };

      /**
       * RemoveHook
       * Public method to remove a DOMPurify hook at a given entryPoint
       * (pops it from the stack of hooks if more are present)
       *
       * @param {String} entryPoint entry point for the hook to remove
       * @return {Function} removed(popped) hook
       */
      DOMPurify.removeHook = function (entryPoint) {
        if (hooks[entryPoint]) {
          return arrayPop(hooks[entryPoint]);
        }
      };

      /**
       * RemoveHooks
       * Public method to remove all DOMPurify hooks at a given entryPoint
       *
       * @param  {String} entryPoint entry point for the hooks to remove
       */
      DOMPurify.removeHooks = function (entryPoint) {
        if (hooks[entryPoint]) {
          hooks[entryPoint] = [];
        }
      };

      /**
       * RemoveAllHooks
       * Public method to remove all DOMPurify hooks
       */
      DOMPurify.removeAllHooks = function () {
        hooks = {};
      };
      return DOMPurify;
    }
    var purify = createDOMPurify();

    const each$4 = Tools.each, trim = Tools.trim;
    const queryParts = [
      'source',
      'protocol',
      'authority',
      'userInfo',
      'user',
      'password',
      'host',
      'port',
      'relative',
      'path',
      'directory',
      'file',
      'query',
      'anchor'
    ];
    const DEFAULT_PORTS = {
      ftp: 21,
      http: 80,
      https: 443,
      mailto: 25
    };
    const safeSvgDataUrlElements = [
      'img',
      'video'
    ];
    const blockSvgDataUris = (allowSvgDataUrls, tagName) => {
      if (isNonNullable(allowSvgDataUrls)) {
        return !allowSvgDataUrls;
      } else {
        return isNonNullable(tagName) ? !contains$2(safeSvgDataUrlElements, tagName) : true;
      }
    };
    const decodeUri = encodedUri => {
      try {
        return decodeURIComponent(encodedUri);
      } catch (ex) {
        return unescape(encodedUri);
      }
    };
    const isInvalidUri = (settings, uri, tagName) => {
      const decodedUri = decodeUri(uri).replace(/\s/g, '');
      if (settings.allow_script_urls) {
        return false;
      } else if (/((java|vb)script|mhtml):/i.test(decodedUri)) {
        return true;
      } else if (settings.allow_html_data_urls) {
        return false;
      } else if (/^data:image\//i.test(decodedUri)) {
        return blockSvgDataUris(settings.allow_svg_data_urls, tagName) && /^data:image\/svg\+xml/i.test(decodedUri);
      } else {
        return /^data:/i.test(decodedUri);
      }
    };
    class URI {
      static parseDataUri(uri) {
        let type;
        const uriComponents = decodeURIComponent(uri).split(',');
        const matches = /data:([^;]+)/.exec(uriComponents[0]);
        if (matches) {
          type = matches[1];
        }
        return {
          type,
          data: uriComponents[1]
        };
      }
      static isDomSafe(uri, context, options = {}) {
        if (options.allow_script_urls) {
          return true;
        } else {
          const decodedUri = Entities.decode(uri).replace(/[\s\u0000-\u001F]+/g, '');
          return !isInvalidUri(options, decodedUri, context);
        }
      }
      static getDocumentBaseUrl(loc) {
        var _a;
        let baseUrl;
        if (loc.protocol.indexOf('http') !== 0 && loc.protocol !== 'file:') {
          baseUrl = (_a = loc.href) !== null && _a !== void 0 ? _a : '';
        } else {
          baseUrl = loc.protocol + '//' + loc.host + loc.pathname;
        }
        if (/^[^:]+:\/\/\/?[^\/]+\//.test(baseUrl)) {
          baseUrl = baseUrl.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
          if (!/[\/\\]$/.test(baseUrl)) {
            baseUrl += '/';
          }
        }
        return baseUrl;
      }
      constructor(url, settings = {}) {
        this.path = '';
        this.directory = '';
        url = trim(url);
        this.settings = settings;
        const baseUri = settings.base_uri;
        const self = this;
        if (/^([\w\-]+):([^\/]{2})/i.test(url) || /^\s*#/.test(url)) {
          self.source = url;
          return;
        }
        const isProtocolRelative = url.indexOf('//') === 0;
        if (url.indexOf('/') === 0 && !isProtocolRelative) {
          url = (baseUri ? baseUri.protocol || 'http' : 'http') + '://mce_host' + url;
        }
        if (!/^[\w\-]*:?\/\//.test(url)) {
          const baseUrl = baseUri ? baseUri.path : new URI(document.location.href).directory;
          if ((baseUri === null || baseUri === void 0 ? void 0 : baseUri.protocol) === '') {
            url = '//mce_host' + self.toAbsPath(baseUrl, url);
          } else {
            const match = /([^#?]*)([#?]?.*)/.exec(url);
            if (match) {
              url = (baseUri && baseUri.protocol || 'http') + '://mce_host' + self.toAbsPath(baseUrl, match[1]) + match[2];
            }
          }
        }
        url = url.replace(/@@/g, '(mce_at)');
        const urlMatch = /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@\/]*):?([^:@\/]*))?@)?(\[[a-zA-Z0-9:.%]+\]|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/.exec(url);
        if (urlMatch) {
          each$4(queryParts, (v, i) => {
            let part = urlMatch[i];
            if (part) {
              part = part.replace(/\(mce_at\)/g, '@@');
            }
            self[v] = part;
          });
        }
        if (baseUri) {
          if (!self.protocol) {
            self.protocol = baseUri.protocol;
          }
          if (!self.userInfo) {
            self.userInfo = baseUri.userInfo;
          }
          if (!self.port && self.host === 'mce_host') {
            self.port = baseUri.port;
          }
          if (!self.host || self.host === 'mce_host') {
            self.host = baseUri.host;
          }
          self.source = '';
        }
        if (isProtocolRelative) {
          self.protocol = '';
        }
      }
      setPath(path) {
        const pathMatch = /^(.*?)\/?(\w+)?$/.exec(path);
        if (pathMatch) {
          this.path = pathMatch[0];
          this.directory = pathMatch[1];
          this.file = pathMatch[2];
        }
        this.source = '';
        this.getURI();
      }
      toRelative(uri) {
        if (uri === './') {
          return uri;
        }
        const relativeUri = new URI(uri, { base_uri: this });
        if (relativeUri.host !== 'mce_host' && this.host !== relativeUri.host && relativeUri.host || this.port !== relativeUri.port || this.protocol !== relativeUri.protocol && relativeUri.protocol !== '') {
          return relativeUri.getURI();
        }
        const tu = this.getURI(), uu = relativeUri.getURI();
        if (tu === uu || tu.charAt(tu.length - 1) === '/' && tu.substr(0, tu.length - 1) === uu) {
          return tu;
        }
        let output = this.toRelPath(this.path, relativeUri.path);
        if (relativeUri.query) {
          output += '?' + relativeUri.query;
        }
        if (relativeUri.anchor) {
          output += '#' + relativeUri.anchor;
        }
        return output;
      }
      toAbsolute(uri, noHost) {
        const absoluteUri = new URI(uri, { base_uri: this });
        return absoluteUri.getURI(noHost && this.isSameOrigin(absoluteUri));
      }
      isSameOrigin(uri) {
        if (this.host == uri.host && this.protocol == uri.protocol) {
          if (this.port == uri.port) {
            return true;
          }
          const defaultPort = this.protocol ? DEFAULT_PORTS[this.protocol] : null;
          if (defaultPort && (this.port || defaultPort) == (uri.port || defaultPort)) {
            return true;
          }
        }
        return false;
      }
      toRelPath(base, path) {
        let breakPoint = 0, out = '', i, l;
        const normalizedBase = base.substring(0, base.lastIndexOf('/')).split('/');
        const items = path.split('/');
        if (normalizedBase.length >= items.length) {
          for (i = 0, l = normalizedBase.length; i < l; i++) {
            if (i >= items.length || normalizedBase[i] !== items[i]) {
              breakPoint = i + 1;
              break;
            }
          }
        }
        if (normalizedBase.length < items.length) {
          for (i = 0, l = items.length; i < l; i++) {
            if (i >= normalizedBase.length || normalizedBase[i] !== items[i]) {
              breakPoint = i + 1;
              break;
            }
          }
        }
        if (breakPoint === 1) {
          return path;
        }
        for (i = 0, l = normalizedBase.length - (breakPoint - 1); i < l; i++) {
          out += '../';
        }
        for (i = breakPoint - 1, l = items.length; i < l; i++) {
          if (i !== breakPoint - 1) {
            out += '/' + items[i];
          } else {
            out += items[i];
          }
        }
        return out;
      }
      toAbsPath(base, path) {
        let nb = 0;
        const tr = /\/$/.test(path) ? '/' : '';
        const normalizedBase = base.split('/');
        const normalizedPath = path.split('/');
        const baseParts = [];
        each$4(normalizedBase, k => {
          if (k) {
            baseParts.push(k);
          }
        });
        const pathParts = [];
        for (let i = normalizedPath.length - 1; i >= 0; i--) {
          if (normalizedPath[i].length === 0 || normalizedPath[i] === '.') {
            continue;
          }
          if (normalizedPath[i] === '..') {
            nb++;
            continue;
          }
          if (nb > 0) {
            nb--;
            continue;
          }
          pathParts.push(normalizedPath[i]);
        }
        const i = baseParts.length - nb;
        let outPath;
        if (i <= 0) {
          outPath = reverse(pathParts).join('/');
        } else {
          outPath = baseParts.slice(0, i).join('/') + '/' + reverse(pathParts).join('/');
        }
        if (outPath.indexOf('/') !== 0) {
          outPath = '/' + outPath;
        }
        if (tr && outPath.lastIndexOf('/') !== outPath.length - 1) {
          outPath += tr;
        }
        return outPath;
      }
      getURI(noProtoHost = false) {
        let s;
        if (!this.source || noProtoHost) {
          s = '';
          if (!noProtoHost) {
            if (this.protocol) {
              s += this.protocol + '://';
            } else {
              s += '//';
            }
            if (this.userInfo) {
              s += this.userInfo + '@';
            }
            if (this.host) {
              s += this.host;
            }
            if (this.port) {
              s += ':' + this.port;
            }
          }
          if (this.path) {
            s += this.path;
          }
          if (this.query) {
            s += '?' + this.query;
          }
          if (this.anchor) {
            s += '#' + this.anchor;
          }
          this.source = s;
        }
        return this.source;
      }
    }

    const filteredUrlAttrs = Tools.makeMap('src,href,data,background,action,formaction,poster,xlink:href');
    const internalElementAttr = 'data-mce-type';
    let uid = 0;
    const processNode = (node, settings, schema, scope, evt) => {
      var _a, _b, _c, _d;
      const validate = settings.validate;
      const specialElements = schema.getSpecialElements();
      if (node.nodeType === COMMENT && !settings.allow_conditional_comments && /^\[if/i.test((_a = node.nodeValue) !== null && _a !== void 0 ? _a : '')) {
        node.nodeValue = ' ' + node.nodeValue;
      }
      const lcTagName = (_b = evt === null || evt === void 0 ? void 0 : evt.tagName) !== null && _b !== void 0 ? _b : node.nodeName.toLowerCase();
      if (scope !== 'html' && schema.isValid(scope)) {
        if (isNonNullable(evt)) {
          evt.allowedTags[lcTagName] = true;
        }
        return;
      }
      if (node.nodeType !== ELEMENT || lcTagName === 'body') {
        return;
      }
      const element = SugarElement.fromDom(node);
      const isInternalElement = has$1(element, internalElementAttr);
      const bogus = get$9(element, 'data-mce-bogus');
      if (!isInternalElement && isString(bogus)) {
        if (bogus === 'all') {
          remove$5(element);
        } else {
          unwrap(element);
        }
        return;
      }
      const rule = schema.getElementRule(lcTagName);
      if (validate && !rule) {
        if (has$2(specialElements, lcTagName)) {
          remove$5(element);
        } else {
          unwrap(element);
        }
        return;
      } else {
        if (isNonNullable(evt)) {
          evt.allowedTags[lcTagName] = true;
        }
      }
      if (validate && rule && !isInternalElement) {
        each$e((_c = rule.attributesForced) !== null && _c !== void 0 ? _c : [], attr => {
          set$3(element, attr.name, attr.value === '{$uid}' ? `mce_${ uid++ }` : attr.value);
        });
        each$e((_d = rule.attributesDefault) !== null && _d !== void 0 ? _d : [], attr => {
          if (!has$1(element, attr.name)) {
            set$3(element, attr.name, attr.value === '{$uid}' ? `mce_${ uid++ }` : attr.value);
          }
        });
        if (rule.attributesRequired && !exists(rule.attributesRequired, attr => has$1(element, attr))) {
          unwrap(element);
          return;
        }
        if (rule.removeEmptyAttrs && hasNone(element)) {
          unwrap(element);
          return;
        }
        if (rule.outputName && rule.outputName !== lcTagName) {
          mutate(element, rule.outputName);
        }
      }
    };
    const processAttr = (ele, settings, schema, scope, evt) => {
      const tagName = ele.tagName.toLowerCase();
      const {attrName, attrValue} = evt;
      evt.keepAttr = shouldKeepAttribute(settings, schema, scope, tagName, attrName, attrValue);
      if (evt.keepAttr) {
        evt.allowedAttributes[attrName] = true;
        if (isBooleanAttribute(attrName, schema)) {
          evt.attrValue = attrName;
        }
        if (settings.allow_svg_data_urls && startsWith(attrValue, 'data:image/svg+xml')) {
          evt.forceKeepAttr = true;
        }
      } else if (isRequiredAttributeOfInternalElement(ele, attrName)) {
        evt.forceKeepAttr = true;
      }
    };
    const shouldKeepAttribute = (settings, schema, scope, tagName, attrName, attrValue) => {
      if (scope !== 'html' && !isNonHtmlElementRootName(tagName)) {
        return true;
      }
      return !(attrName in filteredUrlAttrs && isInvalidUri(settings, attrValue, tagName)) && (!settings.validate || schema.isValid(tagName, attrName) || startsWith(attrName, 'data-') || startsWith(attrName, 'aria-'));
    };
    const isRequiredAttributeOfInternalElement = (ele, attrName) => ele.hasAttribute(internalElementAttr) && (attrName === 'id' || attrName === 'class' || attrName === 'style');
    const isBooleanAttribute = (attrName, schema) => attrName in schema.getBoolAttrs();
    const filterAttributes = (ele, settings, schema, scope) => {
      const {attributes} = ele;
      for (let i = attributes.length - 1; i >= 0; i--) {
        const attr = attributes[i];
        const attrName = attr.name;
        const attrValue = attr.value;
        if (!shouldKeepAttribute(settings, schema, scope, ele.tagName.toLowerCase(), attrName, attrValue) && !isRequiredAttributeOfInternalElement(ele, attrName)) {
          ele.removeAttribute(attrName);
        } else if (isBooleanAttribute(attrName, schema)) {
          ele.setAttribute(attrName, attrName);
        }
      }
    };
    const setupPurify = (settings, schema, namespaceTracker) => {
      const purify$1 = purify();
      purify$1.addHook('uponSanitizeElement', (ele, evt) => {
        processNode(ele, settings, schema, namespaceTracker.track(ele), evt);
      });
      purify$1.addHook('uponSanitizeAttribute', (ele, evt) => {
        processAttr(ele, settings, schema, namespaceTracker.current(), evt);
      });
      return purify$1;
    };
    const getPurifyConfig = (settings, mimeType) => {
      const basePurifyConfig = {
        IN_PLACE: true,
        ALLOW_UNKNOWN_PROTOCOLS: true,
        ALLOWED_TAGS: [
          '#comment',
          '#cdata-section',
          'body'
        ],
        ALLOWED_ATTR: [],
        SAFE_FOR_XML: false
      };
      const config = { ...basePurifyConfig };
      config.PARSER_MEDIA_TYPE = mimeType;
      if (settings.allow_script_urls) {
        config.ALLOWED_URI_REGEXP = /.*/;
      } else if (settings.allow_html_data_urls) {
        config.ALLOWED_URI_REGEXP = /^(?!(\w+script|mhtml):)/i;
      }
      return config;
    };
    const sanitizeNamespaceElement = ele => {
      const xlinkAttrs = [
        'type',
        'href',
        'role',
        'arcrole',
        'title',
        'show',
        'actuate',
        'label',
        'from',
        'to'
      ].map(name => `xlink:${ name }`);
      const config = {
        IN_PLACE: true,
        USE_PROFILES: {
          html: true,
          svg: true,
          svgFilters: true
        },
        ALLOWED_ATTR: xlinkAttrs
      };
      purify().sanitize(ele, config);
      return ele.innerHTML;
    };
    const getSanitizer = (settings, schema) => {
      const namespaceTracker = createNamespaceTracker();
      if (settings.sanitize) {
        const purify = setupPurify(settings, schema, namespaceTracker);
        const sanitizeHtmlElement = (body, mimeType) => {
          purify.sanitize(body, getPurifyConfig(settings, mimeType));
          purify.removed = [];
          namespaceTracker.reset();
        };
        return {
          sanitizeHtmlElement,
          sanitizeNamespaceElement
        };
      } else {
        const sanitizeHtmlElement = (body, _mimeType) => {
          const nodeIterator = document.createNodeIterator(body, NodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_COMMENT | NodeFilter.SHOW_TEXT);
          let node;
          while (node = nodeIterator.nextNode()) {
            const currentScope = namespaceTracker.track(node);
            processNode(node, settings, schema, currentScope);
            if (isElement$6(node)) {
              filterAttributes(node, settings, schema, currentScope);
            }
          }
          namespaceTracker.reset();
        };
        const sanitizeNamespaceElement = noop;
        return {
          sanitizeHtmlElement,
          sanitizeNamespaceElement
        };
      }
    };

    const makeMap = Tools.makeMap, extend$1 = Tools.extend;
    const transferChildren = (parent, nativeParent, specialElements, nsSanitizer) => {
      const parentName = parent.name;
      const isSpecial = parentName in specialElements && parentName !== 'title' && parentName !== 'textarea' && parentName !== 'noscript';
      const childNodes = nativeParent.childNodes;
      for (let ni = 0, nl = childNodes.length; ni < nl; ni++) {
        const nativeChild = childNodes[ni];
        const child = new AstNode(nativeChild.nodeName.toLowerCase(), nativeChild.nodeType);
        if (isElement$6(nativeChild)) {
          const attributes = nativeChild.attributes;
          for (let ai = 0, al = attributes.length; ai < al; ai++) {
            const attr = attributes[ai];
            child.attr(attr.name, attr.value);
          }
          if (isNonHtmlElementRootName(child.name)) {
            nsSanitizer(nativeChild);
            child.value = nativeChild.innerHTML;
          }
        } else if (isText$a(nativeChild)) {
          child.value = nativeChild.data;
          if (isSpecial) {
            child.raw = true;
          }
        } else if (isComment(nativeChild) || isCData(nativeChild) || isPi(nativeChild)) {
          child.value = nativeChild.data;
        }
        if (!isNonHtmlElementRootName(child.name)) {
          transferChildren(child, nativeChild, specialElements, nsSanitizer);
        }
        parent.append(child);
      }
    };
    const walkTree = (root, preprocessors, postprocessors) => {
      const traverseOrder = [];
      for (let node = root, lastNode = node; node; lastNode = node, node = node.walk()) {
        const tempNode = node;
        each$e(preprocessors, preprocess => preprocess(tempNode));
        if (isNullable(tempNode.parent) && tempNode !== root) {
          node = lastNode;
        } else {
          traverseOrder.push(tempNode);
        }
      }
      for (let i = traverseOrder.length - 1; i >= 0; i--) {
        const node = traverseOrder[i];
        each$e(postprocessors, postprocess => postprocess(node));
      }
    };
    const whitespaceCleaner = (root, schema, settings, args) => {
      const validate = settings.validate;
      const nonEmptyElements = schema.getNonEmptyElements();
      const whitespaceElements = schema.getWhitespaceElements();
      const blockElements = extend$1(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
      const textRootBlockElements = getTextRootBlockElements(schema);
      const allWhiteSpaceRegExp = /[ \t\r\n]+/g;
      const startWhiteSpaceRegExp = /^[ \t\r\n]+/;
      const endWhiteSpaceRegExp = /[ \t\r\n]+$/;
      const hasWhitespaceParent = node => {
        let tempNode = node.parent;
        while (isNonNullable(tempNode)) {
          if (tempNode.name in whitespaceElements) {
            return true;
          } else {
            tempNode = tempNode.parent;
          }
        }
        return false;
      };
      const isTextRootBlockEmpty = node => {
        let tempNode = node;
        while (isNonNullable(tempNode)) {
          if (tempNode.name in textRootBlockElements) {
            return isEmpty(schema, nonEmptyElements, whitespaceElements, tempNode);
          } else {
            tempNode = tempNode.parent;
          }
        }
        return false;
      };
      const isBlock = node => node.name in blockElements || isTransparentAstBlock(schema, node) || isNonHtmlElementRootName(node.name) && node.parent === root;
      const isAtEdgeOfBlock = (node, start) => {
        const neighbour = start ? node.prev : node.next;
        if (isNonNullable(neighbour) || isNullable(node.parent)) {
          return false;
        }
        return isBlock(node.parent) && (node.parent !== root || args.isRootContent === true);
      };
      const preprocess = node => {
        var _a;
        if (node.type === 3) {
          if (!hasWhitespaceParent(node)) {
            let text = (_a = node.value) !== null && _a !== void 0 ? _a : '';
            text = text.replace(allWhiteSpaceRegExp, ' ');
            if (isLineBreakNode(node.prev, isBlock) || isAtEdgeOfBlock(node, true)) {
              text = text.replace(startWhiteSpaceRegExp, '');
            }
            if (text.length === 0) {
              node.remove();
            } else {
              node.value = text;
            }
          }
        }
      };
      const postprocess = node => {
        var _a;
        if (node.type === 1) {
          const elementRule = schema.getElementRule(node.name);
          if (validate && elementRule) {
            const isNodeEmpty = isEmpty(schema, nonEmptyElements, whitespaceElements, node);
            if (elementRule.paddInEmptyBlock && isNodeEmpty && isTextRootBlockEmpty(node)) {
              paddEmptyNode(settings, args, isBlock, node);
            } else if (elementRule.removeEmpty && isNodeEmpty) {
              if (isBlock(node)) {
                node.remove();
              } else {
                node.unwrap();
              }
            } else if (elementRule.paddEmpty && (isNodeEmpty || isPaddedWithNbsp(node))) {
              paddEmptyNode(settings, args, isBlock, node);
            }
          }
        } else if (node.type === 3) {
          if (!hasWhitespaceParent(node)) {
            let text = (_a = node.value) !== null && _a !== void 0 ? _a : '';
            if (node.next && isBlock(node.next) || isAtEdgeOfBlock(node, false)) {
              text = text.replace(endWhiteSpaceRegExp, '');
            }
            if (text.length === 0) {
              node.remove();
            } else {
              node.value = text;
            }
          }
        }
      };
      return [
        preprocess,
        postprocess
      ];
    };
    const getRootBlockName = (settings, args) => {
      var _a;
      const name = (_a = args.forced_root_block) !== null && _a !== void 0 ? _a : settings.forced_root_block;
      if (name === false) {
        return '';
      } else if (name === true) {
        return 'p';
      } else {
        return name;
      }
    };
    const DomParser = (settings = {}, schema = Schema()) => {
      const nodeFilterRegistry = create$8();
      const attributeFilterRegistry = create$8();
      const defaultedSettings = {
        validate: true,
        root_name: 'body',
        sanitize: true,
        ...settings
      };
      const parser = new DOMParser();
      const sanitizer = getSanitizer(defaultedSettings, schema);
      const parseAndSanitizeWithContext = (html, rootName, format = 'html') => {
        const mimeType = format === 'xhtml' ? 'application/xhtml+xml' : 'text/html';
        const isSpecialRoot = has$2(schema.getSpecialElements(), rootName.toLowerCase());
        const content = isSpecialRoot ? `<${ rootName }>${ html }</${ rootName }>` : html;
        const wrappedHtml = format === 'xhtml' ? `<html xmlns="http://www.w3.org/1999/xhtml"><head></head><body>${ content }</body></html>` : `<body>${ content }</body>`;
        const body = parser.parseFromString(wrappedHtml, mimeType).body;
        sanitizer.sanitizeHtmlElement(body, mimeType);
        return isSpecialRoot ? body.firstChild : body;
      };
      const addNodeFilter = nodeFilterRegistry.addFilter;
      const getNodeFilters = nodeFilterRegistry.getFilters;
      const removeNodeFilter = nodeFilterRegistry.removeFilter;
      const addAttributeFilter = attributeFilterRegistry.addFilter;
      const getAttributeFilters = attributeFilterRegistry.getFilters;
      const removeAttributeFilter = attributeFilterRegistry.removeFilter;
      const findInvalidChildren = (node, invalidChildren) => {
        if (isInvalid(schema, node)) {
          invalidChildren.push(node);
        }
      };
      const isWrappableNode = (blockElements, node) => {
        const isInternalElement = isString(node.attr(internalElementAttr));
        const isInlineElement = node.type === 1 && (!has$2(blockElements, node.name) && !isTransparentAstBlock(schema, node)) && !isNonHtmlElementRootName(node.name);
        return node.type === 3 || isInlineElement && !isInternalElement;
      };
      const addRootBlocks = (rootNode, rootBlockName) => {
        const blockElements = extend$1(makeMap('script,style,head,html,body,title,meta,param'), schema.getBlockElements());
        const startWhiteSpaceRegExp = /^[ \t\r\n]+/;
        const endWhiteSpaceRegExp = /[ \t\r\n]+$/;
        let node = rootNode.firstChild, rootBlockNode = null;
        const trim = rootBlock => {
          var _a, _b;
          if (rootBlock) {
            node = rootBlock.firstChild;
            if (node && node.type === 3) {
              node.value = (_a = node.value) === null || _a === void 0 ? void 0 : _a.replace(startWhiteSpaceRegExp, '');
            }
            node = rootBlock.lastChild;
            if (node && node.type === 3) {
              node.value = (_b = node.value) === null || _b === void 0 ? void 0 : _b.replace(endWhiteSpaceRegExp, '');
            }
          }
        };
        if (!schema.isValidChild(rootNode.name, rootBlockName.toLowerCase())) {
          return;
        }
        while (node) {
          const next = node.next;
          if (isWrappableNode(blockElements, node)) {
            if (!rootBlockNode) {
              rootBlockNode = new AstNode(rootBlockName, 1);
              rootBlockNode.attr(defaultedSettings.forced_root_block_attrs);
              rootNode.insert(rootBlockNode, node);
              rootBlockNode.append(node);
            } else {
              rootBlockNode.append(node);
            }
          } else {
            trim(rootBlockNode);
            rootBlockNode = null;
          }
          node = next;
        }
        trim(rootBlockNode);
      };
      const parse = (html, args = {}) => {
        var _a;
        const validate = defaultedSettings.validate;
        const rootName = (_a = args.context) !== null && _a !== void 0 ? _a : defaultedSettings.root_name;
        const element = parseAndSanitizeWithContext(html, rootName, args.format);
        updateChildren(schema, element);
        const rootNode = new AstNode(rootName, 11);
        transferChildren(rootNode, element, schema.getSpecialElements(), sanitizer.sanitizeNamespaceElement);
        element.innerHTML = '';
        const [whitespacePre, whitespacePost] = whitespaceCleaner(rootNode, schema, defaultedSettings, args);
        const invalidChildren = [];
        const invalidFinder = validate ? node => findInvalidChildren(node, invalidChildren) : noop;
        const matches = {
          nodes: {},
          attributes: {}
        };
        const matchFinder = node => matchNode$1(getNodeFilters(), getAttributeFilters(), node, matches);
        walkTree(rootNode, [
          whitespacePre,
          matchFinder
        ], [
          whitespacePost,
          invalidFinder
        ]);
        invalidChildren.reverse();
        if (validate && invalidChildren.length > 0) {
          if (args.context) {
            const {
              pass: topLevelChildren,
              fail: otherChildren
            } = partition$2(invalidChildren, child => child.parent === rootNode);
            cleanInvalidNodes(otherChildren, schema, rootNode, matchFinder);
            args.invalid = topLevelChildren.length > 0;
          } else {
            cleanInvalidNodes(invalidChildren, schema, rootNode, matchFinder);
          }
        }
        const rootBlockName = getRootBlockName(defaultedSettings, args);
        if (rootBlockName && (rootNode.name === 'body' || args.isRootContent)) {
          addRootBlocks(rootNode, rootBlockName);
        }
        if (!args.invalid) {
          runFilters(matches, args);
        }
        return rootNode;
      };
      const exports = {
        schema,
        addAttributeFilter,
        getAttributeFilters,
        removeAttributeFilter,
        addNodeFilter,
        getNodeFilters,
        removeNodeFilter,
        parse
      };
      register$4(exports, defaultedSettings);
      register$5(exports, defaultedSettings, schema);
      return exports;
    };

    const serializeContent = content => isTreeNode(content) ? HtmlSerializer({ validate: false }).serialize(content) : content;
    const withSerializedContent = (content, fireEvent, parserSettings) => {
      const serializedContent = serializeContent(content);
      const eventArgs = fireEvent(serializedContent);
      if (eventArgs.isDefaultPrevented()) {
        return eventArgs;
      } else if (isTreeNode(content)) {
        if (eventArgs.content !== serializedContent) {
          const rootNode = DomParser({
            validate: false,
            forced_root_block: false,
            ...parserSettings
          }).parse(eventArgs.content, { context: content.name });
          return {
            ...eventArgs,
            content: rootNode
          };
        } else {
          return {
            ...eventArgs,
            content
          };
        }
      } else {
        return eventArgs;
      }
    };
    const preProcessGetContent = (editor, args) => {
      if (args.no_events) {
        return Result.value(args);
      } else {
        const eventArgs = fireBeforeGetContent(editor, args);
        if (eventArgs.isDefaultPrevented()) {
          return Result.error(fireGetContent(editor, {
            content: '',
            ...eventArgs
          }).content);
        } else {
          return Result.value(eventArgs);
        }
      }
    };
    const postProcessGetContent = (editor, content, args) => {
      if (args.no_events) {
        return content;
      } else {
        const processedEventArgs = withSerializedContent(content, content => fireGetContent(editor, {
          ...args,
          content
        }), {
          sanitize: shouldSanitizeXss(editor),
          sandbox_iframes: shouldSandboxIframes(editor)
        });
        return processedEventArgs.content;
      }
    };
    const preProcessSetContent = (editor, args) => {
      if (args.no_events) {
        return Result.value(args);
      } else {
        const processedEventArgs = withSerializedContent(args.content, content => fireBeforeSetContent(editor, {
          ...args,
          content
        }), {
          sanitize: shouldSanitizeXss(editor),
          sandbox_iframes: shouldSandboxIframes(editor)
        });
        if (processedEventArgs.isDefaultPrevented()) {
          fireSetContent(editor, processedEventArgs);
          return Result.error(undefined);
        } else {
          return Result.value(processedEventArgs);
        }
      }
    };
    const postProcessSetContent = (editor, content, args) => {
      if (!args.no_events) {
        fireSetContent(editor, {
          ...args,
          content
        });
      }
    };

    const tableModel = (element, width, rows) => ({
      element,
      width,
      rows
    });
    const tableRow = (element, cells) => ({
      element,
      cells
    });
    const cellPosition = (x, y) => ({
      x,
      y
    });
    const getSpan = (td, key) => {
      return getOpt(td, key).bind(toInt).getOr(1);
    };
    const fillout = (table, x, y, tr, td) => {
      const rowspan = getSpan(td, 'rowspan');
      const colspan = getSpan(td, 'colspan');
      const rows = table.rows;
      for (let y2 = y; y2 < y + rowspan; y2++) {
        if (!rows[y2]) {
          rows[y2] = tableRow(deep$1(tr), []);
        }
        for (let x2 = x; x2 < x + colspan; x2++) {
          const cells = rows[y2].cells;
          cells[x2] = y2 === y && x2 === x ? td : shallow$1(td);
        }
      }
    };
    const cellExists = (table, x, y) => {
      const rows = table.rows;
      const cells = rows[y] ? rows[y].cells : [];
      return !!cells[x];
    };
    const skipCellsX = (table, x, y) => {
      while (cellExists(table, x, y)) {
        x++;
      }
      return x;
    };
    const getWidth = rows => {
      return foldl(rows, (acc, row) => {
        return row.cells.length > acc ? row.cells.length : acc;
      }, 0);
    };
    const findElementPos = (table, element) => {
      const rows = table.rows;
      for (let y = 0; y < rows.length; y++) {
        const cells = rows[y].cells;
        for (let x = 0; x < cells.length; x++) {
          if (eq(cells[x], element)) {
            return Optional.some(cellPosition(x, y));
          }
        }
      }
      return Optional.none();
    };
    const extractRows = (table, sx, sy, ex, ey) => {
      const newRows = [];
      const rows = table.rows;
      for (let y = sy; y <= ey; y++) {
        const cells = rows[y].cells;
        const slice = sx < ex ? cells.slice(sx, ex + 1) : cells.slice(ex, sx + 1);
        newRows.push(tableRow(rows[y].element, slice));
      }
      return newRows;
    };
    const subTable = (table, startPos, endPos) => {
      const sx = startPos.x, sy = startPos.y;
      const ex = endPos.x, ey = endPos.y;
      const newRows = sy < ey ? extractRows(table, sx, sy, ex, ey) : extractRows(table, sx, ey, ex, sy);
      return tableModel(table.element, getWidth(newRows), newRows);
    };
    const createDomTable = (table, rows) => {
      const tableElement = shallow$1(table.element);
      const tableBody = SugarElement.fromTag('tbody');
      append(tableBody, rows);
      append$1(tableElement, tableBody);
      return tableElement;
    };
    const modelRowsToDomRows = table => {
      return map$3(table.rows, row => {
        const cells = map$3(row.cells, cell => {
          const td = deep$1(cell);
          remove$a(td, 'colspan');
          remove$a(td, 'rowspan');
          return td;
        });
        const tr = shallow$1(row.element);
        append(tr, cells);
        return tr;
      });
    };
    const fromDom = tableElm => {
      const table = tableModel(shallow$1(tableElm), 0, []);
      each$e(descendants(tableElm, 'tr'), (tr, y) => {
        each$e(descendants(tr, 'td,th'), (td, x) => {
          fillout(table, skipCellsX(table, x, y), y, tr, td);
        });
      });
      return tableModel(table.element, getWidth(table.rows), table.rows);
    };
    const toDom = table => {
      return createDomTable(table, modelRowsToDomRows(table));
    };
    const subsection = (table, startElement, endElement) => {
      return findElementPos(table, startElement).bind(startPos => {
        return findElementPos(table, endElement).map(endPos => {
          return subTable(table, startPos, endPos);
        });
      });
    };

    const findParentListContainer = parents => find$2(parents, elm => name(elm) === 'ul' || name(elm) === 'ol');
    const getFullySelectedListWrappers = (parents, rng) => find$2(parents, elm => name(elm) === 'li' && hasAllContentsSelected(elm, rng)).fold(constant([]), _li => findParentListContainer(parents).map(listCont => {
      const listElm = SugarElement.fromTag(name(listCont));
      const listStyles = filter$4(getAllRaw(listCont), (_style, name) => startsWith(name, 'list-style'));
      setAll(listElm, listStyles);
      return [
        SugarElement.fromTag('li'),
        listElm
      ];
    }).getOr([]));
    const wrap = (innerElm, elms) => {
      const wrapped = foldl(elms, (acc, elm) => {
        append$1(elm, acc);
        return elm;
      }, innerElm);
      return elms.length > 0 ? fromElements([wrapped]) : wrapped;
    };
    const directListWrappers = commonAnchorContainer => {
      if (isListItem$1(commonAnchorContainer)) {
        return parent(commonAnchorContainer).filter(isList).fold(constant([]), listElm => [
          commonAnchorContainer,
          listElm
        ]);
      } else {
        return isList(commonAnchorContainer) ? [commonAnchorContainer] : [];
      }
    };
    const getWrapElements = (rootNode, rng, schema) => {
      const commonAnchorContainer = SugarElement.fromDom(rng.commonAncestorContainer);
      const parents = parentsAndSelf(commonAnchorContainer, rootNode);
      const wrapElements = filter$5(parents, el => schema.isWrapper(name(el)));
      const listWrappers = getFullySelectedListWrappers(parents, rng);
      const allWrappers = wrapElements.concat(listWrappers.length ? listWrappers : directListWrappers(commonAnchorContainer));
      return map$3(allWrappers, shallow$1);
    };
    const emptyFragment = () => fromElements([]);
    const getFragmentFromRange = (rootNode, rng, schema) => wrap(SugarElement.fromDom(rng.cloneContents()), getWrapElements(rootNode, rng, schema));
    const getParentTable = (rootElm, cell) => ancestor$3(cell, 'table', curry(eq, rootElm));
    const getTableFragment = (rootNode, selectedTableCells) => getParentTable(rootNode, selectedTableCells[0]).bind(tableElm => {
      const firstCell = selectedTableCells[0];
      const lastCell = selectedTableCells[selectedTableCells.length - 1];
      const fullTableModel = fromDom(tableElm);
      return subsection(fullTableModel, firstCell, lastCell).map(sectionedTableModel => fromElements([toDom(sectionedTableModel)]));
    }).getOrThunk(emptyFragment);
    const getSelectionFragment = (rootNode, ranges, schema) => ranges.length > 0 && ranges[0].collapsed ? emptyFragment() : getFragmentFromRange(rootNode, ranges[0], schema);
    const read$3 = (rootNode, ranges, schema) => {
      const selectedCells = getCellsFromElementOrRanges(ranges, rootNode);
      return selectedCells.length > 0 ? getTableFragment(rootNode, selectedCells) : getSelectionFragment(rootNode, ranges, schema);
    };

    const isCollapsibleWhitespace = (text, index) => index >= 0 && index < text.length && isWhiteSpace(text.charAt(index));
    const getInnerText = bin => {
      return trim$2(bin.innerText);
    };
    const getContextNodeName = parentBlockOpt => parentBlockOpt.map(block => block.nodeName).getOr('div').toLowerCase();
    const getTextContent = editor => Optional.from(editor.selection.getRng()).map(rng => {
      var _a;
      const parentBlockOpt = Optional.from(editor.dom.getParent(rng.commonAncestorContainer, editor.dom.isBlock));
      const body = editor.getBody();
      const contextNodeName = getContextNodeName(parentBlockOpt);
      const rangeContentClone = SugarElement.fromDom(rng.cloneContents());
      cleanupBogusElements(rangeContentClone);
      cleanupInputNames(rangeContentClone);
      const bin = editor.dom.add(body, contextNodeName, {
        'data-mce-bogus': 'all',
        'style': 'overflow: hidden; opacity: 0;'
      }, rangeContentClone.dom);
      const text = getInnerText(bin);
      const nonRenderedText = trim$2((_a = bin.textContent) !== null && _a !== void 0 ? _a : '');
      editor.dom.remove(bin);
      if (isCollapsibleWhitespace(nonRenderedText, 0) || isCollapsibleWhitespace(nonRenderedText, nonRenderedText.length - 1)) {
        const parentBlock = parentBlockOpt.getOr(body);
        const parentBlockText = getInnerText(parentBlock);
        const textIndex = parentBlockText.indexOf(text);
        if (textIndex === -1) {
          return text;
        } else {
          const hasProceedingSpace = isCollapsibleWhitespace(parentBlockText, textIndex - 1);
          const hasTrailingSpace = isCollapsibleWhitespace(parentBlockText, textIndex + text.length);
          return (hasProceedingSpace ? ' ' : '') + text + (hasTrailingSpace ? ' ' : '');
        }
      } else {
        return text;
      }
    }).getOr('');
    const getSerializedContent = (editor, args) => {
      const rng = editor.selection.getRng(), tmpElm = editor.dom.create('body');
      const sel = editor.selection.getSel();
      const ranges = processRanges(editor, getRanges$1(sel));
      const fragment = args.contextual ? read$3(SugarElement.fromDom(editor.getBody()), ranges, editor.schema).dom : rng.cloneContents();
      if (fragment) {
        tmpElm.appendChild(fragment);
      }
      return editor.selection.serializer.serialize(tmpElm, args);
    };
    const extractSelectedContent = (editor, args) => {
      if (args.format === 'text') {
        return getTextContent(editor);
      } else {
        const content = getSerializedContent(editor, args);
        if (args.format === 'tree') {
          return content;
        } else {
          return editor.selection.isCollapsed() ? '' : content;
        }
      }
    };
    const setupArgs$3 = (args, format) => ({
      ...args,
      format,
      get: true,
      selection: true,
      getInner: true
    });
    const getSelectedContentInternal = (editor, format, args = {}) => {
      const defaultedArgs = setupArgs$3(args, format);
      return preProcessGetContent(editor, defaultedArgs).fold(identity, updatedArgs => {
        const content = extractSelectedContent(editor, updatedArgs);
        return postProcessGetContent(editor, content, updatedArgs);
      });
    };

    const KEEP = 0, INSERT = 1, DELETE = 2;
    const diff = (left, right) => {
      const size = left.length + right.length + 2;
      const vDown = new Array(size);
      const vUp = new Array(size);
      const snake = (start, end, diag) => {
        return {
          start,
          end,
          diag
        };
      };
      const buildScript = (start1, end1, start2, end2, script) => {
        const middle = getMiddleSnake(start1, end1, start2, end2);
        if (middle === null || middle.start === end1 && middle.diag === end1 - end2 || middle.end === start1 && middle.diag === start1 - start2) {
          let i = start1;
          let j = start2;
          while (i < end1 || j < end2) {
            if (i < end1 && j < end2 && left[i] === right[j]) {
              script.push([
                KEEP,
                left[i]
              ]);
              ++i;
              ++j;
            } else {
              if (end1 - start1 > end2 - start2) {
                script.push([
                  DELETE,
                  left[i]
                ]);
                ++i;
              } else {
                script.push([
                  INSERT,
                  right[j]
                ]);
                ++j;
              }
            }
          }
        } else {
          buildScript(start1, middle.start, start2, middle.start - middle.diag, script);
          for (let i2 = middle.start; i2 < middle.end; ++i2) {
            script.push([
              KEEP,
              left[i2]
            ]);
          }
          buildScript(middle.end, end1, middle.end - middle.diag, end2, script);
        }
      };
      const buildSnake = (start, diag, end1, end2) => {
        let end = start;
        while (end - diag < end2 && end < end1 && left[end] === right[end - diag]) {
          ++end;
        }
        return snake(start, end, diag);
      };
      const getMiddleSnake = (start1, end1, start2, end2) => {
        const m = end1 - start1;
        const n = end2 - start2;
        if (m === 0 || n === 0) {
          return null;
        }
        const delta = m - n;
        const sum = n + m;
        const offset = (sum % 2 === 0 ? sum : sum + 1) / 2;
        vDown[1 + offset] = start1;
        vUp[1 + offset] = end1 + 1;
        let d, k, i, x, y;
        for (d = 0; d <= offset; ++d) {
          for (k = -d; k <= d; k += 2) {
            i = k + offset;
            if (k === -d || k !== d && vDown[i - 1] < vDown[i + 1]) {
              vDown[i] = vDown[i + 1];
            } else {
              vDown[i] = vDown[i - 1] + 1;
            }
            x = vDown[i];
            y = x - start1 + start2 - k;
            while (x < end1 && y < end2 && left[x] === right[y]) {
              vDown[i] = ++x;
              ++y;
            }
            if (delta % 2 !== 0 && delta - d <= k && k <= delta + d) {
              if (vUp[i - delta] <= vDown[i]) {
                return buildSnake(vUp[i - delta], k + start1 - start2, end1, end2);
              }
            }
          }
          for (k = delta - d; k <= delta + d; k += 2) {
            i = k + offset - delta;
            if (k === delta - d || k !== delta + d && vUp[i + 1] <= vUp[i - 1]) {
              vUp[i] = vUp[i + 1] - 1;
            } else {
              vUp[i] = vUp[i - 1];
            }
            x = vUp[i] - 1;
            y = x - start1 + start2 - k;
            while (x >= start1 && y >= start2 && left[x] === right[y]) {
              vUp[i] = x--;
              y--;
            }
            if (delta % 2 === 0 && -d <= k && k <= d) {
              if (vUp[i] <= vDown[i + delta]) {
                return buildSnake(vUp[i], k + start1 - start2, end1, end2);
              }
            }
          }
        }
        return null;
      };
      const script = [];
      buildScript(0, left.length, 0, right.length, script);
      return script;
    };

    const getOuterHtml = elm => {
      if (isElement$6(elm)) {
        return elm.outerHTML;
      } else if (isText$a(elm)) {
        return Entities.encodeRaw(elm.data, false);
      } else if (isComment(elm)) {
        return '<!--' + elm.data + '-->';
      }
      return '';
    };
    const createFragment = html => {
      let node;
      const container = document.createElement('div');
      const frag = document.createDocumentFragment();
      if (html) {
        container.innerHTML = html;
      }
      while (node = container.firstChild) {
        frag.appendChild(node);
      }
      return frag;
    };
    const insertAt = (elm, html, index) => {
      const fragment = createFragment(html);
      if (elm.hasChildNodes() && index < elm.childNodes.length) {
        const target = elm.childNodes[index];
        elm.insertBefore(fragment, target);
      } else {
        elm.appendChild(fragment);
      }
    };
    const removeAt = (elm, index) => {
      if (elm.hasChildNodes() && index < elm.childNodes.length) {
        const target = elm.childNodes[index];
        elm.removeChild(target);
      }
    };
    const applyDiff = (diff, elm) => {
      let index = 0;
      each$e(diff, action => {
        if (action[0] === KEEP) {
          index++;
        } else if (action[0] === INSERT) {
          insertAt(elm, action[1], index);
          index++;
        } else if (action[0] === DELETE) {
          removeAt(elm, index);
        }
      });
    };
    const read$2 = (elm, trimZwsp) => filter$5(map$3(from(elm.childNodes), trimZwsp ? compose(trim$2, getOuterHtml) : getOuterHtml), item => {
      return item.length > 0;
    });
    const write = (fragments, elm) => {
      const currentFragments = map$3(from(elm.childNodes), getOuterHtml);
      applyDiff(diff(currentFragments, fragments), elm);
      return elm;
    };

    const lazyTempDocument = cached(() => document.implementation.createHTMLDocument('undo'));
    const hasIframes = body => body.querySelector('iframe') !== null;
    const createFragmentedLevel = fragments => {
      return {
        type: 'fragmented',
        fragments,
        content: '',
        bookmark: null,
        beforeBookmark: null
      };
    };
    const createCompleteLevel = content => {
      return {
        type: 'complete',
        fragments: null,
        content,
        bookmark: null,
        beforeBookmark: null
      };
    };
    const createFromEditor = editor => {
      const tempAttrs = editor.serializer.getTempAttrs();
      const body = trim$1(editor.getBody(), tempAttrs);
      return hasIframes(body) ? createFragmentedLevel(read$2(body, true)) : createCompleteLevel(trim$2(body.innerHTML));
    };
    const applyToEditor = (editor, level, before) => {
      const bookmark = before ? level.beforeBookmark : level.bookmark;
      if (level.type === 'fragmented') {
        write(level.fragments, editor.getBody());
      } else {
        editor.setContent(level.content, {
          format: 'raw',
          no_selection: isNonNullable(bookmark) && isPathBookmark(bookmark) ? !bookmark.isFakeCaret : true
        });
      }
      if (bookmark) {
        editor.selection.moveToBookmark(bookmark);
        editor.selection.scrollIntoView();
      }
    };
    const getLevelContent = level => {
      return level.type === 'fragmented' ? level.fragments.join('') : level.content;
    };
    const getCleanLevelContent = level => {
      const elm = SugarElement.fromTag('body', lazyTempDocument());
      set$1(elm, getLevelContent(level));
      each$e(descendants(elm, '*[data-mce-bogus]'), unwrap);
      return get$6(elm);
    };
    const hasEqualContent = (level1, level2) => getLevelContent(level1) === getLevelContent(level2);
    const hasEqualCleanedContent = (level1, level2) => getCleanLevelContent(level1) === getCleanLevelContent(level2);
    const isEq$1 = (level1, level2) => {
      if (!level1 || !level2) {
        return false;
      } else if (hasEqualContent(level1, level2)) {
        return true;
      } else {
        return hasEqualCleanedContent(level1, level2);
      }
    };

    const isUnlocked = locks => locks.get() === 0;

    const setTyping = (undoManager, typing, locks) => {
      if (isUnlocked(locks)) {
        undoManager.typing = typing;
      }
    };
    const endTyping = (undoManager, locks) => {
      if (undoManager.typing) {
        setTyping(undoManager, false, locks);
        undoManager.add();
      }
    };
    const endTypingLevelIgnoreLocks = undoManager => {
      if (undoManager.typing) {
        undoManager.typing = false;
        undoManager.add();
      }
    };

    const beforeChange$1 = (editor, locks, beforeBookmark) => {
      if (isUnlocked(locks)) {
        beforeBookmark.set(getUndoBookmark(editor.selection));
      }
    };
    const addUndoLevel$1 = (editor, undoManager, index, locks, beforeBookmark, level, event) => {
      const currentLevel = createFromEditor(editor);
      const newLevel = Tools.extend(level || {}, currentLevel);
      if (!isUnlocked(locks) || editor.removed) {
        return null;
      }
      const lastLevel = undoManager.data[index.get()];
      if (editor.dispatch('BeforeAddUndo', {
          level: newLevel,
          lastLevel,
          originalEvent: event
        }).isDefaultPrevented()) {
        return null;
      }
      if (lastLevel && isEq$1(lastLevel, newLevel)) {
        return null;
      }
      if (undoManager.data[index.get()]) {
        beforeBookmark.get().each(bm => {
          undoManager.data[index.get()].beforeBookmark = bm;
        });
      }
      const customUndoRedoLevels = getCustomUndoRedoLevels(editor);
      if (customUndoRedoLevels) {
        if (undoManager.data.length > customUndoRedoLevels) {
          for (let i = 0; i < undoManager.data.length - 1; i++) {
            undoManager.data[i] = undoManager.data[i + 1];
          }
          undoManager.data.length--;
          index.set(undoManager.data.length);
        }
      }
      newLevel.bookmark = getUndoBookmark(editor.selection);
      if (index.get() < undoManager.data.length - 1) {
        undoManager.data.length = index.get() + 1;
      }
      undoManager.data.push(newLevel);
      index.set(undoManager.data.length - 1);
      const args = {
        level: newLevel,
        lastLevel,
        originalEvent: event
      };
      if (index.get() > 0) {
        editor.setDirty(true);
        editor.dispatch('AddUndo', args);
        editor.dispatch('change', args);
      } else {
        editor.dispatch('AddUndo', args);
      }
      return newLevel;
    };
    const clear$1 = (editor, undoManager, index) => {
      undoManager.data = [];
      index.set(0);
      undoManager.typing = false;
      editor.dispatch('ClearUndos');
    };
    const extra$1 = (editor, undoManager, index, callback1, callback2) => {
      if (undoManager.transact(callback1)) {
        const bookmark = undoManager.data[index.get()].bookmark;
        const lastLevel = undoManager.data[index.get() - 1];
        applyToEditor(editor, lastLevel, true);
        if (undoManager.transact(callback2)) {
          undoManager.data[index.get() - 1].beforeBookmark = bookmark;
        }
      }
    };
    const redo$1 = (editor, index, data) => {
      let level;
      if (index.get() < data.length - 1) {
        index.set(index.get() + 1);
        level = data[index.get()];
        applyToEditor(editor, level, false);
        editor.setDirty(true);
        editor.dispatch('Redo', { level });
      }
      return level;
    };
    const undo$1 = (editor, undoManager, locks, index) => {
      let level;
      if (undoManager.typing) {
        undoManager.add();
        undoManager.typing = false;
        setTyping(undoManager, false, locks);
      }
      if (index.get() > 0) {
        index.set(index.get() - 1);
        level = undoManager.data[index.get()];
        applyToEditor(editor, level, true);
        editor.setDirty(true);
        editor.dispatch('Undo', { level });
      }
      return level;
    };
    const reset$1 = undoManager => {
      undoManager.clear();
      undoManager.add();
    };
    const hasUndo$1 = (editor, undoManager, index) => index.get() > 0 || undoManager.typing && undoManager.data[0] && !isEq$1(createFromEditor(editor), undoManager.data[0]);
    const hasRedo$1 = (undoManager, index) => index.get() < undoManager.data.length - 1 && !undoManager.typing;
    const transact$1 = (undoManager, locks, callback) => {
      endTyping(undoManager, locks);
      undoManager.beforeChange();
      undoManager.ignore(callback);
      return undoManager.add();
    };
    const ignore$1 = (locks, callback) => {
      try {
        locks.set(locks.get() + 1);
        callback();
      } finally {
        locks.set(locks.get() - 1);
      }
    };

    const addVisualInternal = (editor, elm) => {
      const dom = editor.dom;
      const scope = isNonNullable(elm) ? elm : editor.getBody();
      each$e(dom.select('table,a', scope), matchedElm => {
        switch (matchedElm.nodeName) {
        case 'TABLE':
          const cls = getVisualAidsTableClass(editor);
          const value = dom.getAttrib(matchedElm, 'border');
          if ((!value || value === '0') && editor.hasVisual) {
            dom.addClass(matchedElm, cls);
          } else {
            dom.removeClass(matchedElm, cls);
          }
          break;
        case 'A':
          if (!dom.getAttrib(matchedElm, 'href')) {
            const value = dom.getAttrib(matchedElm, 'name') || matchedElm.id;
            const cls = getVisualAidsAnchorClass(editor);
            if (value && editor.hasVisual) {
              dom.addClass(matchedElm, cls);
            } else {
              dom.removeClass(matchedElm, cls);
            }
          }
          break;
        }
      });
      editor.dispatch('VisualAid', {
        element: elm,
        hasVisual: editor.hasVisual
      });
    };

    const makePlainAdaptor = editor => ({
      init: { bindEvents: noop },
      undoManager: {
        beforeChange: (locks, beforeBookmark) => beforeChange$1(editor, locks, beforeBookmark),
        add: (undoManager, index, locks, beforeBookmark, level, event) => addUndoLevel$1(editor, undoManager, index, locks, beforeBookmark, level, event),
        undo: (undoManager, locks, index) => undo$1(editor, undoManager, locks, index),
        redo: (index, data) => redo$1(editor, index, data),
        clear: (undoManager, index) => clear$1(editor, undoManager, index),
        reset: undoManager => reset$1(undoManager),
        hasUndo: (undoManager, index) => hasUndo$1(editor, undoManager, index),
        hasRedo: (undoManager, index) => hasRedo$1(undoManager, index),
        transact: (undoManager, locks, callback) => transact$1(undoManager, locks, callback),
        ignore: (locks, callback) => ignore$1(locks, callback),
        extra: (undoManager, index, callback1, callback2) => extra$1(editor, undoManager, index, callback1, callback2)
      },
      formatter: {
        match: (name, vars, node, similar) => match$2(editor, name, vars, node, similar),
        matchAll: (names, vars) => matchAll(editor, names, vars),
        matchNode: (node, name, vars, similar) => matchNode(editor, node, name, vars, similar),
        canApply: name => canApply(editor, name),
        closest: names => closest(editor, names),
        apply: (name, vars, node) => applyFormat$1(editor, name, vars, node),
        remove: (name, vars, node, similar) => removeFormat$1(editor, name, vars, node, similar),
        toggle: (name, vars, node) => toggle(editor, name, vars, node),
        formatChanged: (registeredFormatListeners, formats, callback, similar, vars) => formatChangedInternal(editor, registeredFormatListeners, formats, callback, similar, vars)
      },
      editor: {
        getContent: args => getContentInternal(editor, args),
        setContent: (content, args) => setContentInternal(editor, content, args),
        insertContent: (value, details) => insertHtmlAtCaret(editor, value, details),
        addVisual: elm => addVisualInternal(editor, elm)
      },
      selection: { getContent: (format, args) => getSelectedContentInternal(editor, format, args) },
      autocompleter: {
        addDecoration: range => create$9(editor, range),
        removeDecoration: () => remove$2(editor, SugarElement.fromDom(editor.getBody()))
      },
      raw: { getModel: () => Optional.none() }
    });
    const makeRtcAdaptor = rtcEditor => {
      const defaultVars = vars => isObject(vars) ? vars : {};
      const {init, undoManager, formatter, editor, selection, autocompleter, raw} = rtcEditor;
      return {
        init: { bindEvents: init.bindEvents },
        undoManager: {
          beforeChange: undoManager.beforeChange,
          add: undoManager.add,
          undo: undoManager.undo,
          redo: undoManager.redo,
          clear: undoManager.clear,
          reset: undoManager.reset,
          hasUndo: undoManager.hasUndo,
          hasRedo: undoManager.hasRedo,
          transact: (_undoManager, _locks, fn) => undoManager.transact(fn),
          ignore: (_locks, callback) => undoManager.ignore(callback),
          extra: (_undoManager, _index, callback1, callback2) => undoManager.extra(callback1, callback2)
        },
        formatter: {
          match: (name, vars, _node, similar) => formatter.match(name, defaultVars(vars), similar),
          matchAll: formatter.matchAll,
          matchNode: formatter.matchNode,
          canApply: name => formatter.canApply(name),
          closest: names => formatter.closest(names),
          apply: (name, vars, _node) => formatter.apply(name, defaultVars(vars)),
          remove: (name, vars, _node, _similar) => formatter.remove(name, defaultVars(vars)),
          toggle: (name, vars, _node) => formatter.toggle(name, defaultVars(vars)),
          formatChanged: (_rfl, formats, callback, similar, vars) => formatter.formatChanged(formats, callback, similar, vars)
        },
        editor: {
          getContent: args => editor.getContent(args),
          setContent: (content, args) => {
            return {
              content: editor.setContent(content, args),
              html: ''
            };
          },
          insertContent: (content, _details) => {
            editor.insertContent(content);
            return '';
          },
          addVisual: editor.addVisual
        },
        selection: { getContent: (_format, args) => selection.getContent(args) },
        autocompleter: {
          addDecoration: autocompleter.addDecoration,
          removeDecoration: autocompleter.removeDecoration
        },
        raw: { getModel: () => Optional.some(raw.getRawModel()) }
      };
    };
    const makeNoopAdaptor = () => {
      const nul = constant(null);
      const empty = constant('');
      return {
        init: { bindEvents: noop },
        undoManager: {
          beforeChange: noop,
          add: nul,
          undo: nul,
          redo: nul,
          clear: noop,
          reset: noop,
          hasUndo: never,
          hasRedo: never,
          transact: nul,
          ignore: noop,
          extra: noop
        },
        formatter: {
          match: never,
          matchAll: constant([]),
          matchNode: constant(undefined),
          canApply: never,
          closest: empty,
          apply: noop,
          remove: noop,
          toggle: noop,
          formatChanged: constant({ unbind: noop })
        },
        editor: {
          getContent: empty,
          setContent: constant({
            content: '',
            html: ''
          }),
          insertContent: constant(''),
          addVisual: noop
        },
        selection: { getContent: empty },
        autocompleter: {
          addDecoration: noop,
          removeDecoration: noop
        },
        raw: { getModel: constant(Optional.none()) }
      };
    };
    const isRtc = editor => has$2(editor.plugins, 'rtc');
    const getRtcSetup = editor => get$a(editor.plugins, 'rtc').bind(rtcPlugin => Optional.from(rtcPlugin.setup));
    const setup$t = editor => {
      const editorCast = editor;
      return getRtcSetup(editor).fold(() => {
        editorCast.rtcInstance = makePlainAdaptor(editor);
        return Optional.none();
      }, setup => {
        editorCast.rtcInstance = makeNoopAdaptor();
        return Optional.some(() => setup().then(rtcEditor => {
          editorCast.rtcInstance = makeRtcAdaptor(rtcEditor);
          return rtcEditor.rtc.isRemote;
        }));
      });
    };
    const getRtcInstanceWithFallback = editor => editor.rtcInstance ? editor.rtcInstance : makePlainAdaptor(editor);
    const getRtcInstanceWithError = editor => {
      const rtcInstance = editor.rtcInstance;
      if (!rtcInstance) {
        throw new Error('Failed to get RTC instance not yet initialized.');
      } else {
        return rtcInstance;
      }
    };
    const beforeChange = (editor, locks, beforeBookmark) => {
      getRtcInstanceWithError(editor).undoManager.beforeChange(locks, beforeBookmark);
    };
    const addUndoLevel = (editor, undoManager, index, locks, beforeBookmark, level, event) => getRtcInstanceWithError(editor).undoManager.add(undoManager, index, locks, beforeBookmark, level, event);
    const undo = (editor, undoManager, locks, index) => getRtcInstanceWithError(editor).undoManager.undo(undoManager, locks, index);
    const redo = (editor, index, data) => getRtcInstanceWithError(editor).undoManager.redo(index, data);
    const clear = (editor, undoManager, index) => {
      getRtcInstanceWithError(editor).undoManager.clear(undoManager, index);
    };
    const reset = (editor, undoManager) => {
      getRtcInstanceWithError(editor).undoManager.reset(undoManager);
    };
    const hasUndo = (editor, undoManager, index) => getRtcInstanceWithError(editor).undoManager.hasUndo(undoManager, index);
    const hasRedo = (editor, undoManager, index) => getRtcInstanceWithError(editor).undoManager.hasRedo(undoManager, index);
    const transact = (editor, undoManager, locks, callback) => getRtcInstanceWithError(editor).undoManager.transact(undoManager, locks, callback);
    const ignore = (editor, locks, callback) => {
      getRtcInstanceWithError(editor).undoManager.ignore(locks, callback);
    };
    const extra = (editor, undoManager, index, callback1, callback2) => {
      getRtcInstanceWithError(editor).undoManager.extra(undoManager, index, callback1, callback2);
    };
    const matchFormat = (editor, name, vars, node, similar) => getRtcInstanceWithError(editor).formatter.match(name, vars, node, similar);
    const matchAllFormats = (editor, names, vars) => getRtcInstanceWithError(editor).formatter.matchAll(names, vars);
    const matchNodeFormat = (editor, node, name, vars, similar) => getRtcInstanceWithError(editor).formatter.matchNode(node, name, vars, similar);
    const canApplyFormat = (editor, name) => getRtcInstanceWithError(editor).formatter.canApply(name);
    const closestFormat = (editor, names) => getRtcInstanceWithError(editor).formatter.closest(names);
    const applyFormat = (editor, name, vars, node) => {
      getRtcInstanceWithError(editor).formatter.apply(name, vars, node);
    };
    const removeFormat = (editor, name, vars, node, similar) => {
      getRtcInstanceWithError(editor).formatter.remove(name, vars, node, similar);
    };
    const toggleFormat = (editor, name, vars, node) => {
      getRtcInstanceWithError(editor).formatter.toggle(name, vars, node);
    };
    const formatChanged = (editor, registeredFormatListeners, formats, callback, similar, vars) => getRtcInstanceWithError(editor).formatter.formatChanged(registeredFormatListeners, formats, callback, similar, vars);
    const getContent$2 = (editor, args) => getRtcInstanceWithFallback(editor).editor.getContent(args);
    const setContent$2 = (editor, content, args) => getRtcInstanceWithFallback(editor).editor.setContent(content, args);
    const insertContent$1 = (editor, value, details) => getRtcInstanceWithFallback(editor).editor.insertContent(value, details);
    const getSelectedContent = (editor, format, args) => getRtcInstanceWithError(editor).selection.getContent(format, args);
    const addVisual$1 = (editor, elm) => getRtcInstanceWithError(editor).editor.addVisual(elm);
    const bindEvents = editor => getRtcInstanceWithError(editor).init.bindEvents();
    const addAutocompleterDecoration = (editor, range) => getRtcInstanceWithError(editor).autocompleter.addDecoration(range);
    const removeAutocompleterDecoration = editor => getRtcInstanceWithError(editor).autocompleter.removeDecoration();

    const getContent$1 = (editor, args = {}) => {
      const format = args.format ? args.format : 'html';
      return getSelectedContent(editor, format, args);
    };

    const removeEmpty = text => {
      if (text.dom.length === 0) {
        remove$5(text);
        return Optional.none();
      } else {
        return Optional.some(text);
      }
    };
    const walkPastBookmark = (node, start) => node.filter(elm => BookmarkManager.isBookmarkNode(elm.dom)).bind(start ? nextSibling : prevSibling);
    const merge$1 = (outer, inner, rng, start, schema) => {
      const outerElm = outer.dom;
      const innerElm = inner.dom;
      const oldLength = start ? outerElm.length : innerElm.length;
      if (start) {
        mergeTextNodes(outerElm, innerElm, schema, false, !start);
        rng.setStart(innerElm, oldLength);
      } else {
        mergeTextNodes(innerElm, outerElm, schema, false, !start);
        rng.setEnd(innerElm, oldLength);
      }
    };
    const normalizeTextIfRequired = (inner, start, schema) => {
      parent(inner).each(root => {
        const text = inner.dom;
        if (start && needsToBeNbspLeft(root, CaretPosition(text, 0), schema)) {
          normalizeWhitespaceAfter(text, 0, schema);
        } else if (!start && needsToBeNbspRight(root, CaretPosition(text, text.length), schema)) {
          normalizeWhitespaceBefore(text, text.length, schema);
        }
      });
    };
    const mergeAndNormalizeText = (outerNode, innerNode, rng, start, schema) => {
      outerNode.bind(outer => {
        const normalizer = start ? normalizeWhitespaceBefore : normalizeWhitespaceAfter;
        normalizer(outer.dom, start ? outer.dom.length : 0, schema);
        return innerNode.filter(isText$b).map(inner => merge$1(outer, inner, rng, start, schema));
      }).orThunk(() => {
        const innerTextNode = walkPastBookmark(innerNode, start).or(innerNode).filter(isText$b);
        return innerTextNode.map(inner => normalizeTextIfRequired(inner, start, schema));
      });
    };
    const rngSetContent = (rng, fragment, schema) => {
      const firstChild = Optional.from(fragment.firstChild).map(SugarElement.fromDom);
      const lastChild = Optional.from(fragment.lastChild).map(SugarElement.fromDom);
      rng.deleteContents();
      rng.insertNode(fragment);
      const prevText = firstChild.bind(prevSibling).filter(isText$b).bind(removeEmpty);
      const nextText = lastChild.bind(nextSibling).filter(isText$b).bind(removeEmpty);
      mergeAndNormalizeText(prevText, firstChild, rng, true, schema);
      mergeAndNormalizeText(nextText, lastChild, rng, false, schema);
      rng.collapse(false);
    };
    const setupArgs$2 = (args, content) => ({
      format: 'html',
      ...args,
      set: true,
      selection: true,
      content
    });
    const cleanContent = (editor, args) => {
      if (args.format !== 'raw') {
        const rng = editor.selection.getRng();
        const contextBlock = editor.dom.getParent(rng.commonAncestorContainer, editor.dom.isBlock);
        const contextArgs = contextBlock ? { context: contextBlock.nodeName.toLowerCase() } : {};
        const node = editor.parser.parse(args.content, {
          forced_root_block: false,
          ...contextArgs,
          ...args
        });
        return HtmlSerializer({ validate: false }, editor.schema).serialize(node);
      } else {
        return args.content;
      }
    };
    const setContent$1 = (editor, content, args = {}) => {
      const defaultedArgs = setupArgs$2(args, content);
      preProcessSetContent(editor, defaultedArgs).each(updatedArgs => {
        const cleanedContent = cleanContent(editor, updatedArgs);
        const rng = editor.selection.getRng();
        rngSetContent(rng, rng.createContextualFragment(cleanedContent), editor.schema);
        editor.selection.setRng(rng);
        scrollRangeIntoView(editor, rng);
        postProcessSetContent(editor, cleanedContent, updatedArgs);
      });
    };

    const deleteFromCallbackMap = (callbackMap, selector, callback) => {
      if (has$2(callbackMap, selector)) {
        const newCallbacks = filter$5(callbackMap[selector], cb => cb !== callback);
        if (newCallbacks.length === 0) {
          delete callbackMap[selector];
        } else {
          callbackMap[selector] = newCallbacks;
        }
      }
    };
    var SelectorChanged = (dom, editor) => {
      let selectorChangedData;
      let currentSelectors;
      const findMatchingNode = (selector, nodes) => find$2(nodes, node => dom.is(node, selector));
      const getParents = elem => dom.getParents(elem, undefined, dom.getRoot());
      const setup = () => {
        selectorChangedData = {};
        currentSelectors = {};
        editor.on('NodeChange', e => {
          const node = e.element;
          const parents = getParents(node);
          const matchedSelectors = {};
          each$d(selectorChangedData, (callbacks, selector) => {
            findMatchingNode(selector, parents).each(node => {
              if (!currentSelectors[selector]) {
                each$e(callbacks, callback => {
                  callback(true, {
                    node,
                    selector,
                    parents
                  });
                });
                currentSelectors[selector] = callbacks;
              }
              matchedSelectors[selector] = callbacks;
            });
          });
          each$d(currentSelectors, (callbacks, selector) => {
            if (!matchedSelectors[selector]) {
              delete currentSelectors[selector];
              each$e(callbacks, callback => {
                callback(false, {
                  node,
                  selector,
                  parents
                });
              });
            }
          });
        });
      };
      return {
        selectorChangedWithUnbind: (selector, callback) => {
          if (!selectorChangedData) {
            setup();
          }
          if (!selectorChangedData[selector]) {
            selectorChangedData[selector] = [];
          }
          selectorChangedData[selector].push(callback);
          findMatchingNode(selector, getParents(editor.selection.getStart())).each(() => {
            currentSelectors[selector] = selectorChangedData[selector];
          });
          return {
            unbind: () => {
              deleteFromCallbackMap(selectorChangedData, selector, callback);
              deleteFromCallbackMap(currentSelectors, selector, callback);
            }
          };
        }
      };
    };

    const isAttachedToDom = node => {
      return !!(node && node.ownerDocument) && contains(SugarElement.fromDom(node.ownerDocument), SugarElement.fromDom(node));
    };
    const isValidRange = rng => {
      if (!rng) {
        return false;
      } else {
        return isAttachedToDom(rng.startContainer) && isAttachedToDom(rng.endContainer);
      }
    };
    const EditorSelection = (dom, win, serializer, editor) => {
      let selectedRange;
      let explicitRange;
      const {selectorChangedWithUnbind} = SelectorChanged(dom, editor);
      const setCursorLocation = (node, offset) => {
        const rng = dom.createRng();
        if (isNonNullable(node) && isNonNullable(offset)) {
          rng.setStart(node, offset);
          rng.setEnd(node, offset);
          setRng(rng);
          collapse(false);
        } else {
          moveEndPoint(dom, rng, editor.getBody(), true);
          setRng(rng);
        }
      };
      const getContent = args => getContent$1(editor, args);
      const setContent = (content, args) => setContent$1(editor, content, args);
      const getStart$1 = real => getStart(editor.getBody(), getRng$1(), real);
      const getEnd = real => getEnd$1(editor.getBody(), getRng$1(), real);
      const getBookmark = (type, normalized) => bookmarkManager.getBookmark(type, normalized);
      const moveToBookmark = bookmark => bookmarkManager.moveToBookmark(bookmark);
      const select$1 = (node, content) => {
        select(dom, node, content).each(setRng);
        return node;
      };
      const isCollapsed = () => {
        const rng = getRng$1(), sel = getSel();
        if (!rng || rng.item) {
          return false;
        }
        if (rng.compareEndPoints) {
          return rng.compareEndPoints('StartToEnd', rng) === 0;
        }
        return !sel || rng.collapsed;
      };
      const isEditable = () => {
        const rng = getRng$1();
        const fakeSelectedElements = editor.getBody().querySelectorAll('[data-mce-selected="1"]');
        if (fakeSelectedElements.length > 0) {
          return forall(fakeSelectedElements, el => dom.isEditable(el.parentElement));
        } else {
          return isEditableRange(dom, rng);
        }
      };
      const collapse = toStart => {
        const rng = getRng$1();
        rng.collapse(!!toStart);
        setRng(rng);
      };
      const getSel = () => win.getSelection ? win.getSelection() : win.document.selection;
      const getRng$1 = () => {
        let rng;
        const tryCompareBoundaryPoints = (how, sourceRange, destinationRange) => {
          try {
            return sourceRange.compareBoundaryPoints(how, destinationRange);
          } catch (ex) {
            return -1;
          }
        };
        const doc = win.document;
        if (isNonNullable(editor.bookmark) && !hasFocus(editor)) {
          const bookmark = getRng(editor);
          if (bookmark.isSome()) {
            return bookmark.map(r => processRanges(editor, [r])[0]).getOr(doc.createRange());
          }
        }
        try {
          const selection = getSel();
          if (selection && !isRestrictedNode(selection.anchorNode)) {
            if (selection.rangeCount > 0) {
              rng = selection.getRangeAt(0);
            } else {
              rng = doc.createRange();
            }
            rng = processRanges(editor, [rng])[0];
          }
        } catch (ex) {
        }
        if (!rng) {
          rng = doc.createRange();
        }
        if (isDocument$1(rng.startContainer) && rng.collapsed) {
          const elm = dom.getRoot();
          rng.setStart(elm, 0);
          rng.setEnd(elm, 0);
        }
        if (selectedRange && explicitRange) {
          if (tryCompareBoundaryPoints(rng.START_TO_START, rng, selectedRange) === 0 && tryCompareBoundaryPoints(rng.END_TO_END, rng, selectedRange) === 0) {
            rng = explicitRange;
          } else {
            selectedRange = null;
            explicitRange = null;
          }
        }
        return rng;
      };
      const setRng = (rng, forward) => {
        if (!isValidRange(rng)) {
          return;
        }
        const sel = getSel();
        const evt = editor.dispatch('SetSelectionRange', {
          range: rng,
          forward
        });
        rng = evt.range;
        if (sel) {
          explicitRange = rng;
          try {
            sel.removeAllRanges();
            sel.addRange(rng);
          } catch (ex) {
          }
          if (forward === false && sel.extend) {
            sel.collapse(rng.endContainer, rng.endOffset);
            sel.extend(rng.startContainer, rng.startOffset);
          }
          selectedRange = sel.rangeCount > 0 ? sel.getRangeAt(0) : null;
        }
        if (!rng.collapsed && rng.startContainer === rng.endContainer && (sel === null || sel === void 0 ? void 0 : sel.setBaseAndExtent)) {
          if (rng.endOffset - rng.startOffset < 2) {
            if (rng.startContainer.hasChildNodes()) {
              const node = rng.startContainer.childNodes[rng.startOffset];
              if (node && node.nodeName === 'IMG') {
                sel.setBaseAndExtent(rng.startContainer, rng.startOffset, rng.endContainer, rng.endOffset);
                if (sel.anchorNode !== rng.startContainer || sel.focusNode !== rng.endContainer) {
                  sel.setBaseAndExtent(node, 0, node, 1);
                }
              }
            }
          }
        }
        editor.dispatch('AfterSetSelectionRange', {
          range: rng,
          forward
        });
      };
      const setNode = elm => {
        setContent(dom.getOuterHTML(elm));
        return elm;
      };
      const getNode$1 = () => getNode(editor.getBody(), getRng$1());
      const getSelectedBlocks$1 = (startElm, endElm) => getSelectedBlocks(dom, getRng$1(), startElm, endElm);
      const isForward = () => {
        const sel = getSel();
        const anchorNode = sel === null || sel === void 0 ? void 0 : sel.anchorNode;
        const focusNode = sel === null || sel === void 0 ? void 0 : sel.focusNode;
        if (!sel || !anchorNode || !focusNode || isRestrictedNode(anchorNode) || isRestrictedNode(focusNode)) {
          return true;
        }
        const anchorRange = dom.createRng();
        const focusRange = dom.createRng();
        try {
          anchorRange.setStart(anchorNode, sel.anchorOffset);
          anchorRange.collapse(true);
          focusRange.setStart(focusNode, sel.focusOffset);
          focusRange.collapse(true);
        } catch (e) {
          return true;
        }
        return anchorRange.compareBoundaryPoints(anchorRange.START_TO_START, focusRange) <= 0;
      };
      const normalize = () => {
        const rng = getRng$1();
        const sel = getSel();
        if (!hasMultipleRanges(sel) && hasAnyRanges(editor)) {
          const normRng = normalize$2(dom, rng);
          normRng.each(normRng => {
            setRng(normRng, isForward());
          });
          return normRng.getOr(rng);
        }
        return rng;
      };
      const selectorChanged = (selector, callback) => {
        selectorChangedWithUnbind(selector, callback);
        return exports;
      };
      const getScrollContainer = () => {
        let scrollContainer;
        let node = dom.getRoot();
        while (node && node.nodeName !== 'BODY') {
          if (node.scrollHeight > node.clientHeight) {
            scrollContainer = node;
            break;
          }
          node = node.parentNode;
        }
        return scrollContainer;
      };
      const scrollIntoView = (elm, alignToTop) => {
        if (isNonNullable(elm)) {
          scrollElementIntoView(editor, elm, alignToTop);
        } else {
          scrollRangeIntoView(editor, getRng$1(), alignToTop);
        }
      };
      const placeCaretAt = (clientX, clientY) => setRng(fromPoint(clientX, clientY, editor.getDoc()));
      const getBoundingClientRect = () => {
        const rng = getRng$1();
        return rng.collapsed ? CaretPosition.fromRangeStart(rng).getClientRects()[0] : rng.getBoundingClientRect();
      };
      const destroy = () => {
        win = selectedRange = explicitRange = null;
        controlSelection.destroy();
      };
      const expand = (options = { type: 'word' }) => setRng(RangeUtils(dom).expand(getRng$1(), options));
      const exports = {
        dom,
        win,
        serializer,
        editor,
        expand,
        collapse,
        setCursorLocation,
        getContent,
        setContent,
        getBookmark,
        moveToBookmark,
        select: select$1,
        isCollapsed,
        isEditable,
        isForward,
        setNode,
        getNode: getNode$1,
        getSel,
        setRng,
        getRng: getRng$1,
        getStart: getStart$1,
        getEnd,
        getSelectedBlocks: getSelectedBlocks$1,
        normalize,
        selectorChanged,
        selectorChangedWithUnbind,
        getScrollContainer,
        scrollIntoView,
        placeCaretAt,
        getBoundingClientRect,
        destroy
      };
      const bookmarkManager = BookmarkManager(exports);
      const controlSelection = ControlSelection(exports, editor);
      exports.bookmarkManager = bookmarkManager;
      exports.controlSelection = controlSelection;
      return exports;
    };

    const register$3 = (htmlParser, settings, dom) => {
      htmlParser.addAttributeFilter('data-mce-tabindex', (nodes, name) => {
        let i = nodes.length;
        while (i--) {
          const node = nodes[i];
          node.attr('tabindex', node.attr('data-mce-tabindex'));
          node.attr(name, null);
        }
      });
      htmlParser.addAttributeFilter('src,href,style', (nodes, name) => {
        const internalName = 'data-mce-' + name;
        const urlConverter = settings.url_converter;
        const urlConverterScope = settings.url_converter_scope;
        let i = nodes.length;
        while (i--) {
          const node = nodes[i];
          let value = node.attr(internalName);
          if (value !== undefined) {
            node.attr(name, value.length > 0 ? value : null);
            node.attr(internalName, null);
          } else {
            value = node.attr(name);
            if (name === 'style') {
              value = dom.serializeStyle(dom.parseStyle(value), node.name);
            } else if (urlConverter) {
              value = urlConverter.call(urlConverterScope, value, name, node.name);
            }
            node.attr(name, value.length > 0 ? value : null);
          }
        }
      });
      htmlParser.addAttributeFilter('class', nodes => {
        let i = nodes.length;
        while (i--) {
          const node = nodes[i];
          let value = node.attr('class');
          if (value) {
            value = value.replace(/(?:^|\s)mce-item-\w+(?!\S)/g, '');
            node.attr('class', value.length > 0 ? value : null);
          }
        }
      });
      htmlParser.addAttributeFilter('data-mce-type', (nodes, name, args) => {
        let i = nodes.length;
        while (i--) {
          const node = nodes[i];
          if (node.attr('data-mce-type') === 'bookmark' && !args.cleanup) {
            const hasChildren = Optional.from(node.firstChild).exists(firstChild => {
              var _a;
              return !isZwsp$1((_a = firstChild.value) !== null && _a !== void 0 ? _a : '');
            });
            if (hasChildren) {
              node.unwrap();
            } else {
              node.remove();
            }
          }
        }
      });
      htmlParser.addNodeFilter('script,style', (nodes, name) => {
        var _a;
        const trim = value => {
          return value.replace(/(<!--\[CDATA\[|\]\]-->)/g, '\n').replace(/^[\r\n]*|[\r\n]*$/g, '').replace(/^\s*((<!--)?(\s*\/\/)?\s*<!\[CDATA\[|(<!--\s*)?\/\*\s*<!\[CDATA\[\s*\*\/|(\/\/)?\s*<!--|\/\*\s*<!--\s*\*\/)\s*[\r\n]*/gi, '').replace(/\s*(\/\*\s*\]\]>\s*\*\/(-->)?|\s*\/\/\s*\]\]>(-->)?|\/\/\s*(-->)?|\]\]>|\/\*\s*-->\s*\*\/|\s*-->\s*)\s*$/g, '');
        };
        let i = nodes.length;
        while (i--) {
          const node = nodes[i];
          const firstChild = node.firstChild;
          const value = (_a = firstChild === null || firstChild === void 0 ? void 0 : firstChild.value) !== null && _a !== void 0 ? _a : '';
          if (name === 'script') {
            const type = node.attr('type');
            if (type) {
              node.attr('type', type === 'mce-no/type' ? null : type.replace(/^mce\-/, ''));
            }
            if (settings.element_format === 'xhtml' && firstChild && value.length > 0) {
              firstChild.value = '// <![CDATA[\n' + trim(value) + '\n// ]]>';
            }
          } else {
            if (settings.element_format === 'xhtml' && firstChild && value.length > 0) {
              firstChild.value = '<!--\n' + trim(value) + '\n-->';
            }
          }
        }
      });
      htmlParser.addNodeFilter('#comment', nodes => {
        let i = nodes.length;
        while (i--) {
          const node = nodes[i];
          const value = node.value;
          if (settings.preserve_cdata && (value === null || value === void 0 ? void 0 : value.indexOf('[CDATA[')) === 0) {
            node.name = '#cdata';
            node.type = 4;
            node.value = dom.decode(value.replace(/^\[CDATA\[|\]\]$/g, ''));
          } else if ((value === null || value === void 0 ? void 0 : value.indexOf('mce:protected ')) === 0) {
            node.name = '#text';
            node.type = 3;
            node.raw = true;
            node.value = unescape(value).substr(14);
          }
        }
      });
      htmlParser.addNodeFilter('xml:namespace,input', (nodes, name) => {
        let i = nodes.length;
        while (i--) {
          const node = nodes[i];
          if (node.type === 7) {
            node.remove();
          } else if (node.type === 1) {
            if (name === 'input' && !node.attr('type')) {
              node.attr('type', 'text');
            }
          }
        }
      });
      htmlParser.addAttributeFilter('data-mce-type', nodes => {
        each$e(nodes, node => {
          if (node.attr('data-mce-type') === 'format-caret') {
            if (node.isEmpty(htmlParser.schema.getNonEmptyElements())) {
              node.remove();
            } else {
              node.unwrap();
            }
          }
        });
      });
      htmlParser.addAttributeFilter('data-mce-src,data-mce-href,data-mce-style,' + 'data-mce-selected,data-mce-expando,data-mce-block,' + 'data-mce-type,data-mce-resize,data-mce-placeholder', (nodes, name) => {
        let i = nodes.length;
        while (i--) {
          nodes[i].attr(name, null);
        }
      });
      if (settings.remove_trailing_brs) {
        addNodeFilter(settings, htmlParser, htmlParser.schema);
      }
    };
    const trimTrailingBr = rootNode => {
      const isBr = node => {
        return (node === null || node === void 0 ? void 0 : node.name) === 'br';
      };
      const brNode1 = rootNode.lastChild;
      if (isBr(brNode1)) {
        const brNode2 = brNode1.prev;
        if (isBr(brNode2)) {
          brNode1.remove();
          brNode2.remove();
        }
      }
    };

    const preProcess$1 = (editor, node, args) => {
      let oldDoc;
      const dom = editor.dom;
      let clonedNode = node.cloneNode(true);
      const impl = document.implementation;
      if (impl.createHTMLDocument) {
        const doc = impl.createHTMLDocument('');
        Tools.each(clonedNode.nodeName === 'BODY' ? clonedNode.childNodes : [clonedNode], node => {
          doc.body.appendChild(doc.importNode(node, true));
        });
        if (clonedNode.nodeName !== 'BODY') {
          clonedNode = doc.body.firstChild;
        } else {
          clonedNode = doc.body;
        }
        oldDoc = dom.doc;
        dom.doc = doc;
      }
      firePreProcess(editor, {
        ...args,
        node: clonedNode
      });
      if (oldDoc) {
        dom.doc = oldDoc;
      }
      return clonedNode;
    };
    const shouldFireEvent = (editor, args) => {
      return isNonNullable(editor) && editor.hasEventListeners('PreProcess') && !args.no_events;
    };
    const process$1 = (editor, node, args) => {
      return shouldFireEvent(editor, args) ? preProcess$1(editor, node, args) : node;
    };

    const addTempAttr = (htmlParser, tempAttrs, name) => {
      if (Tools.inArray(tempAttrs, name) === -1) {
        htmlParser.addAttributeFilter(name, (nodes, name) => {
          let i = nodes.length;
          while (i--) {
            nodes[i].attr(name, null);
          }
        });
        tempAttrs.push(name);
      }
    };
    const postProcess = (editor, args, content) => {
      if (!args.no_events && editor) {
        const outArgs = firePostProcess(editor, {
          ...args,
          content
        });
        return outArgs.content;
      } else {
        return content;
      }
    };
    const getHtmlFromNode = (dom, node, args) => {
      const html = trim$2(args.getInner ? node.innerHTML : dom.getOuterHTML(node));
      return args.selection || isWsPreserveElement(SugarElement.fromDom(node)) ? html : Tools.trim(html);
    };
    const parseHtml = (htmlParser, html, args) => {
      const parserArgs = args.selection ? {
        forced_root_block: false,
        ...args
      } : args;
      const rootNode = htmlParser.parse(html, parserArgs);
      trimTrailingBr(rootNode);
      return rootNode;
    };
    const serializeNode = (settings, schema, node) => {
      const htmlSerializer = HtmlSerializer(settings, schema);
      return htmlSerializer.serialize(node);
    };
    const toHtml = (editor, settings, schema, rootNode, args) => {
      const content = serializeNode(settings, schema, rootNode);
      return postProcess(editor, args, content);
    };
    const DomSerializerImpl = (settings, editor) => {
      const tempAttrs = ['data-mce-selected'];
      const defaultedSettings = {
        entity_encoding: 'named',
        remove_trailing_brs: true,
        pad_empty_with_br: false,
        ...settings
      };
      const dom = editor && editor.dom ? editor.dom : DOMUtils.DOM;
      const schema = editor && editor.schema ? editor.schema : Schema(defaultedSettings);
      const htmlParser = DomParser(defaultedSettings, schema);
      register$3(htmlParser, defaultedSettings, dom);
      const serialize = (node, parserArgs = {}) => {
        const args = {
          format: 'html',
          ...parserArgs
        };
        const targetNode = process$1(editor, node, args);
        const html = getHtmlFromNode(dom, targetNode, args);
        const rootNode = parseHtml(htmlParser, html, args);
        return args.format === 'tree' ? rootNode : toHtml(editor, defaultedSettings, schema, rootNode, args);
      };
      return {
        schema,
        addNodeFilter: htmlParser.addNodeFilter,
        addAttributeFilter: htmlParser.addAttributeFilter,
        serialize: serialize,
        addRules: schema.addValidElements,
        setRules: schema.setValidElements,
        addTempAttr: curry(addTempAttr, htmlParser, tempAttrs),
        getTempAttrs: constant(tempAttrs),
        getNodeFilters: htmlParser.getNodeFilters,
        getAttributeFilters: htmlParser.getAttributeFilters,
        removeNodeFilter: htmlParser.removeNodeFilter,
        removeAttributeFilter: htmlParser.removeAttributeFilter
      };
    };

    const DomSerializer = (settings, editor) => {
      const domSerializer = DomSerializerImpl(settings, editor);
      return {
        schema: domSerializer.schema,
        addNodeFilter: domSerializer.addNodeFilter,
        addAttributeFilter: domSerializer.addAttributeFilter,
        serialize: domSerializer.serialize,
        addRules: domSerializer.addRules,
        setRules: domSerializer.setRules,
        addTempAttr: domSerializer.addTempAttr,
        getTempAttrs: domSerializer.getTempAttrs,
        getNodeFilters: domSerializer.getNodeFilters,
        getAttributeFilters: domSerializer.getAttributeFilters,
        removeNodeFilter: domSerializer.removeNodeFilter,
        removeAttributeFilter: domSerializer.removeAttributeFilter
      };
    };

    const defaultFormat$1 = 'html';
    const setupArgs$1 = (args, format) => ({
      ...args,
      format,
      get: true,
      getInner: true
    });
    const getContent = (editor, args = {}) => {
      const format = args.format ? args.format : defaultFormat$1;
      const defaultedArgs = setupArgs$1(args, format);
      return preProcessGetContent(editor, defaultedArgs).fold(identity, updatedArgs => {
        const content = getContent$2(editor, updatedArgs);
        return postProcessGetContent(editor, content, updatedArgs);
      });
    };

    const defaultFormat = 'html';
    const setupArgs = (args, content) => ({
      format: defaultFormat,
      ...args,
      set: true,
      content
    });
    const setContent = (editor, content, args = {}) => {
      const defaultedArgs = setupArgs(args, content);
      return preProcessSetContent(editor, defaultedArgs).map(updatedArgs => {
        const result = setContent$2(editor, updatedArgs.content, updatedArgs);
        postProcessSetContent(editor, result.html, updatedArgs);
        return result.content;
      }).getOr(content);
    };

    const removedOptions = ('autoresize_on_init,content_editable_state,padd_empty_with_br,block_elements,' + 'boolean_attributes,editor_deselector,editor_selector,elements,file_browser_callback_types,filepicker_validator_handler,' + 'force_hex_style_colors,force_p_newlines,gecko_spellcheck,images_dataimg_filter,media_scripts,mode,move_caret_before_on_enter_elements,' + 'non_empty_elements,self_closing_elements,short_ended_elements,special,spellchecker_select_languages,spellchecker_whitelist,' + 'tab_focus,tabfocus_elements,table_responsive_width,text_block_elements,text_inline_elements,toolbar_drawer,types,validate,whitespace_elements,' + 'paste_enable_default_filters,paste_filter_drop,paste_word_valid_elements,paste_retain_style_properties,paste_convert_word_fake_lists').split(',');
    const deprecatedOptions = 'template_cdate_classes,template_mdate_classes,template_selected_content_classes,template_preview_replace_values,template_replace_values,templates,template_cdate_format,template_mdate_format'.split(',');
    const removedPlugins = 'bbcode,colorpicker,contextmenu,fullpage,legacyoutput,spellchecker,textcolor'.split(',');
    const deprecatedPlugins = [
      {
        name: 'template',
        replacedWith: 'Advanced Template'
      },
      { name: 'rtc' }
    ];
    const getMatchingOptions = (options, searchingFor) => {
      const settingNames = filter$5(searchingFor, setting => has$2(options, setting));
      return sort(settingNames);
    };
    const getRemovedOptions = options => {
      const settingNames = getMatchingOptions(options, removedOptions);
      const forcedRootBlock = options.forced_root_block;
      if (forcedRootBlock === false || forcedRootBlock === '') {
        settingNames.push('forced_root_block (false only)');
      }
      return sort(settingNames);
    };
    const getDeprecatedOptions = options => getMatchingOptions(options, deprecatedOptions);
    const getMatchingPlugins = (options, searchingFor) => {
      const plugins = Tools.makeMap(options.plugins, ' ');
      const hasPlugin = plugin => has$2(plugins, plugin);
      const pluginNames = filter$5(searchingFor, hasPlugin);
      return sort(pluginNames);
    };
    const getRemovedPlugins = options => getMatchingPlugins(options, removedPlugins);
    const getDeprecatedPlugins = options => getMatchingPlugins(options, deprecatedPlugins.map(entry => entry.name));
    const logRemovedWarnings = (rawOptions, normalizedOptions) => {
      const removedOptions = getRemovedOptions(rawOptions);
      const removedPlugins = getRemovedPlugins(normalizedOptions);
      const hasRemovedPlugins = removedPlugins.length > 0;
      const hasRemovedOptions = removedOptions.length > 0;
      const isLegacyMobileTheme = normalizedOptions.theme === 'mobile';
      if (hasRemovedPlugins || hasRemovedOptions || isLegacyMobileTheme) {
        const listJoiner = '\n- ';
        const themesMessage = isLegacyMobileTheme ? `\n\nThemes:${ listJoiner }mobile` : '';
        const pluginsMessage = hasRemovedPlugins ? `\n\nPlugins:${ listJoiner }${ removedPlugins.join(listJoiner) }` : '';
        const optionsMessage = hasRemovedOptions ? `\n\nOptions:${ listJoiner }${ removedOptions.join(listJoiner) }` : '';
        console.warn('The following deprecated features are currently enabled and have been removed in TinyMCE 6.0. These features will no longer work and should be removed from the TinyMCE configuration. ' + 'See https://www.tiny.cloud/docs/tinymce/6/migration-from-5x/ for more information.' + themesMessage + pluginsMessage + optionsMessage);
      }
    };
    const getPluginDescription = name => find$2(deprecatedPlugins, entry => entry.name === name).fold(() => name, entry => {
      if (entry.replacedWith) {
        return `${ name }, replaced by ${ entry.replacedWith }`;
      } else {
        return name;
      }
    });
    const logDeprecatedWarnings = (rawOptions, normalizedOptions) => {
      const deprecatedOptions = getDeprecatedOptions(rawOptions);
      const deprecatedPlugins = getDeprecatedPlugins(normalizedOptions);
      const hasDeprecatedPlugins = deprecatedPlugins.length > 0;
      const hasDeprecatedOptions = deprecatedOptions.length > 0;
      if (hasDeprecatedPlugins || hasDeprecatedOptions) {
        const listJoiner = '\n- ';
        const pluginsMessage = hasDeprecatedPlugins ? `\n\nPlugins:${ listJoiner }${ deprecatedPlugins.map(getPluginDescription).join(listJoiner) }` : '';
        const optionsMessage = hasDeprecatedOptions ? `\n\nOptions:${ listJoiner }${ deprecatedOptions.join(listJoiner) }` : '';
        console.warn('The following deprecated features are currently enabled but will be removed soon.' + pluginsMessage + optionsMessage);
      }
    };
    const logWarnings = (rawOptions, normalizedOptions) => {
      logRemovedWarnings(rawOptions, normalizedOptions);
      logDeprecatedWarnings(rawOptions, normalizedOptions);
    };

    const DOM$8 = DOMUtils.DOM;
    const restoreOriginalStyles = editor => {
      DOM$8.setStyle(editor.id, 'display', editor.orgDisplay);
    };
    const safeDestroy = x => Optional.from(x).each(x => x.destroy());
    const clearDomReferences = editor => {
      const ed = editor;
      ed.contentAreaContainer = ed.formElement = ed.container = ed.editorContainer = null;
      ed.bodyElement = ed.contentDocument = ed.contentWindow = null;
      ed.iframeElement = ed.targetElm = null;
      const selection = editor.selection;
      if (selection) {
        const dom = selection.dom;
        ed.selection = selection.win = selection.dom = dom.doc = null;
      }
    };
    const restoreForm = editor => {
      const form = editor.formElement;
      if (form) {
        if (form._mceOldSubmit) {
          form.submit = form._mceOldSubmit;
          delete form._mceOldSubmit;
        }
        DOM$8.unbind(form, 'submit reset', editor.formEventDelegate);
      }
    };
    const remove$1 = editor => {
      if (!editor.removed) {
        const {_selectionOverrides, editorUpload} = editor;
        const body = editor.getBody();
        const element = editor.getElement();
        if (body) {
          editor.save({ is_removing: true });
        }
        editor.removed = true;
        editor.unbindAllNativeEvents();
        if (editor.hasHiddenInput && isNonNullable(element === null || element === void 0 ? void 0 : element.nextSibling)) {
          DOM$8.remove(element.nextSibling);
        }
        fireRemove(editor);
        editor.editorManager.remove(editor);
        if (!editor.inline && body) {
          restoreOriginalStyles(editor);
        }
        fireDetach(editor);
        DOM$8.remove(editor.getContainer());
        safeDestroy(_selectionOverrides);
        safeDestroy(editorUpload);
        editor.destroy();
      }
    };
    const destroy = (editor, automatic) => {
      const {selection, dom} = editor;
      if (editor.destroyed) {
        return;
      }
      if (!automatic && !editor.removed) {
        editor.remove();
        return;
      }
      if (!automatic) {
        editor.editorManager.off('beforeunload', editor._beforeUnload);
        if (editor.theme && editor.theme.destroy) {
          editor.theme.destroy();
        }
        safeDestroy(selection);
        safeDestroy(dom);
      }
      restoreForm(editor);
      clearDomReferences(editor);
      editor.destroyed = true;
    };

    const CreateIconManager = () => {
      const lookup = {};
      const add = (id, iconPack) => {
        lookup[id] = iconPack;
      };
      const get = id => {
        if (lookup[id]) {
          return lookup[id];
        } else {
          return { icons: {} };
        }
      };
      const has = id => has$2(lookup, id);
      return {
        add,
        get,
        has
      };
    };
    const IconManager = CreateIconManager();

    const ModelManager = AddOnManager.ModelManager;

    const getProp = (propName, elm) => {
      const rawElm = elm.dom;
      return rawElm[propName];
    };
    const getComputedSizeProp = (propName, elm) => parseInt(get$7(elm, propName), 10);
    const getClientWidth = curry(getProp, 'clientWidth');
    const getClientHeight = curry(getProp, 'clientHeight');
    const getMarginTop = curry(getComputedSizeProp, 'margin-top');
    const getMarginLeft = curry(getComputedSizeProp, 'margin-left');
    const getBoundingClientRect = elm => elm.dom.getBoundingClientRect();
    const isInsideElementContentArea = (bodyElm, clientX, clientY) => {
      const clientWidth = getClientWidth(bodyElm);
      const clientHeight = getClientHeight(bodyElm);
      return clientX >= 0 && clientY >= 0 && clientX <= clientWidth && clientY <= clientHeight;
    };
    const transpose = (inline, elm, clientX, clientY) => {
      const clientRect = getBoundingClientRect(elm);
      const deltaX = inline ? clientRect.left + elm.dom.clientLeft + getMarginLeft(elm) : 0;
      const deltaY = inline ? clientRect.top + elm.dom.clientTop + getMarginTop(elm) : 0;
      const x = clientX - deltaX;
      const y = clientY - deltaY;
      return {
        x,
        y
      };
    };
    const isXYInContentArea = (editor, clientX, clientY) => {
      const bodyElm = SugarElement.fromDom(editor.getBody());
      const targetElm = editor.inline ? bodyElm : documentElement(bodyElm);
      const transposedPoint = transpose(editor.inline, targetElm, clientX, clientY);
      return isInsideElementContentArea(targetElm, transposedPoint.x, transposedPoint.y);
    };
    const fromDomSafe = node => Optional.from(node).map(SugarElement.fromDom);
    const isEditorAttachedToDom = editor => {
      const rawContainer = editor.inline ? editor.getBody() : editor.getContentAreaContainer();
      return fromDomSafe(rawContainer).map(inBody).getOr(false);
    };

    var NotificationManagerImpl = () => {
      const unimplemented = () => {
        throw new Error('Theme did not provide a NotificationManager implementation.');
      };
      return {
        open: unimplemented,
        close: unimplemented,
        getArgs: unimplemented
      };
    };

    const NotificationManager = editor => {
      const notifications = [];
      const getImplementation = () => {
        const theme = editor.theme;
        return theme && theme.getNotificationManagerImpl ? theme.getNotificationManagerImpl() : NotificationManagerImpl();
      };
      const getTopNotification = () => {
        return Optional.from(notifications[0]);
      };
      const isEqual = (a, b) => {
        return a.type === b.type && a.text === b.text && !a.progressBar && !a.timeout && !b.progressBar && !b.timeout;
      };
      const reposition = () => {
        each$e(notifications, notification => {
          notification.reposition();
        });
      };
      const addNotification = notification => {
        notifications.push(notification);
      };
      const closeNotification = notification => {
        findIndex$2(notifications, otherNotification => {
          return otherNotification === notification;
        }).each(index => {
          notifications.splice(index, 1);
        });
      };
      const open = (spec, fireEvent = true) => {
        if (editor.removed || !isEditorAttachedToDom(editor)) {
          return {};
        }
        if (fireEvent) {
          editor.dispatch('BeforeOpenNotification', { notification: spec });
        }
        return find$2(notifications, notification => {
          return isEqual(getImplementation().getArgs(notification), spec);
        }).getOrThunk(() => {
          editor.editorManager.setActive(editor);
          const notification = getImplementation().open(spec, () => {
            closeNotification(notification);
            reposition();
            if (hasEditorOrUiFocus(editor)) {
              getTopNotification().fold(() => editor.focus(), top => focus$1(SugarElement.fromDom(top.getEl())));
            }
          });
          addNotification(notification);
          reposition();
          editor.dispatch('OpenNotification', { notification: { ...notification } });
          return notification;
        });
      };
      const close = () => {
        getTopNotification().each(notification => {
          getImplementation().close(notification);
          closeNotification(notification);
          reposition();
        });
      };
      const getNotifications = constant(notifications);
      const registerEvents = editor => {
        editor.on('SkinLoaded', () => {
          const serviceMessage = getServiceMessage(editor);
          if (serviceMessage) {
            open({
              text: serviceMessage,
              type: 'warning',
              timeout: 0
            }, false);
          }
          reposition();
        });
        editor.on('show ResizeEditor ResizeWindow NodeChange', () => {
          requestAnimationFrame(reposition);
        });
        editor.on('remove', () => {
          each$e(notifications.slice(), notification => {
            getImplementation().close(notification);
          });
        });
      };
      registerEvents(editor);
      return {
        open,
        close,
        getNotifications
      };
    };

    const PluginManager = AddOnManager.PluginManager;

    const ThemeManager = AddOnManager.ThemeManager;

    var WindowManagerImpl = () => {
      const unimplemented = () => {
        throw new Error('Theme did not provide a WindowManager implementation.');
      };
      return {
        open: unimplemented,
        openUrl: unimplemented,
        alert: unimplemented,
        confirm: unimplemented,
        close: unimplemented
      };
    };

    const WindowManager = editor => {
      let dialogs = [];
      const getImplementation = () => {
        const theme = editor.theme;
        return theme && theme.getWindowManagerImpl ? theme.getWindowManagerImpl() : WindowManagerImpl();
      };
      const funcBind = (scope, f) => {
        return (...args) => {
          return f ? f.apply(scope, args) : undefined;
        };
      };
      const fireOpenEvent = dialog => {
        editor.dispatch('OpenWindow', { dialog });
      };
      const fireCloseEvent = dialog => {
        editor.dispatch('CloseWindow', { dialog });
      };
      const addDialog = dialog => {
        dialogs.push(dialog);
        fireOpenEvent(dialog);
      };
      const closeDialog = dialog => {
        fireCloseEvent(dialog);
        dialogs = filter$5(dialogs, otherDialog => {
          return otherDialog !== dialog;
        });
        if (dialogs.length === 0) {
          editor.focus();
        }
      };
      const getTopDialog = () => {
        return Optional.from(dialogs[dialogs.length - 1]);
      };
      const storeSelectionAndOpenDialog = openDialog => {
        editor.editorManager.setActive(editor);
        store(editor);
        editor.ui.show();
        const dialog = openDialog();
        addDialog(dialog);
        return dialog;
      };
      const open = (args, params) => {
        return storeSelectionAndOpenDialog(() => getImplementation().open(args, params, closeDialog));
      };
      const openUrl = args => {
        return storeSelectionAndOpenDialog(() => getImplementation().openUrl(args, closeDialog));
      };
      const alert = (message, callback, scope) => {
        const windowManagerImpl = getImplementation();
        windowManagerImpl.alert(message, funcBind(scope ? scope : windowManagerImpl, callback));
      };
      const confirm = (message, callback, scope) => {
        const windowManagerImpl = getImplementation();
        windowManagerImpl.confirm(message, funcBind(scope ? scope : windowManagerImpl, callback));
      };
      const close = () => {
        getTopDialog().each(dialog => {
          getImplementation().close(dialog);
          closeDialog(dialog);
        });
      };
      editor.on('remove', () => {
        each$e(dialogs, dialog => {
          getImplementation().close(dialog);
        });
      });
      return {
        open,
        openUrl,
        alert,
        confirm,
        close
      };
    };

    const displayNotification = (editor, message) => {
      editor.notificationManager.open({
        type: 'error',
        text: message
      });
    };
    const displayError = (editor, message) => {
      if (editor._skinLoaded) {
        displayNotification(editor, message);
      } else {
        editor.on('SkinLoaded', () => {
          displayNotification(editor, message);
        });
      }
    };
    const uploadError = (editor, message) => {
      displayError(editor, I18n.translate([
        'Failed to upload image: {0}',
        message
      ]));
    };
    const logError = (editor, errorType, msg) => {
      fireError(editor, errorType, { message: msg });
      console.error(msg);
    };
    const createLoadError = (type, url, name) => name ? `Failed to load ${ type }: ${ name } from url ${ url }` : `Failed to load ${ type } url: ${ url }`;
    const pluginLoadError = (editor, url, name) => {
      logError(editor, 'PluginLoadError', createLoadError('plugin', url, name));
    };
    const iconsLoadError = (editor, url, name) => {
      logError(editor, 'IconsLoadError', createLoadError('icons', url, name));
    };
    const languageLoadError = (editor, url, name) => {
      logError(editor, 'LanguageLoadError', createLoadError('language', url, name));
    };
    const themeLoadError = (editor, url, name) => {
      logError(editor, 'ThemeLoadError', createLoadError('theme', url, name));
    };
    const modelLoadError = (editor, url, name) => {
      logError(editor, 'ModelLoadError', createLoadError('model', url, name));
    };
    const pluginInitError = (editor, name, err) => {
      const message = I18n.translate([
        'Failed to initialize plugin: {0}',
        name
      ]);
      fireError(editor, 'PluginLoadError', { message });
      initError(message, err);
      displayError(editor, message);
    };
    const initError = (message, ...x) => {
      const console = window.console;
      if (console) {
        if (console.error) {
          console.error(message, ...x);
        } else {
          console.log(message, ...x);
        }
      }
    };

    const isContentCssSkinName = url => /^[a-z0-9\-]+$/i.test(url);
    const toContentSkinResourceName = url => 'content/' + url + '/content.css';
    const isBundledCssSkinName = url => tinymce.Resource.has(toContentSkinResourceName(url));
    const getContentCssUrls = editor => {
      return transformToUrls(editor, getContentCss(editor));
    };
    const getFontCssUrls = editor => {
      return transformToUrls(editor, getFontCss(editor));
    };
    const transformToUrls = (editor, cssLinks) => {
      const skinUrl = editor.editorManager.baseURL + '/skins/content';
      const suffix = editor.editorManager.suffix;
      const contentCssFile = `content${ suffix }.css`;
      return map$3(cssLinks, url => {
        if (isBundledCssSkinName(url)) {
          return url;
        } else if (isContentCssSkinName(url) && !editor.inline) {
          return `${ skinUrl }/${ url }/${ contentCssFile }`;
        } else {
          return editor.documentBaseURI.toAbsolute(url);
        }
      });
    };
    const appendContentCssFromSettings = editor => {
      editor.contentCSS = editor.contentCSS.concat(getContentCssUrls(editor), getFontCssUrls(editor));
    };

    const getAllImages = elm => {
      return elm ? from(elm.getElementsByTagName('img')) : [];
    };
    const ImageScanner = (uploadStatus, blobCache) => {
      const cachedPromises = {};
      const findAll = (elm, predicate = always) => {
        const images = filter$5(getAllImages(elm), img => {
          const src = img.src;
          if (img.hasAttribute('data-mce-bogus')) {
            return false;
          }
          if (img.hasAttribute('data-mce-placeholder')) {
            return false;
          }
          if (!src || src === Env.transparentSrc) {
            return false;
          }
          if (startsWith(src, 'blob:')) {
            return !uploadStatus.isUploaded(src) && predicate(img);
          }
          if (startsWith(src, 'data:')) {
            return predicate(img);
          }
          return false;
        });
        const promises = map$3(images, img => {
          const imageSrc = img.src;
          if (has$2(cachedPromises, imageSrc)) {
            return cachedPromises[imageSrc].then(imageInfo => {
              if (isString(imageInfo)) {
                return imageInfo;
              } else {
                return {
                  image: img,
                  blobInfo: imageInfo.blobInfo
                };
              }
            });
          } else {
            const newPromise = imageToBlobInfo(blobCache, imageSrc).then(blobInfo => {
              delete cachedPromises[imageSrc];
              return {
                image: img,
                blobInfo
              };
            }).catch(error => {
              delete cachedPromises[imageSrc];
              return error;
            });
            cachedPromises[imageSrc] = newPromise;
            return newPromise;
          }
        });
        return Promise.all(promises);
      };
      return { findAll };
    };

    const UploadStatus = () => {
      const PENDING = 1, UPLOADED = 2;
      let blobUriStatuses = {};
      const createStatus = (status, resultUri) => {
        return {
          status,
          resultUri
        };
      };
      const hasBlobUri = blobUri => {
        return blobUri in blobUriStatuses;
      };
      const getResultUri = blobUri => {
        const result = blobUriStatuses[blobUri];
        return result ? result.resultUri : null;
      };
      const isPending = blobUri => {
        return hasBlobUri(blobUri) ? blobUriStatuses[blobUri].status === PENDING : false;
      };
      const isUploaded = blobUri => {
        return hasBlobUri(blobUri) ? blobUriStatuses[blobUri].status === UPLOADED : false;
      };
      const markPending = blobUri => {
        blobUriStatuses[blobUri] = createStatus(PENDING, null);
      };
      const markUploaded = (blobUri, resultUri) => {
        blobUriStatuses[blobUri] = createStatus(UPLOADED, resultUri);
      };
      const removeFailed = blobUri => {
        delete blobUriStatuses[blobUri];
      };
      const destroy = () => {
        blobUriStatuses = {};
      };
      return {
        hasBlobUri,
        getResultUri,
        isPending,
        isUploaded,
        markPending,
        markUploaded,
        removeFailed,
        destroy
      };
    };

    let count = 0;
    const seed = () => {
      const rnd = () => {
        return Math.round(Math.random() * 4294967295).toString(36);
      };
      const now = new Date().getTime();
      return 's' + now.toString(36) + rnd() + rnd() + rnd();
    };
    const uuid = prefix => {
      return prefix + count++ + seed();
    };

    const BlobCache = () => {
      let cache = [];
      const mimeToExt = mime => {
        const mimes = {
          'image/jpeg': 'jpg',
          'image/jpg': 'jpg',
          'image/gif': 'gif',
          'image/png': 'png',
          'image/apng': 'apng',
          'image/avif': 'avif',
          'image/svg+xml': 'svg',
          'image/webp': 'webp',
          'image/bmp': 'bmp',
          'image/tiff': 'tiff'
        };
        return mimes[mime.toLowerCase()] || 'dat';
      };
      const create = (o, blob, base64, name, filename) => {
        if (isString(o)) {
          const id = o;
          return toBlobInfo({
            id,
            name,
            filename,
            blob: blob,
            base64: base64
          });
        } else if (isObject(o)) {
          return toBlobInfo(o);
        } else {
          throw new Error('Unknown input type');
        }
      };
      const toBlobInfo = o => {
        if (!o.blob || !o.base64) {
          throw new Error('blob and base64 representations of the image are required for BlobInfo to be created');
        }
        const id = o.id || uuid('blobid');
        const name = o.name || id;
        const blob = o.blob;
        return {
          id: constant(id),
          name: constant(name),
          filename: constant(o.filename || name + '.' + mimeToExt(blob.type)),
          blob: constant(blob),
          base64: constant(o.base64),
          blobUri: constant(o.blobUri || URL.createObjectURL(blob)),
          uri: constant(o.uri)
        };
      };
      const add = blobInfo => {
        if (!get(blobInfo.id())) {
          cache.push(blobInfo);
        }
      };
      const findFirst = predicate => find$2(cache, predicate).getOrUndefined();
      const get = id => findFirst(cachedBlobInfo => cachedBlobInfo.id() === id);
      const getByUri = blobUri => findFirst(blobInfo => blobInfo.blobUri() === blobUri);
      const getByData = (base64, type) => findFirst(blobInfo => blobInfo.base64() === base64 && blobInfo.blob().type === type);
      const removeByUri = blobUri => {
        cache = filter$5(cache, blobInfo => {
          if (blobInfo.blobUri() === blobUri) {
            URL.revokeObjectURL(blobInfo.blobUri());
            return false;
          }
          return true;
        });
      };
      const destroy = () => {
        each$e(cache, cachedBlobInfo => {
          URL.revokeObjectURL(cachedBlobInfo.blobUri());
        });
        cache = [];
      };
      return {
        create,
        add,
        get,
        getByUri,
        getByData,
        findFirst,
        removeByUri,
        destroy
      };
    };

    const Uploader = (uploadStatus, settings) => {
      const pendingPromises = {};
      const pathJoin = (path1, path2) => {
        if (path1) {
          return path1.replace(/\/$/, '') + '/' + path2.replace(/^\//, '');
        }
        return path2;
      };
      const defaultHandler = (blobInfo, progress) => new Promise((success, failure) => {
        const xhr = new XMLHttpRequest();
        xhr.open('POST', settings.url);
        xhr.withCredentials = settings.credentials;
        xhr.upload.onprogress = e => {
          progress(e.loaded / e.total * 100);
        };
        xhr.onerror = () => {
          failure('Image upload failed due to a XHR Transport error. Code: ' + xhr.status);
        };
        xhr.onload = () => {
          if (xhr.status < 200 || xhr.status >= 300) {
            failure('HTTP Error: ' + xhr.status);
            return;
          }
          const json = JSON.parse(xhr.responseText);
          if (!json || !isString(json.location)) {
            failure('Invalid JSON: ' + xhr.responseText);
            return;
          }
          success(pathJoin(settings.basePath, json.location));
        };
        const formData = new FormData();
        formData.append('file', blobInfo.blob(), blobInfo.filename());
        xhr.send(formData);
      });
      const uploadHandler = isFunction(settings.handler) ? settings.handler : defaultHandler;
      const noUpload = () => new Promise(resolve => {
        resolve([]);
      });
      const handlerSuccess = (blobInfo, url) => ({
        url,
        blobInfo,
        status: true
      });
      const handlerFailure = (blobInfo, error) => ({
        url: '',
        blobInfo,
        status: false,
        error
      });
      const resolvePending = (blobUri, result) => {
        Tools.each(pendingPromises[blobUri], resolve => {
          resolve(result);
        });
        delete pendingPromises[blobUri];
      };
      const uploadBlobInfo = (blobInfo, handler, openNotification) => {
        uploadStatus.markPending(blobInfo.blobUri());
        return new Promise(resolve => {
          let notification;
          let progress;
          try {
            const closeNotification = () => {
              if (notification) {
                notification.close();
                progress = noop;
              }
            };
            const success = url => {
              closeNotification();
              uploadStatus.markUploaded(blobInfo.blobUri(), url);
              resolvePending(blobInfo.blobUri(), handlerSuccess(blobInfo, url));
              resolve(handlerSuccess(blobInfo, url));
            };
            const failure = error => {
              closeNotification();
              uploadStatus.removeFailed(blobInfo.blobUri());
              resolvePending(blobInfo.blobUri(), handlerFailure(blobInfo, error));
              resolve(handlerFailure(blobInfo, error));
            };
            progress = percent => {
              if (percent < 0 || percent > 100) {
                return;
              }
              Optional.from(notification).orThunk(() => Optional.from(openNotification).map(apply$1)).each(n => {
                notification = n;
                n.progressBar.value(percent);
              });
            };
            handler(blobInfo, progress).then(success, err => {
              failure(isString(err) ? { message: err } : err);
            });
          } catch (ex) {
            resolve(handlerFailure(blobInfo, ex));
          }
        });
      };
      const isDefaultHandler = handler => handler === defaultHandler;
      const pendingUploadBlobInfo = blobInfo => {
        const blobUri = blobInfo.blobUri();
        return new Promise(resolve => {
          pendingPromises[blobUri] = pendingPromises[blobUri] || [];
          pendingPromises[blobUri].push(resolve);
        });
      };
      const uploadBlobs = (blobInfos, openNotification) => {
        blobInfos = Tools.grep(blobInfos, blobInfo => !uploadStatus.isUploaded(blobInfo.blobUri()));
        return Promise.all(Tools.map(blobInfos, blobInfo => uploadStatus.isPending(blobInfo.blobUri()) ? pendingUploadBlobInfo(blobInfo) : uploadBlobInfo(blobInfo, uploadHandler, openNotification)));
      };
      const upload = (blobInfos, openNotification) => !settings.url && isDefaultHandler(uploadHandler) ? noUpload() : uploadBlobs(blobInfos, openNotification);
      return { upload };
    };

    const openNotification = editor => () => editor.notificationManager.open({
      text: editor.translate('Image uploading...'),
      type: 'info',
      timeout: -1,
      progressBar: true
    });
    const createUploader = (editor, uploadStatus) => Uploader(uploadStatus, {
      url: getImageUploadUrl(editor),
      basePath: getImageUploadBasePath(editor),
      credentials: getImagesUploadCredentials(editor),
      handler: getImagesUploadHandler(editor)
    });
    const ImageUploader = editor => {
      const uploadStatus = UploadStatus();
      const uploader = createUploader(editor, uploadStatus);
      return { upload: (blobInfos, showNotification = true) => uploader.upload(blobInfos, showNotification ? openNotification(editor) : undefined) };
    };

    const isEmptyForPadding = (editor, element) => editor.dom.isEmpty(element.dom) && isNonNullable(editor.schema.getTextBlockElements()[name(element)]);
    const addPaddingToEmpty = editor => element => {
      if (isEmptyForPadding(editor, element)) {
        append$1(element, SugarElement.fromHtml('<br data-mce-bogus="1" />'));
      }
    };
    const EditorUpload = editor => {
      const blobCache = BlobCache();
      let uploader, imageScanner;
      const uploadStatus = UploadStatus();
      const urlFilters = [];
      const aliveGuard = callback => {
        return result => {
          if (editor.selection) {
            return callback(result);
          }
          return [];
        };
      };
      const cacheInvalidator = url => url + (url.indexOf('?') === -1 ? '?' : '&') + new Date().getTime();
      const replaceString = (content, search, replace) => {
        let index = 0;
        do {
          index = content.indexOf(search, index);
          if (index !== -1) {
            content = content.substring(0, index) + replace + content.substr(index + search.length);
            index += replace.length - search.length + 1;
          }
        } while (index !== -1);
        return content;
      };
      const replaceImageUrl = (content, targetUrl, replacementUrl) => {
        const replacementString = `src="${ replacementUrl }"${ replacementUrl === Env.transparentSrc ? ' data-mce-placeholder="1"' : '' }`;
        content = replaceString(content, `src="${ targetUrl }"`, replacementString);
        content = replaceString(content, 'data-mce-src="' + targetUrl + '"', 'data-mce-src="' + replacementUrl + '"');
        return content;
      };
      const replaceUrlInUndoStack = (targetUrl, replacementUrl) => {
        each$e(editor.undoManager.data, level => {
          if (level.type === 'fragmented') {
            level.fragments = map$3(level.fragments, fragment => replaceImageUrl(fragment, targetUrl, replacementUrl));
          } else {
            level.content = replaceImageUrl(level.content, targetUrl, replacementUrl);
          }
        });
      };
      const replaceImageUriInView = (image, resultUri) => {
        const src = editor.convertURL(resultUri, 'src');
        replaceUrlInUndoStack(image.src, resultUri);
        setAll$1(SugarElement.fromDom(image), {
          'src': shouldReuseFileName(editor) ? cacheInvalidator(resultUri) : resultUri,
          'data-mce-src': src
        });
      };
      const uploadImages = () => {
        if (!uploader) {
          uploader = createUploader(editor, uploadStatus);
        }
        return scanForImages().then(aliveGuard(imageInfos => {
          const blobInfos = map$3(imageInfos, imageInfo => imageInfo.blobInfo);
          return uploader.upload(blobInfos, openNotification(editor)).then(aliveGuard(result => {
            const imagesToRemove = [];
            let shouldDispatchChange = false;
            const filteredResult = map$3(result, (uploadInfo, index) => {
              const {blobInfo, image} = imageInfos[index];
              let removed = false;
              if (uploadInfo.status && shouldReplaceBlobUris(editor)) {
                if (uploadInfo.url && !contains$1(image.src, uploadInfo.url)) {
                  shouldDispatchChange = true;
                }
                blobCache.removeByUri(image.src);
                if (isRtc(editor)) ; else {
                  replaceImageUriInView(image, uploadInfo.url);
                }
              } else if (uploadInfo.error) {
                if (uploadInfo.error.remove) {
                  replaceUrlInUndoStack(image.src, Env.transparentSrc);
                  imagesToRemove.push(image);
                  removed = true;
                }
                uploadError(editor, uploadInfo.error.message);
              }
              return {
                element: image,
                status: uploadInfo.status,
                uploadUri: uploadInfo.url,
                blobInfo,
                removed
              };
            });
            if (imagesToRemove.length > 0 && !isRtc(editor)) {
              editor.undoManager.transact(() => {
                each$e(fromDom$1(imagesToRemove), sugarElement => {
                  const parentOpt = parent(sugarElement);
                  remove$5(sugarElement);
                  parentOpt.each(addPaddingToEmpty(editor));
                  blobCache.removeByUri(sugarElement.dom.src);
                });
              });
            } else if (shouldDispatchChange) {
              editor.undoManager.dispatchChange();
            }
            return filteredResult;
          }));
        }));
      };
      const uploadImagesAuto = () => isAutomaticUploadsEnabled(editor) ? uploadImages() : Promise.resolve([]);
      const isValidDataUriImage = imgElm => forall(urlFilters, filter => filter(imgElm));
      const addFilter = filter => {
        urlFilters.push(filter);
      };
      const scanForImages = () => {
        if (!imageScanner) {
          imageScanner = ImageScanner(uploadStatus, blobCache);
        }
        return imageScanner.findAll(editor.getBody(), isValidDataUriImage).then(aliveGuard(result => {
          const filteredResult = filter$5(result, resultItem => {
            if (isString(resultItem)) {
              displayError(editor, resultItem);
              return false;
            } else if (resultItem.uriType === 'blob') {
              return false;
            } else {
              return true;
            }
          });
          if (isRtc(editor)) ; else {
            each$e(filteredResult, resultItem => {
              replaceUrlInUndoStack(resultItem.image.src, resultItem.blobInfo.blobUri());
              resultItem.image.src = resultItem.blobInfo.blobUri();
              resultItem.image.removeAttribute('data-mce-src');
            });
          }
          return filteredResult;
        }));
      };
      const destroy = () => {
        blobCache.destroy();
        uploadStatus.destroy();
        imageScanner = uploader = null;
      };
      const replaceBlobUris = content => {
        return content.replace(/src="(blob:[^"]+)"/g, (match, blobUri) => {
          const resultUri = uploadStatus.getResultUri(blobUri);
          if (resultUri) {
            return 'src="' + resultUri + '"';
          }
          let blobInfo = blobCache.getByUri(blobUri);
          if (!blobInfo) {
            blobInfo = foldl(editor.editorManager.get(), (result, editor) => {
              return result || editor.editorUpload && editor.editorUpload.blobCache.getByUri(blobUri);
            }, undefined);
          }
          if (blobInfo) {
            const blob = blobInfo.blob();
            return 'src="data:' + blob.type + ';base64,' + blobInfo.base64() + '"';
          }
          return match;
        });
      };
      editor.on('SetContent', () => {
        if (isAutomaticUploadsEnabled(editor)) {
          uploadImagesAuto();
        } else {
          scanForImages();
        }
      });
      editor.on('RawSaveContent', e => {
        e.content = replaceBlobUris(e.content);
      });
      editor.on('GetContent', e => {
        if (e.source_view || e.format === 'raw' || e.format === 'tree') {
          return;
        }
        e.content = replaceBlobUris(e.content);
      });
      editor.on('PostRender', () => {
        editor.parser.addNodeFilter('img', images => {
          each$e(images, img => {
            const src = img.attr('src');
            if (!src || blobCache.getByUri(src)) {
              return;
            }
            const resultUri = uploadStatus.getResultUri(src);
            if (resultUri) {
              img.attr('src', resultUri);
            }
          });
        });
      });
      return {
        blobCache,
        addFilter,
        uploadImages,
        uploadImagesAuto,
        scanForImages,
        destroy
      };
    };

    const get$1 = editor => {
      const dom = editor.dom;
      const schemaType = editor.schema.type;
      const formats = {
        valigntop: [{
            selector: 'td,th',
            styles: { verticalAlign: 'top' }
          }],
        valignmiddle: [{
            selector: 'td,th',
            styles: { verticalAlign: 'middle' }
          }],
        valignbottom: [{
            selector: 'td,th',
            styles: { verticalAlign: 'bottom' }
          }],
        alignleft: [
          {
            selector: 'figure.image',
            collapsed: false,
            classes: 'align-left',
            ceFalseOverride: true,
            preview: 'font-family font-size'
          },
          {
            selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li,pre',
            styles: { textAlign: 'left' },
            inherit: false,
            preview: false
          },
          {
            selector: 'img,audio,video',
            collapsed: false,
            styles: { float: 'left' },
            preview: 'font-family font-size'
          },
          {
            selector: 'table',
            collapsed: false,
            styles: {
              marginLeft: '0px',
              marginRight: 'auto'
            },
            onformat: table => {
              dom.setStyle(table, 'float', null);
            },
            preview: 'font-family font-size'
          },
          {
            selector: '.mce-preview-object,[data-ephox-embed-iri]',
            ceFalseOverride: true,
            styles: { float: 'left' }
          }
        ],
        aligncenter: [
          {
            selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li,pre',
            styles: { textAlign: 'center' },
            inherit: false,
            preview: 'font-family font-size'
          },
          {
            selector: 'figure.image',
            collapsed: false,
            classes: 'align-center',
            ceFalseOverride: true,
            preview: 'font-family font-size'
          },
          {
            selector: 'img,audio,video',
            collapsed: false,
            styles: {
              display: 'block',
              marginLeft: 'auto',
              marginRight: 'auto'
            },
            preview: false
          },
          {
            selector: 'table',
            collapsed: false,
            styles: {
              marginLeft: 'auto',
              marginRight: 'auto'
            },
            preview: 'font-family font-size'
          },
          {
            selector: '.mce-preview-object',
            ceFalseOverride: true,
            styles: {
              display: 'table',
              marginLeft: 'auto',
              marginRight: 'auto'
            },
            preview: false
          },
          {
            selector: '[data-ephox-embed-iri]',
            ceFalseOverride: true,
            styles: {
              marginLeft: 'auto',
              marginRight: 'auto'
            },
            preview: false
          }
        ],
        alignright: [
          {
            selector: 'figure.image',
            collapsed: false,
            classes: 'align-right',
            ceFalseOverride: true,
            preview: 'font-family font-size'
          },
          {
            selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li,pre',
            styles: { textAlign: 'right' },
            inherit: false,
            preview: 'font-family font-size'
          },
          {
            selector: 'img,audio,video',
            collapsed: false,
            styles: { float: 'right' },
            preview: 'font-family font-size'
          },
          {
            selector: 'table',
            collapsed: false,
            styles: {
              marginRight: '0px',
              marginLeft: 'auto'
            },
            onformat: table => {
              dom.setStyle(table, 'float', null);
            },
            preview: 'font-family font-size'
          },
          {
            selector: '.mce-preview-object,[data-ephox-embed-iri]',
            ceFalseOverride: true,
            styles: { float: 'right' },
            preview: false
          }
        ],
        alignjustify: [{
            selector: 'figure,p,h1,h2,h3,h4,h5,h6,td,th,tr,div,ul,ol,li,pre',
            styles: { textAlign: 'justify' },
            inherit: false,
            preview: 'font-family font-size'
          }],
        bold: [
          {
            inline: 'strong',
            remove: 'all',
            preserve_attributes: [
              'class',
              'style'
            ]
          },
          {
            inline: 'span',
            styles: { fontWeight: 'bold' }
          },
          {
            inline: 'b',
            remove: 'all',
            preserve_attributes: [
              'class',
              'style'
            ]
          }
        ],
        italic: [
          {
            inline: 'em',
            remove: 'all',
            preserve_attributes: [
              'class',
              'style'
            ]
          },
          {
            inline: 'span',
            styles: { fontStyle: 'italic' }
          },
          {
            inline: 'i',
            remove: 'all',
            preserve_attributes: [
              'class',
              'style'
            ]
          }
        ],
        underline: [
          {
            inline: 'span',
            styles: { textDecoration: 'underline' },
            exact: true
          },
          {
            inline: 'u',
            remove: 'all',
            preserve_attributes: [
              'class',
              'style'
            ]
          }
        ],
        strikethrough: (() => {
          const span = {
            inline: 'span',
            styles: { textDecoration: 'line-through' },
            exact: true
          };
          const strike = {
            inline: 'strike',
            remove: 'all',
            preserve_attributes: [
              'class',
              'style'
            ]
          };
          const s = {
            inline: 's',
            remove: 'all',
            preserve_attributes: [
              'class',
              'style'
            ]
          };
          return schemaType !== 'html4' ? [
            s,
            span,
            strike
          ] : [
            span,
            s,
            strike
          ];
        })(),
        forecolor: {
          inline: 'span',
          styles: { color: '%value' },
          links: true,
          remove_similar: true,
          clear_child_styles: true
        },
        hilitecolor: {
          inline: 'span',
          styles: { backgroundColor: '%value' },
          links: true,
          remove_similar: true,
          clear_child_styles: true
        },
        fontname: {
          inline: 'span',
          toggle: false,
          styles: { fontFamily: '%value' },
          clear_child_styles: true
        },
        fontsize: {
          inline: 'span',
          toggle: false,
          styles: { fontSize: '%value' },
          clear_child_styles: true
        },
        lineheight: {
          selector: 'h1,h2,h3,h4,h5,h6,p,li,td,th,div',
          styles: { lineHeight: '%value' }
        },
        fontsize_class: {
          inline: 'span',
          attributes: { class: '%value' }
        },
        blockquote: {
          block: 'blockquote',
          wrapper: true,
          remove: 'all'
        },
        subscript: { inline: 'sub' },
        superscript: { inline: 'sup' },
        code: { inline: 'code' },
        link: {
          inline: 'a',
          selector: 'a',
          remove: 'all',
          split: true,
          deep: true,
          onmatch: (node, _fmt, _itemName) => {
            return isElement$6(node) && node.hasAttribute('href');
          },
          onformat: (elm, _fmt, vars) => {
            Tools.each(vars, (value, key) => {
              dom.setAttrib(elm, key, value);
            });
          }
        },
        lang: {
          inline: 'span',
          clear_child_styles: true,
          remove_similar: true,
          attributes: {
            'lang': '%value',
            'data-mce-lang': vars => {
              var _a;
              return (_a = vars === null || vars === void 0 ? void 0 : vars.customValue) !== null && _a !== void 0 ? _a : null;
            }
          }
        },
        removeformat: [
          {
            selector: 'b,strong,em,i,font,u,strike,s,sub,sup,dfn,code,samp,kbd,var,cite,mark,q,del,ins,small',
            remove: 'all',
            split: true,
            expand: false,
            block_expand: true,
            deep: true
          },
          {
            selector: 'span',
            attributes: [
              'style',
              'class'
            ],
            remove: 'empty',
            split: true,
            expand: false,
            deep: true
          },
          {
            selector: '*',
            attributes: [
              'style',
              'class'
            ],
            split: false,
            expand: false,
            deep: true
          }
        ]
      };
      Tools.each('p h1 h2 h3 h4 h5 h6 div address pre dt dd samp'.split(/\s/), name => {
        formats[name] = {
          block: name,
          remove: 'all'
        };
      });
      return formats;
    };

    const genericBase = {
      remove_similar: true,
      inherit: false
    };
    const cellBase = {
      selector: 'td,th',
      ...genericBase
    };
    const cellFormats = {
      tablecellbackgroundcolor: {
        styles: { backgroundColor: '%value' },
        ...cellBase
      },
      tablecellverticalalign: {
        styles: { 'vertical-align': '%value' },
        ...cellBase
      },
      tablecellbordercolor: {
        styles: { borderColor: '%value' },
        ...cellBase
      },
      tablecellclass: {
        classes: ['%value'],
        ...cellBase
      },
      tableclass: {
        selector: 'table',
        classes: ['%value'],
        ...genericBase
      },
      tablecellborderstyle: {
        styles: { borderStyle: '%value' },
        ...cellBase
      },
      tablecellborderwidth: {
        styles: { borderWidth: '%value' },
        ...cellBase
      }
    };
    const get = constant(cellFormats);

    const FormatRegistry = editor => {
      const formats = {};
      const get$2 = name => isNonNullable(name) ? formats[name] : formats;
      const has = name => has$2(formats, name);
      const register = (name, format) => {
        if (name) {
          if (!isString(name)) {
            each$d(name, (format, name) => {
              register(name, format);
            });
          } else {
            if (!isArray$1(format)) {
              format = [format];
            }
            each$e(format, format => {
              if (isUndefined(format.deep)) {
                format.deep = !isSelectorFormat(format);
              }
              if (isUndefined(format.split)) {
                format.split = !isSelectorFormat(format) || isInlineFormat(format);
              }
              if (isUndefined(format.remove) && isSelectorFormat(format) && !isInlineFormat(format)) {
                format.remove = 'none';
              }
              if (isSelectorFormat(format) && isInlineFormat(format)) {
                format.mixed = true;
                format.block_expand = true;
              }
              if (isString(format.classes)) {
                format.classes = format.classes.split(/\s+/);
              }
            });
            formats[name] = format;
          }
        }
      };
      const unregister = name => {
        if (name && formats[name]) {
          delete formats[name];
        }
        return formats;
      };
      register(get$1(editor));
      register(get());
      register(getFormats(editor));
      return {
        get: get$2,
        has,
        register,
        unregister
      };
    };

    const each$3 = Tools.each;
    const dom = DOMUtils.DOM;
    const isPreviewItem = item => isNonNullable(item) && isObject(item);
    const parsedSelectorToHtml = (ancestry, editor) => {
      const schema = editor && editor.schema || Schema({});
      const decorate = (elm, item) => {
        if (item.classes.length > 0) {
          dom.addClass(elm, item.classes.join(' '));
        }
        dom.setAttribs(elm, item.attrs);
      };
      const createElement = sItem => {
        const item = isString(sItem) ? {
          name: sItem,
          classes: [],
          attrs: {}
        } : sItem;
        const elm = dom.create(item.name);
        decorate(elm, item);
        return elm;
      };
      const getRequiredParent = (elm, candidate) => {
        const elmRule = schema.getElementRule(elm.nodeName.toLowerCase());
        const parentsRequired = elmRule === null || elmRule === void 0 ? void 0 : elmRule.parentsRequired;
        if (parentsRequired && parentsRequired.length) {
          return candidate && contains$2(parentsRequired, candidate) ? candidate : parentsRequired[0];
        } else {
          return false;
        }
      };
      const wrapInHtml = (elm, ancestors, siblings) => {
        let parentCandidate;
        const ancestor = ancestors[0];
        const ancestorName = isPreviewItem(ancestor) ? ancestor.name : undefined;
        const parentRequired = getRequiredParent(elm, ancestorName);
        if (parentRequired) {
          if (ancestorName === parentRequired) {
            parentCandidate = ancestor;
            ancestors = ancestors.slice(1);
          } else {
            parentCandidate = parentRequired;
          }
        } else if (ancestor) {
          parentCandidate = ancestor;
          ancestors = ancestors.slice(1);
        } else if (!siblings) {
          return elm;
        }
        const parent = parentCandidate ? createElement(parentCandidate) : dom.create('div');
        parent.appendChild(elm);
        if (siblings) {
          Tools.each(siblings, sibling => {
            const siblingElm = createElement(sibling);
            parent.insertBefore(siblingElm, elm);
          });
        }
        const parentSiblings = isPreviewItem(parentCandidate) ? parentCandidate.siblings : undefined;
        return wrapInHtml(parent, ancestors, parentSiblings);
      };
      const fragment = dom.create('div');
      if (ancestry.length > 0) {
        const item = ancestry[0];
        const elm = createElement(item);
        const siblings = isPreviewItem(item) ? item.siblings : undefined;
        fragment.appendChild(wrapInHtml(elm, ancestry.slice(1), siblings));
      }
      return fragment;
    };
    const parseSelectorItem = item => {
      item = Tools.trim(item);
      let tagName = 'div';
      const obj = {
        name: tagName,
        classes: [],
        attrs: {},
        selector: item
      };
      if (item !== '*') {
        tagName = item.replace(/(?:([#\.]|::?)([\w\-]+)|(\[)([^\]]+)\]?)/g, ($0, $1, $2, $3, $4) => {
          switch ($1) {
          case '#':
            obj.attrs.id = $2;
            break;
          case '.':
            obj.classes.push($2);
            break;
          case ':':
            if (Tools.inArray('checked disabled enabled read-only required'.split(' '), $2) !== -1) {
              obj.attrs[$2] = $2;
            }
            break;
          }
          if ($3 === '[') {
            const m = $4.match(/([\w\-]+)(?:\=\"([^\"]+))?/);
            if (m) {
              obj.attrs[m[1]] = m[2];
            }
          }
          return '';
        });
      }
      obj.name = tagName || 'div';
      return obj;
    };
    const parseSelector = selector => {
      if (!isString(selector)) {
        return [];
      }
      selector = selector.split(/\s*,\s*/)[0];
      selector = selector.replace(/\s*(~\+|~|\+|>)\s*/g, '$1');
      return Tools.map(selector.split(/(?:>|\s+(?![^\[\]]+\]))/), item => {
        const siblings = Tools.map(item.split(/(?:~\+|~|\+)/), parseSelectorItem);
        const obj = siblings.pop();
        if (siblings.length) {
          obj.siblings = siblings;
        }
        return obj;
      }).reverse();
    };
    const getCssText = (editor, format) => {
      let previewCss = '';
      let previewStyles = getPreviewStyles(editor);
      if (previewStyles === '') {
        return '';
      }
      const removeVars = val => {
        return isString(val) ? val.replace(/%(\w+)/g, '') : '';
      };
      const getComputedStyle = (name, elm) => {
        return dom.getStyle(elm !== null && elm !== void 0 ? elm : editor.getBody(), name, true);
      };
      if (isString(format)) {
        const formats = editor.formatter.get(format);
        if (!formats) {
          return '';
        }
        format = formats[0];
      }
      if ('preview' in format) {
        const preview = format.preview;
        if (preview === false) {
          return '';
        } else {
          previewStyles = preview || previewStyles;
        }
      }
      let name = format.block || format.inline || 'span';
      let previewFrag;
      const items = parseSelector(format.selector);
      if (items.length > 0) {
        if (!items[0].name) {
          items[0].name = name;
        }
        name = format.selector;
        previewFrag = parsedSelectorToHtml(items, editor);
      } else {
        previewFrag = parsedSelectorToHtml([name], editor);
      }
      const previewElm = dom.select(name, previewFrag)[0] || previewFrag.firstChild;
      each$3(format.styles, (value, name) => {
        const newValue = removeVars(value);
        if (newValue) {
          dom.setStyle(previewElm, name, newValue);
        }
      });
      each$3(format.attributes, (value, name) => {
        const newValue = removeVars(value);
        if (newValue) {
          dom.setAttrib(previewElm, name, newValue);
        }
      });
      each$3(format.classes, value => {
        const newValue = removeVars(value);
        if (!dom.hasClass(previewElm, newValue)) {
          dom.addClass(previewElm, newValue);
        }
      });
      editor.dispatch('PreviewFormats');
      dom.setStyles(previewFrag, {
        position: 'absolute',
        left: -65535
      });
      editor.getBody().appendChild(previewFrag);
      const rawParentFontSize = getComputedStyle('fontSize');
      const parentFontSize = /px$/.test(rawParentFontSize) ? parseInt(rawParentFontSize, 10) : 0;
      each$3(previewStyles.split(' '), name => {
        let value = getComputedStyle(name, previewElm);
        if (name === 'background-color' && /transparent|rgba\s*\([^)]+,\s*0\)/.test(value)) {
          value = getComputedStyle(name);
          if (rgbaToHexString(value).toLowerCase() === '#ffffff') {
            return;
          }
        }
        if (name === 'color') {
          if (rgbaToHexString(value).toLowerCase() === '#000000') {
            return;
          }
        }
        if (name === 'font-size') {
          if (/em|%$/.test(value)) {
            if (parentFontSize === 0) {
              return;
            }
            const numValue = parseFloat(value) / (/%$/.test(value) ? 100 : 1);
            value = numValue * parentFontSize + 'px';
          }
        }
        if (name === 'border' && value) {
          previewCss += 'padding:0 2px;';
        }
        previewCss += name + ':' + value + ';';
      });
      editor.dispatch('AfterPreviewFormats');
      dom.remove(previewFrag);
      return previewCss;
    };

    const setup$s = editor => {
      editor.addShortcut('meta+b', '', 'Bold');
      editor.addShortcut('meta+i', '', 'Italic');
      editor.addShortcut('meta+u', '', 'Underline');
      for (let i = 1; i <= 6; i++) {
        editor.addShortcut('access+' + i, '', [
          'FormatBlock',
          false,
          'h' + i
        ]);
      }
      editor.addShortcut('access+7', '', [
        'FormatBlock',
        false,
        'p'
      ]);
      editor.addShortcut('access+8', '', [
        'FormatBlock',
        false,
        'div'
      ]);
      editor.addShortcut('access+9', '', [
        'FormatBlock',
        false,
        'address'
      ]);
    };

    const Formatter = editor => {
      const formats = FormatRegistry(editor);
      const formatChangeState = Cell({});
      setup$s(editor);
      setup$v(editor);
      if (!isRtc(editor)) {
        setup$u(formatChangeState, editor);
      }
      return {
        get: formats.get,
        has: formats.has,
        register: formats.register,
        unregister: formats.unregister,
        apply: (name, vars, node) => {
          applyFormat(editor, name, vars, node);
        },
        remove: (name, vars, node, similar) => {
          removeFormat(editor, name, vars, node, similar);
        },
        toggle: (name, vars, node) => {
          toggleFormat(editor, name, vars, node);
        },
        match: (name, vars, node, similar) => matchFormat(editor, name, vars, node, similar),
        closest: names => closestFormat(editor, names),
        matchAll: (names, vars) => matchAllFormats(editor, names, vars),
        matchNode: (node, name, vars, similar) => matchNodeFormat(editor, node, name, vars, similar),
        canApply: name => canApplyFormat(editor, name),
        formatChanged: (formats, callback, similar, vars) => formatChanged(editor, formatChangeState, formats, callback, similar, vars),
        getCssText: curry(getCssText, editor)
      };
    };

    const shouldIgnoreCommand = cmd => {
      switch (cmd.toLowerCase()) {
      case 'undo':
      case 'redo':
      case 'mcefocus':
        return true;
      default:
        return false;
      }
    };
    const registerEvents = (editor, undoManager, locks) => {
      const isFirstTypedCharacter = Cell(false);
      const addNonTypingUndoLevel = e => {
        setTyping(undoManager, false, locks);
        undoManager.add({}, e);
      };
      editor.on('init', () => {
        undoManager.add();
      });
      editor.on('BeforeExecCommand', e => {
        const cmd = e.command;
        if (!shouldIgnoreCommand(cmd)) {
          endTyping(undoManager, locks);
          undoManager.beforeChange();
        }
      });
      editor.on('ExecCommand', e => {
        const cmd = e.command;
        if (!shouldIgnoreCommand(cmd)) {
          addNonTypingUndoLevel(e);
        }
      });
      editor.on('ObjectResizeStart cut', () => {
        undoManager.beforeChange();
      });
      editor.on('SaveContent ObjectResized blur', addNonTypingUndoLevel);
      editor.on('dragend', addNonTypingUndoLevel);
      editor.on('keyup', e => {
        const keyCode = e.keyCode;
        if (e.isDefaultPrevented()) {
          return;
        }
        const isMeta = Env.os.isMacOS() && e.key === 'Meta';
        if (keyCode >= 33 && keyCode <= 36 || keyCode >= 37 && keyCode <= 40 || keyCode === 45 || e.ctrlKey || isMeta) {
          addNonTypingUndoLevel();
          editor.nodeChanged();
        }
        if (keyCode === 46 || keyCode === 8) {
          editor.nodeChanged();
        }
        if (isFirstTypedCharacter.get() && undoManager.typing && !isEq$1(createFromEditor(editor), undoManager.data[0])) {
          if (!editor.isDirty()) {
            editor.setDirty(true);
          }
          editor.dispatch('TypingUndo');
          isFirstTypedCharacter.set(false);
          editor.nodeChanged();
        }
      });
      editor.on('keydown', e => {
        const keyCode = e.keyCode;
        if (e.isDefaultPrevented()) {
          return;
        }
        if (keyCode >= 33 && keyCode <= 36 || keyCode >= 37 && keyCode <= 40 || keyCode === 45) {
          if (undoManager.typing) {
            addNonTypingUndoLevel(e);
          }
          return;
        }
        const modKey = e.ctrlKey && !e.altKey || e.metaKey;
        if ((keyCode < 16 || keyCode > 20) && keyCode !== 224 && keyCode !== 91 && !undoManager.typing && !modKey) {
          undoManager.beforeChange();
          setTyping(undoManager, true, locks);
          undoManager.add({}, e);
          isFirstTypedCharacter.set(true);
          return;
        }
        const hasOnlyMetaOrCtrlModifier = Env.os.isMacOS() ? e.metaKey : e.ctrlKey && !e.altKey;
        if (hasOnlyMetaOrCtrlModifier) {
          undoManager.beforeChange();
        }
      });
      editor.on('mousedown', e => {
        if (undoManager.typing) {
          addNonTypingUndoLevel(e);
        }
      });
      const isInsertReplacementText = event => event.inputType === 'insertReplacementText';
      const isInsertTextDataNull = event => event.inputType === 'insertText' && event.data === null;
      const isInsertFromPasteOrDrop = event => event.inputType === 'insertFromPaste' || event.inputType === 'insertFromDrop';
      editor.on('input', e => {
        if (e.inputType && (isInsertReplacementText(e) || isInsertTextDataNull(e) || isInsertFromPasteOrDrop(e))) {
          addNonTypingUndoLevel(e);
        }
      });
      editor.on('AddUndo Undo Redo ClearUndos', e => {
        if (!e.isDefaultPrevented()) {
          editor.nodeChanged();
        }
      });
    };
    const addKeyboardShortcuts = editor => {
      editor.addShortcut('meta+z', '', 'Undo');
      editor.addShortcut('meta+y,meta+shift+z', '', 'Redo');
    };

    const UndoManager = editor => {
      const beforeBookmark = value$2();
      const locks = Cell(0);
      const index = Cell(0);
      const undoManager = {
        data: [],
        typing: false,
        beforeChange: () => {
          beforeChange(editor, locks, beforeBookmark);
        },
        add: (level, event) => {
          return addUndoLevel(editor, undoManager, index, locks, beforeBookmark, level, event);
        },
        dispatchChange: () => {
          editor.setDirty(true);
          const level = createFromEditor(editor);
          level.bookmark = getUndoBookmark(editor.selection);
          editor.dispatch('change', {
            level,
            lastLevel: get$b(undoManager.data, index.get()).getOrUndefined()
          });
        },
        undo: () => {
          return undo(editor, undoManager, locks, index);
        },
        redo: () => {
          return redo(editor, index, undoManager.data);
        },
        clear: () => {
          clear(editor, undoManager, index);
        },
        reset: () => {
          reset(editor, undoManager);
        },
        hasUndo: () => {
          return hasUndo(editor, undoManager, index);
        },
        hasRedo: () => {
          return hasRedo(editor, undoManager, index);
        },
        transact: callback => {
          return transact(editor, undoManager, locks, callback);
        },
        ignore: callback => {
          ignore(editor, locks, callback);
        },
        extra: (callback1, callback2) => {
          extra(editor, undoManager, index, callback1, callback2);
        }
      };
      if (!isRtc(editor)) {
        registerEvents(editor, undoManager, locks);
      }
      addKeyboardShortcuts(editor);
      return undoManager;
    };

    const nonTypingKeycodes = [
      9,
      27,
      VK.HOME,
      VK.END,
      19,
      20,
      44,
      144,
      145,
      33,
      34,
      45,
      16,
      17,
      18,
      91,
      92,
      93,
      VK.DOWN,
      VK.UP,
      VK.LEFT,
      VK.RIGHT
    ].concat(Env.browser.isFirefox() ? [224] : []);
    const placeholderAttr = 'data-mce-placeholder';
    const isKeyboardEvent = e => e.type === 'keydown' || e.type === 'keyup';
    const isDeleteEvent = e => {
      const keyCode = e.keyCode;
      return keyCode === VK.BACKSPACE || keyCode === VK.DELETE;
    };
    const isNonTypingKeyboardEvent = e => {
      if (isKeyboardEvent(e)) {
        const keyCode = e.keyCode;
        return !isDeleteEvent(e) && (VK.metaKeyPressed(e) || e.altKey || keyCode >= 112 && keyCode <= 123 || contains$2(nonTypingKeycodes, keyCode));
      } else {
        return false;
      }
    };
    const isTypingKeyboardEvent = e => isKeyboardEvent(e) && !(isDeleteEvent(e) || e.type === 'keyup' && e.keyCode === 229);
    const isVisuallyEmpty = (dom, rootElm, forcedRootBlock) => {
      if (isEmpty$2(SugarElement.fromDom(rootElm), false)) {
        const firstElement = rootElm.firstElementChild;
        if (!firstElement) {
          return true;
        } else if (dom.getStyle(rootElm.firstElementChild, 'padding-left') || dom.getStyle(rootElm.firstElementChild, 'padding-right')) {
          return false;
        } else {
          return forcedRootBlock === firstElement.nodeName.toLowerCase();
        }
      } else {
        return false;
      }
    };
    const setup$r = editor => {
      var _a;
      const dom = editor.dom;
      const rootBlock = getForcedRootBlock(editor);
      const placeholder = (_a = getPlaceholder(editor)) !== null && _a !== void 0 ? _a : '';
      const updatePlaceholder = (e, initial) => {
        if (isNonTypingKeyboardEvent(e)) {
          return;
        }
        const body = editor.getBody();
        const showPlaceholder = isTypingKeyboardEvent(e) ? false : isVisuallyEmpty(dom, body, rootBlock);
        const isPlaceholderShown = dom.getAttrib(body, placeholderAttr) !== '';
        if (isPlaceholderShown !== showPlaceholder || initial) {
          dom.setAttrib(body, placeholderAttr, showPlaceholder ? placeholder : null);
          dom.setAttrib(body, 'aria-placeholder', showPlaceholder ? placeholder : null);
          firePlaceholderToggle(editor, showPlaceholder);
          editor.on(showPlaceholder ? 'keydown' : 'keyup', updatePlaceholder);
          editor.off(showPlaceholder ? 'keyup' : 'keydown', updatePlaceholder);
        }
      };
      if (isNotEmpty(placeholder)) {
        editor.on('init', e => {
          updatePlaceholder(e, true);
          editor.on('change SetContent ExecCommand', updatePlaceholder);
          editor.on('paste', e => Delay.setEditorTimeout(editor, () => updatePlaceholder(e)));
        });
      }
    };

    const blockPosition = (block, position) => ({
      block,
      position
    });
    const blockBoundary = (from, to) => ({
      from,
      to
    });
    const getBlockPosition = (rootNode, pos) => {
      const rootElm = SugarElement.fromDom(rootNode);
      const containerElm = SugarElement.fromDom(pos.container());
      return getParentBlock$2(rootElm, containerElm).map(block => blockPosition(block, pos));
    };
    const isDifferentBlocks = blockBoundary => !eq(blockBoundary.from.block, blockBoundary.to.block);
    const getClosestHost = (root, scope) => {
      const isRoot = node => eq(node, root);
      const isHost = node => isTableCell$2(node) || isContentEditableTrue$3(node.dom);
      return closest$4(scope, isHost, isRoot).filter(isElement$7).getOr(root);
    };
    const hasSameHost = (rootNode, blockBoundary) => {
      const root = SugarElement.fromDom(rootNode);
      return eq(getClosestHost(root, blockBoundary.from.block), getClosestHost(root, blockBoundary.to.block));
    };
    const isEditable$1 = blockBoundary => isContentEditableFalse$b(blockBoundary.from.block.dom) === false && isContentEditableFalse$b(blockBoundary.to.block.dom) === false;
    const hasValidBlocks = blockBoundary => {
      const isValidBlock = block => isTextBlock$2(block) || hasBlockAttr(block.dom);
      return isValidBlock(blockBoundary.from.block) && isValidBlock(blockBoundary.to.block);
    };
    const skipLastBr = (rootNode, forward, blockPosition) => {
      if (isBr$6(blockPosition.position.getNode()) && !isEmpty$2(blockPosition.block)) {
        return positionIn(false, blockPosition.block.dom).bind(lastPositionInBlock => {
          if (lastPositionInBlock.isEqual(blockPosition.position)) {
            return fromPosition(forward, rootNode, lastPositionInBlock).bind(to => getBlockPosition(rootNode, to));
          } else {
            return Optional.some(blockPosition);
          }
        }).getOr(blockPosition);
      } else {
        return blockPosition;
      }
    };
    const readFromRange = (rootNode, forward, rng) => {
      const fromBlockPos = getBlockPosition(rootNode, CaretPosition.fromRangeStart(rng));
      const toBlockPos = fromBlockPos.bind(blockPos => fromPosition(forward, rootNode, blockPos.position).bind(to => getBlockPosition(rootNode, to).map(blockPos => skipLastBr(rootNode, forward, blockPos))));
      return lift2(fromBlockPos, toBlockPos, blockBoundary).filter(blockBoundary => isDifferentBlocks(blockBoundary) && hasSameHost(rootNode, blockBoundary) && isEditable$1(blockBoundary) && hasValidBlocks(blockBoundary));
    };
    const read$1 = (rootNode, forward, rng) => rng.collapsed ? readFromRange(rootNode, forward, rng) : Optional.none();

    const getChildrenUntilBlockBoundary = (block, schema) => {
      const children = children$1(block);
      return findIndex$2(children, el => schema.isBlock(name(el))).fold(constant(children), index => children.slice(0, index));
    };
    const extractChildren = (block, schema) => {
      const children = getChildrenUntilBlockBoundary(block, schema);
      each$e(children, remove$5);
      return children;
    };
    const removeEmptyRoot = (rootNode, block) => {
      const parents = parentsAndSelf(block, rootNode);
      return find$2(parents.reverse(), element => isEmpty$2(element)).each(remove$5);
    };
    const isEmptyBefore = el => filter$5(prevSiblings(el), el => !isEmpty$2(el)).length === 0;
    const nestedBlockMerge = (rootNode, fromBlock, toBlock, schema, insertionPoint) => {
      if (isEmpty$2(toBlock)) {
        fillWithPaddingBr(toBlock);
        return firstPositionIn(toBlock.dom);
      }
      if (isEmptyBefore(insertionPoint) && isEmpty$2(fromBlock)) {
        before$3(insertionPoint, SugarElement.fromTag('br'));
      }
      const position = prevPosition(toBlock.dom, CaretPosition.before(insertionPoint.dom));
      each$e(extractChildren(fromBlock, schema), child => {
        before$3(insertionPoint, child);
      });
      removeEmptyRoot(rootNode, fromBlock);
      return position;
    };
    const isInline = (schema, node) => schema.isInline(name(node));
    const sidelongBlockMerge = (rootNode, fromBlock, toBlock, schema) => {
      if (isEmpty$2(toBlock)) {
        if (isEmpty$2(fromBlock)) {
          const getInlineToBlockDescendants = el => {
            const helper = (node, elements) => firstChild(node).fold(() => elements, child => isInline(schema, child) ? helper(child, elements.concat(shallow$1(child))) : elements);
            return helper(el, []);
          };
          const newFromBlockDescendants = foldr(getInlineToBlockDescendants(toBlock), (element, descendant) => {
            wrap$2(element, descendant);
            return descendant;
          }, createPaddingBr());
          empty(fromBlock);
          append$1(fromBlock, newFromBlockDescendants);
        }
        remove$5(toBlock);
        return firstPositionIn(fromBlock.dom);
      }
      const position = lastPositionIn(toBlock.dom);
      each$e(extractChildren(fromBlock, schema), child => {
        append$1(toBlock, child);
      });
      removeEmptyRoot(rootNode, fromBlock);
      return position;
    };
    const findInsertionPoint = (toBlock, block) => {
      const parentsAndSelf$1 = parentsAndSelf(block, toBlock);
      return Optional.from(parentsAndSelf$1[parentsAndSelf$1.length - 1]);
    };
    const getInsertionPoint = (fromBlock, toBlock) => contains(toBlock, fromBlock) ? findInsertionPoint(toBlock, fromBlock) : Optional.none();
    const trimBr = (first, block) => {
      positionIn(first, block.dom).bind(position => Optional.from(position.getNode())).map(SugarElement.fromDom).filter(isBr$5).each(remove$5);
    };
    const mergeBlockInto = (rootNode, fromBlock, toBlock, schema) => {
      trimBr(true, fromBlock);
      trimBr(false, toBlock);
      return getInsertionPoint(fromBlock, toBlock).fold(curry(sidelongBlockMerge, rootNode, fromBlock, toBlock, schema), curry(nestedBlockMerge, rootNode, fromBlock, toBlock, schema));
    };
    const mergeBlocks = (rootNode, forward, block1, block2, schema) => forward ? mergeBlockInto(rootNode, block2, block1, schema) : mergeBlockInto(rootNode, block1, block2, schema);

    const backspaceDelete$9 = (editor, forward) => {
      const rootNode = SugarElement.fromDom(editor.getBody());
      const position = read$1(rootNode.dom, forward, editor.selection.getRng()).map(blockBoundary => () => {
        mergeBlocks(rootNode, forward, blockBoundary.from.block, blockBoundary.to.block, editor.schema).each(pos => {
          editor.selection.setRng(pos.toRange());
        });
      });
      return position;
    };

    const deleteRangeMergeBlocks = (rootNode, selection, schema) => {
      const rng = selection.getRng();
      return lift2(getParentBlock$2(rootNode, SugarElement.fromDom(rng.startContainer)), getParentBlock$2(rootNode, SugarElement.fromDom(rng.endContainer)), (block1, block2) => {
        if (!eq(block1, block2)) {
          return Optional.some(() => {
            rng.deleteContents();
            mergeBlocks(rootNode, true, block1, block2, schema).each(pos => {
              selection.setRng(pos.toRange());
            });
          });
        } else {
          return Optional.none();
        }
      }).getOr(Optional.none());
    };
    const isRawNodeInTable = (root, rawNode) => {
      const node = SugarElement.fromDom(rawNode);
      const isRoot = curry(eq, root);
      return ancestor$4(node, isTableCell$2, isRoot).isSome();
    };
    const isSelectionInTable = (root, rng) => isRawNodeInTable(root, rng.startContainer) || isRawNodeInTable(root, rng.endContainer);
    const isEverythingSelected = (root, rng) => {
      const noPrevious = prevPosition(root.dom, CaretPosition.fromRangeStart(rng)).isNone();
      const noNext = nextPosition(root.dom, CaretPosition.fromRangeEnd(rng)).isNone();
      return !isSelectionInTable(root, rng) && noPrevious && noNext;
    };
    const emptyEditor = editor => {
      return Optional.some(() => {
        editor.setContent('');
        editor.selection.setCursorLocation();
      });
    };
    const deleteRange$2 = editor => {
      const rootNode = SugarElement.fromDom(editor.getBody());
      const rng = editor.selection.getRng();
      return isEverythingSelected(rootNode, rng) ? emptyEditor(editor) : deleteRangeMergeBlocks(rootNode, editor.selection, editor.schema);
    };
    const backspaceDelete$8 = (editor, _forward) => editor.selection.isCollapsed() ? Optional.none() : deleteRange$2(editor);

    const showCaret = (direction, editor, node, before, scrollIntoView) => Optional.from(editor._selectionOverrides.showCaret(direction, node, before, scrollIntoView));
    const getNodeRange = node => {
      const rng = node.ownerDocument.createRange();
      rng.selectNode(node);
      return rng;
    };
    const selectNode = (editor, node) => {
      const e = editor.dispatch('BeforeObjectSelected', { target: node });
      if (e.isDefaultPrevented()) {
        return Optional.none();
      }
      return Optional.some(getNodeRange(node));
    };
    const renderCaretAtRange = (editor, range, scrollIntoView) => {
      const normalizedRange = normalizeRange(1, editor.getBody(), range);
      const caretPosition = CaretPosition.fromRangeStart(normalizedRange);
      const caretPositionNode = caretPosition.getNode();
      if (isInlineFakeCaretTarget(caretPositionNode)) {
        return showCaret(1, editor, caretPositionNode, !caretPosition.isAtEnd(), false);
      }
      const caretPositionBeforeNode = caretPosition.getNode(true);
      if (isInlineFakeCaretTarget(caretPositionBeforeNode)) {
        return showCaret(1, editor, caretPositionBeforeNode, false, false);
      }
      const ceRoot = getContentEditableRoot$1(editor.dom.getRoot(), caretPosition.getNode());
      if (isInlineFakeCaretTarget(ceRoot)) {
        return showCaret(1, editor, ceRoot, false, scrollIntoView);
      }
      return Optional.none();
    };
    const renderRangeCaret = (editor, range, scrollIntoView) => range.collapsed ? renderCaretAtRange(editor, range, scrollIntoView).getOr(range) : range;

    const isBeforeBoundary = pos => isBeforeContentEditableFalse(pos) || isBeforeMedia(pos);
    const isAfterBoundary = pos => isAfterContentEditableFalse(pos) || isAfterMedia(pos);
    const trimEmptyTextNode = (dom, node) => {
      if (isText$a(node) && node.data.length === 0) {
        dom.remove(node);
      }
    };
    const deleteContentAndShowCaret = (editor, range, node, direction, forward, peekCaretPosition) => {
      showCaret(direction, editor, peekCaretPosition.getNode(!forward), forward, true).each(caretRange => {
        if (range.collapsed) {
          const deleteRange = range.cloneRange();
          if (forward) {
            deleteRange.setEnd(caretRange.startContainer, caretRange.startOffset);
          } else {
            deleteRange.setStart(caretRange.endContainer, caretRange.endOffset);
          }
          deleteRange.deleteContents();
        } else {
          range.deleteContents();
        }
        editor.selection.setRng(caretRange);
      });
      trimEmptyTextNode(editor.dom, node);
    };
    const deleteBoundaryText = (editor, forward) => {
      const range = editor.selection.getRng();
      if (!isText$a(range.commonAncestorContainer)) {
        return Optional.none();
      }
      const direction = forward ? HDirection.Forwards : HDirection.Backwards;
      const caretWalker = CaretWalker(editor.getBody());
      const getNextPosFn = curry(getVisualCaretPosition, forward ? caretWalker.next : caretWalker.prev);
      const isBeforeFn = forward ? isBeforeBoundary : isAfterBoundary;
      const caretPosition = getNormalizedRangeEndPoint(direction, editor.getBody(), range);
      const nextCaretPosition = getNextPosFn(caretPosition);
      const normalizedNextCaretPosition = nextCaretPosition ? normalizePosition(forward, nextCaretPosition) : nextCaretPosition;
      if (!normalizedNextCaretPosition || !isMoveInsideSameBlock(caretPosition, normalizedNextCaretPosition)) {
        return Optional.none();
      } else if (isBeforeFn(normalizedNextCaretPosition)) {
        return Optional.some(() => deleteContentAndShowCaret(editor, range, caretPosition.getNode(), direction, forward, normalizedNextCaretPosition));
      }
      const peekCaretPosition = getNextPosFn(normalizedNextCaretPosition);
      if (peekCaretPosition && isBeforeFn(peekCaretPosition)) {
        if (isMoveInsideSameBlock(normalizedNextCaretPosition, peekCaretPosition)) {
          return Optional.some(() => deleteContentAndShowCaret(editor, range, caretPosition.getNode(), direction, forward, peekCaretPosition));
        }
      }
      return Optional.none();
    };
    const backspaceDelete$7 = (editor, forward) => deleteBoundaryText(editor, forward);

    const getEdgeCefPosition = (editor, atStart) => {
      const root = editor.getBody();
      return atStart ? firstPositionIn(root).filter(isBeforeContentEditableFalse) : lastPositionIn(root).filter(isAfterContentEditableFalse);
    };
    const isCefAtEdgeSelected = editor => {
      const rng = editor.selection.getRng();
      return !rng.collapsed && (getEdgeCefPosition(editor, true).exists(pos => pos.isEqual(CaretPosition.fromRangeStart(rng))) || getEdgeCefPosition(editor, false).exists(pos => pos.isEqual(CaretPosition.fromRangeEnd(rng))));
    };

    const isCompoundElement = node => isNonNullable(node) && (isTableCell$2(SugarElement.fromDom(node)) || isListItem$1(SugarElement.fromDom(node)));
    const DeleteAction = Adt.generate([
      { remove: ['element'] },
      { moveToElement: ['element'] },
      { moveToPosition: ['position'] }
    ]);
    const isAtContentEditableBlockCaret = (forward, from) => {
      const elm = from.getNode(!forward);
      const caretLocation = forward ? 'after' : 'before';
      return isElement$6(elm) && elm.getAttribute('data-mce-caret') === caretLocation;
    };
    const isDeleteFromCefDifferentBlocks = (root, forward, from, to, schema) => {
      const inSameBlock = elm => schema.isInline(elm.nodeName.toLowerCase()) && !isInSameBlock(from, to, root);
      return getRelativeCefElm(!forward, from).fold(() => getRelativeCefElm(forward, to).fold(never, inSameBlock), inSameBlock);
    };
    const deleteEmptyBlockOrMoveToCef = (root, forward, from, to) => {
      const toCefElm = to.getNode(!forward);
      return getParentBlock$2(SugarElement.fromDom(root), SugarElement.fromDom(from.getNode())).map(blockElm => isEmpty$2(blockElm) ? DeleteAction.remove(blockElm.dom) : DeleteAction.moveToElement(toCefElm)).orThunk(() => Optional.some(DeleteAction.moveToElement(toCefElm)));
    };
    const findCefPosition = (root, forward, from, schema) => fromPosition(forward, root, from).bind(to => {
      if (isCompoundElement(to.getNode())) {
        return Optional.none();
      } else if (isDeleteFromCefDifferentBlocks(root, forward, from, to, schema)) {
        return Optional.none();
      } else if (forward && isContentEditableFalse$b(to.getNode())) {
        return deleteEmptyBlockOrMoveToCef(root, forward, from, to);
      } else if (!forward && isContentEditableFalse$b(to.getNode(true))) {
        return deleteEmptyBlockOrMoveToCef(root, forward, from, to);
      } else if (forward && isAfterContentEditableFalse(from)) {
        return Optional.some(DeleteAction.moveToPosition(to));
      } else if (!forward && isBeforeContentEditableFalse(from)) {
        return Optional.some(DeleteAction.moveToPosition(to));
      } else {
        return Optional.none();
      }
    });
    const getContentEditableBlockAction = (forward, elm) => {
      if (isNullable(elm)) {
        return Optional.none();
      } else if (forward && isContentEditableFalse$b(elm.nextSibling)) {
        return Optional.some(DeleteAction.moveToElement(elm.nextSibling));
      } else if (!forward && isContentEditableFalse$b(elm.previousSibling)) {
        return Optional.some(DeleteAction.moveToElement(elm.previousSibling));
      } else {
        return Optional.none();
      }
    };
    const skipMoveToActionFromInlineCefToContent = (root, from, deleteAction) => deleteAction.fold(elm => Optional.some(DeleteAction.remove(elm)), elm => Optional.some(DeleteAction.moveToElement(elm)), to => {
      if (isInSameBlock(from, to, root)) {
        return Optional.none();
      } else {
        return Optional.some(DeleteAction.moveToPosition(to));
      }
    });
    const getContentEditableAction = (root, forward, from, schema) => {
      if (isAtContentEditableBlockCaret(forward, from)) {
        return getContentEditableBlockAction(forward, from.getNode(!forward)).orThunk(() => findCefPosition(root, forward, from, schema));
      } else {
        return findCefPosition(root, forward, from, schema).bind(deleteAction => skipMoveToActionFromInlineCefToContent(root, from, deleteAction));
      }
    };
    const read = (root, forward, rng, schema) => {
      const normalizedRange = normalizeRange(forward ? 1 : -1, root, rng);
      const from = CaretPosition.fromRangeStart(normalizedRange);
      const rootElement = SugarElement.fromDom(root);
      if (!forward && isAfterContentEditableFalse(from)) {
        return Optional.some(DeleteAction.remove(from.getNode(true)));
      } else if (forward && isBeforeContentEditableFalse(from)) {
        return Optional.some(DeleteAction.remove(from.getNode()));
      } else if (!forward && isBeforeContentEditableFalse(from) && isAfterBr(rootElement, from, schema)) {
        return findPreviousBr(rootElement, from, schema).map(br => DeleteAction.remove(br.getNode()));
      } else if (forward && isAfterContentEditableFalse(from) && isBeforeBr$1(rootElement, from, schema)) {
        return findNextBr(rootElement, from, schema).map(br => DeleteAction.remove(br.getNode()));
      } else {
        return getContentEditableAction(root, forward, from, schema);
      }
    };

    const deleteElement$1 = (editor, forward) => element => {
      editor._selectionOverrides.hideFakeCaret();
      deleteElement$2(editor, forward, SugarElement.fromDom(element));
      return true;
    };
    const moveToElement = (editor, forward) => element => {
      const pos = forward ? CaretPosition.before(element) : CaretPosition.after(element);
      editor.selection.setRng(pos.toRange());
      return true;
    };
    const moveToPosition = editor => pos => {
      editor.selection.setRng(pos.toRange());
      return true;
    };
    const getAncestorCe = (editor, node) => Optional.from(getContentEditableRoot$1(editor.getBody(), node));
    const backspaceDeleteCaret = (editor, forward) => {
      const selectedNode = editor.selection.getNode();
      return getAncestorCe(editor, selectedNode).filter(isContentEditableFalse$b).fold(() => read(editor.getBody(), forward, editor.selection.getRng(), editor.schema).map(deleteAction => () => deleteAction.fold(deleteElement$1(editor, forward), moveToElement(editor, forward), moveToPosition(editor))), () => Optional.some(noop));
    };
    const deleteOffscreenSelection = rootElement => {
      each$e(descendants(rootElement, '.mce-offscreen-selection'), remove$5);
    };
    const backspaceDeleteRange = (editor, forward) => {
      const selectedNode = editor.selection.getNode();
      if (isContentEditableFalse$b(selectedNode) && !isTableCell$3(selectedNode)) {
        const hasCefAncestor = getAncestorCe(editor, selectedNode.parentNode).filter(isContentEditableFalse$b);
        return hasCefAncestor.fold(() => Optional.some(() => {
          deleteOffscreenSelection(SugarElement.fromDom(editor.getBody()));
          deleteElement$2(editor, forward, SugarElement.fromDom(editor.selection.getNode()));
          paddEmptyBody(editor);
        }), () => Optional.some(noop));
      }
      if (isCefAtEdgeSelected(editor)) {
        return Optional.some(() => {
          deleteRangeContents(editor, editor.selection.getRng(), SugarElement.fromDom(editor.getBody()));
        });
      }
      return Optional.none();
    };
    const paddEmptyElement = editor => {
      const dom = editor.dom, selection = editor.selection;
      const ceRoot = getContentEditableRoot$1(editor.getBody(), selection.getNode());
      if (isContentEditableTrue$3(ceRoot) && dom.isBlock(ceRoot) && dom.isEmpty(ceRoot)) {
        const br = dom.create('br', { 'data-mce-bogus': '1' });
        dom.setHTML(ceRoot, '');
        ceRoot.appendChild(br);
        selection.setRng(CaretPosition.before(br).toRange());
      }
      return true;
    };
    const backspaceDelete$6 = (editor, forward) => {
      if (editor.selection.isCollapsed()) {
        return backspaceDeleteCaret(editor, forward);
      } else {
        return backspaceDeleteRange(editor, forward);
      }
    };

    const deleteCaret$2 = (editor, forward) => {
      const fromPos = CaretPosition.fromRangeStart(editor.selection.getRng());
      return fromPosition(forward, editor.getBody(), fromPos).filter(pos => forward ? isBeforeImageBlock(pos) : isAfterImageBlock(pos)).bind(pos => getChildNodeAtRelativeOffset(forward ? 0 : -1, pos)).map(elm => () => editor.selection.select(elm));
    };
    const backspaceDelete$5 = (editor, forward) => editor.selection.isCollapsed() ? deleteCaret$2(editor, forward) : Optional.none();

    const isText$2 = isText$a;
    const startsWithCaretContainer = node => isText$2(node) && node.data[0] === ZWSP$1;
    const endsWithCaretContainer = node => isText$2(node) && node.data[node.data.length - 1] === ZWSP$1;
    const createZwsp = node => {
      var _a;
      const doc = (_a = node.ownerDocument) !== null && _a !== void 0 ? _a : document;
      return doc.createTextNode(ZWSP$1);
    };
    const insertBefore$1 = node => {
      var _a;
      if (isText$2(node.previousSibling)) {
        if (endsWithCaretContainer(node.previousSibling)) {
          return node.previousSibling;
        } else {
          node.previousSibling.appendData(ZWSP$1);
          return node.previousSibling;
        }
      } else if (isText$2(node)) {
        if (startsWithCaretContainer(node)) {
          return node;
        } else {
          node.insertData(0, ZWSP$1);
          return node;
        }
      } else {
        const newNode = createZwsp(node);
        (_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(newNode, node);
        return newNode;
      }
    };
    const insertAfter$1 = node => {
      var _a, _b;
      if (isText$2(node.nextSibling)) {
        if (startsWithCaretContainer(node.nextSibling)) {
          return node.nextSibling;
        } else {
          node.nextSibling.insertData(0, ZWSP$1);
          return node.nextSibling;
        }
      } else if (isText$2(node)) {
        if (endsWithCaretContainer(node)) {
          return node;
        } else {
          node.appendData(ZWSP$1);
          return node;
        }
      } else {
        const newNode = createZwsp(node);
        if (node.nextSibling) {
          (_a = node.parentNode) === null || _a === void 0 ? void 0 : _a.insertBefore(newNode, node.nextSibling);
        } else {
          (_b = node.parentNode) === null || _b === void 0 ? void 0 : _b.appendChild(newNode);
        }
        return newNode;
      }
    };
    const insertInline = (before, node) => before ? insertBefore$1(node) : insertAfter$1(node);
    const insertInlineBefore = curry(insertInline, true);
    const insertInlineAfter = curry(insertInline, false);

    const insertInlinePos = (pos, before) => {
      if (isText$a(pos.container())) {
        return insertInline(before, pos.container());
      } else {
        return insertInline(before, pos.getNode());
      }
    };
    const isPosCaretContainer = (pos, caret) => {
      const caretNode = caret.get();
      return caretNode && pos.container() === caretNode && isCaretContainerInline(caretNode);
    };
    const renderCaret = (caret, location) => location.fold(element => {
      remove$3(caret.get());
      const text = insertInlineBefore(element);
      caret.set(text);
      return Optional.some(CaretPosition(text, text.length - 1));
    }, element => firstPositionIn(element).map(pos => {
      if (!isPosCaretContainer(pos, caret)) {
        remove$3(caret.get());
        const text = insertInlinePos(pos, true);
        caret.set(text);
        return CaretPosition(text, 1);
      } else {
        const node = caret.get();
        return CaretPosition(node, 1);
      }
    }), element => lastPositionIn(element).map(pos => {
      if (!isPosCaretContainer(pos, caret)) {
        remove$3(caret.get());
        const text = insertInlinePos(pos, false);
        caret.set(text);
        return CaretPosition(text, text.length - 1);
      } else {
        const node = caret.get();
        return CaretPosition(node, node.length - 1);
      }
    }), element => {
      remove$3(caret.get());
      const text = insertInlineAfter(element);
      caret.set(text);
      return Optional.some(CaretPosition(text, 1));
    });

    const evaluateUntil = (fns, args) => {
      for (let i = 0; i < fns.length; i++) {
        const result = fns[i].apply(null, args);
        if (result.isSome()) {
          return result;
        }
      }
      return Optional.none();
    };

    const Location = Adt.generate([
      { before: ['element'] },
      { start: ['element'] },
      { end: ['element'] },
      { after: ['element'] }
    ]);
    const rescope$1 = (rootNode, node) => {
      const parentBlock = getParentBlock$3(node, rootNode);
      return parentBlock ? parentBlock : rootNode;
    };
    const before = (isInlineTarget, rootNode, pos) => {
      const nPos = normalizeForwards(pos);
      const scope = rescope$1(rootNode, nPos.container());
      return findRootInline(isInlineTarget, scope, nPos).fold(() => nextPosition(scope, nPos).bind(curry(findRootInline, isInlineTarget, scope)).map(inline => Location.before(inline)), Optional.none);
    };
    const isNotInsideFormatCaretContainer = (rootNode, elm) => getParentCaretContainer(rootNode, elm) === null;
    const findInsideRootInline = (isInlineTarget, rootNode, pos) => findRootInline(isInlineTarget, rootNode, pos).filter(curry(isNotInsideFormatCaretContainer, rootNode));
    const start$1 = (isInlineTarget, rootNode, pos) => {
      const nPos = normalizeBackwards(pos);
      return findInsideRootInline(isInlineTarget, rootNode, nPos).bind(inline => {
        const prevPos = prevPosition(inline, nPos);
        return prevPos.isNone() ? Optional.some(Location.start(inline)) : Optional.none();
      });
    };
    const end = (isInlineTarget, rootNode, pos) => {
      const nPos = normalizeForwards(pos);
      return findInsideRootInline(isInlineTarget, rootNode, nPos).bind(inline => {
        const nextPos = nextPosition(inline, nPos);
        return nextPos.isNone() ? Optional.some(Location.end(inline)) : Optional.none();
      });
    };
    const after = (isInlineTarget, rootNode, pos) => {
      const nPos = normalizeBackwards(pos);
      const scope = rescope$1(rootNode, nPos.container());
      return findRootInline(isInlineTarget, scope, nPos).fold(() => prevPosition(scope, nPos).bind(curry(findRootInline, isInlineTarget, scope)).map(inline => Location.after(inline)), Optional.none);
    };
    const isValidLocation = location => !isRtl(getElement(location));
    const readLocation = (isInlineTarget, rootNode, pos) => {
      const location = evaluateUntil([
        before,
        start$1,
        end,
        after
      ], [
        isInlineTarget,
        rootNode,
        pos
      ]);
      return location.filter(isValidLocation);
    };
    const getElement = location => location.fold(identity, identity, identity, identity);
    const getName = location => location.fold(constant('before'), constant('start'), constant('end'), constant('after'));
    const outside = location => location.fold(Location.before, Location.before, Location.after, Location.after);
    const inside = location => location.fold(Location.start, Location.start, Location.end, Location.end);
    const isEq = (location1, location2) => getName(location1) === getName(location2) && getElement(location1) === getElement(location2);
    const betweenInlines = (forward, isInlineTarget, rootNode, from, to, location) => lift2(findRootInline(isInlineTarget, rootNode, from), findRootInline(isInlineTarget, rootNode, to), (fromInline, toInline) => {
      if (fromInline !== toInline && hasSameParentBlock(rootNode, fromInline, toInline)) {
        return Location.after(forward ? fromInline : toInline);
      } else {
        return location;
      }
    }).getOr(location);
    const skipNoMovement = (fromLocation, toLocation) => fromLocation.fold(always, fromLocation => !isEq(fromLocation, toLocation));
    const findLocationTraverse = (forward, isInlineTarget, rootNode, fromLocation, pos) => {
      const from = normalizePosition(forward, pos);
      const to = fromPosition(forward, rootNode, from).map(curry(normalizePosition, forward));
      const location = to.fold(() => fromLocation.map(outside), to => readLocation(isInlineTarget, rootNode, to).map(curry(betweenInlines, forward, isInlineTarget, rootNode, from, to)).filter(curry(skipNoMovement, fromLocation)));
      return location.filter(isValidLocation);
    };
    const findLocationSimple = (forward, location) => {
      if (forward) {
        return location.fold(compose(Optional.some, Location.start), Optional.none, compose(Optional.some, Location.after), Optional.none);
      } else {
        return location.fold(Optional.none, compose(Optional.some, Location.before), Optional.none, compose(Optional.some, Location.end));
      }
    };
    const findLocation$1 = (forward, isInlineTarget, rootNode, pos) => {
      const from = normalizePosition(forward, pos);
      const fromLocation = readLocation(isInlineTarget, rootNode, from);
      return readLocation(isInlineTarget, rootNode, from).bind(curry(findLocationSimple, forward)).orThunk(() => findLocationTraverse(forward, isInlineTarget, rootNode, fromLocation, pos));
    };

    const hasSelectionModifyApi = editor => {
      return isFunction(editor.selection.getSel().modify);
    };
    const moveRel = (forward, selection, pos) => {
      const delta = forward ? 1 : -1;
      selection.setRng(CaretPosition(pos.container(), pos.offset() + delta).toRange());
      selection.getSel().modify('move', forward ? 'forward' : 'backward', 'word');
      return true;
    };
    const moveByWord = (forward, editor) => {
      const rng = editor.selection.getRng();
      const pos = forward ? CaretPosition.fromRangeEnd(rng) : CaretPosition.fromRangeStart(rng);
      if (!hasSelectionModifyApi(editor)) {
        return false;
      } else if (forward && isBeforeInline(pos)) {
        return moveRel(true, editor.selection, pos);
      } else if (!forward && isAfterInline(pos)) {
        return moveRel(false, editor.selection, pos);
      } else {
        return false;
      }
    };

    var BreakType;
    (function (BreakType) {
      BreakType[BreakType['Br'] = 0] = 'Br';
      BreakType[BreakType['Block'] = 1] = 'Block';
      BreakType[BreakType['Wrap'] = 2] = 'Wrap';
      BreakType[BreakType['Eol'] = 3] = 'Eol';
    }(BreakType || (BreakType = {})));
    const flip = (direction, positions) => direction === HDirection.Backwards ? reverse(positions) : positions;
    const walk$1 = (direction, caretWalker, pos) => direction === HDirection.Forwards ? caretWalker.next(pos) : caretWalker.prev(pos);
    const getBreakType = (scope, direction, currentPos, nextPos) => {
      if (isBr$6(nextPos.getNode(direction === HDirection.Forwards))) {
        return BreakType.Br;
      } else if (isInSameBlock(currentPos, nextPos) === false) {
        return BreakType.Block;
      } else {
        return BreakType.Wrap;
      }
    };
    const getPositionsUntil = (predicate, direction, scope, start) => {
      const caretWalker = CaretWalker(scope);
      let currentPos = start;
      const positions = [];
      while (currentPos) {
        const nextPos = walk$1(direction, caretWalker, currentPos);
        if (!nextPos) {
          break;
        }
        if (isBr$6(nextPos.getNode(false))) {
          if (direction === HDirection.Forwards) {
            return {
              positions: flip(direction, positions).concat([nextPos]),
              breakType: BreakType.Br,
              breakAt: Optional.some(nextPos)
            };
          } else {
            return {
              positions: flip(direction, positions),
              breakType: BreakType.Br,
              breakAt: Optional.some(nextPos)
            };
          }
        }
        if (!nextPos.isVisible()) {
          currentPos = nextPos;
          continue;
        }
        if (predicate(currentPos, nextPos)) {
          const breakType = getBreakType(scope, direction, currentPos, nextPos);
          return {
            positions: flip(direction, positions),
            breakType,
            breakAt: Optional.some(nextPos)
          };
        }
        positions.push(nextPos);
        currentPos = nextPos;
      }
      return {
        positions: flip(direction, positions),
        breakType: BreakType.Eol,
        breakAt: Optional.none()
      };
    };
    const getAdjacentLinePositions = (direction, getPositionsUntilBreak, scope, start) => getPositionsUntilBreak(scope, start).breakAt.map(pos => {
      const positions = getPositionsUntilBreak(scope, pos).positions;
      return direction === HDirection.Backwards ? positions.concat(pos) : [pos].concat(positions);
    }).getOr([]);
    const findClosestHorizontalPositionFromPoint = (positions, x) => foldl(positions, (acc, newPos) => acc.fold(() => Optional.some(newPos), lastPos => lift2(head(lastPos.getClientRects()), head(newPos.getClientRects()), (lastRect, newRect) => {
      const lastDist = Math.abs(x - lastRect.left);
      const newDist = Math.abs(x - newRect.left);
      return newDist <= lastDist ? newPos : lastPos;
    }).or(acc)), Optional.none());
    const findClosestHorizontalPosition = (positions, pos) => head(pos.getClientRects()).bind(targetRect => findClosestHorizontalPositionFromPoint(positions, targetRect.left));
    const getPositionsUntilPreviousLine = curry(getPositionsUntil, CaretPosition.isAbove, -1);
    const getPositionsUntilNextLine = curry(getPositionsUntil, CaretPosition.isBelow, 1);
    const getPositionsAbove = curry(getAdjacentLinePositions, -1, getPositionsUntilPreviousLine);
    const getPositionsBelow = curry(getAdjacentLinePositions, 1, getPositionsUntilNextLine);
    const isAtFirstLine = (scope, pos) => getPositionsUntilPreviousLine(scope, pos).breakAt.isNone();
    const isAtLastLine = (scope, pos) => getPositionsUntilNextLine(scope, pos).breakAt.isNone();
    const getFirstLinePositions = scope => firstPositionIn(scope).map(pos => [pos].concat(getPositionsUntilNextLine(scope, pos).positions)).getOr([]);
    const getLastLinePositions = scope => lastPositionIn(scope).map(pos => getPositionsUntilPreviousLine(scope, pos).positions.concat(pos)).getOr([]);
    const getClosestPositionAbove = (scope, pos) => findClosestHorizontalPosition(getPositionsAbove(scope, pos), pos);
    const getClosestPositionBelow = (scope, pos) => findClosestHorizontalPosition(getPositionsBelow(scope, pos), pos);

    const isContentEditableFalse$4 = isContentEditableFalse$b;
    const distanceToRectLeft$1 = (clientRect, clientX) => Math.abs(clientRect.left - clientX);
    const distanceToRectRight$1 = (clientRect, clientX) => Math.abs(clientRect.right - clientX);
    const isNodeClientRect = rect => hasNonNullableKey(rect, 'node');
    const findClosestClientRect = (clientRects, clientX) => reduce(clientRects, (oldClientRect, clientRect) => {
      const oldDistance = Math.min(distanceToRectLeft$1(oldClientRect, clientX), distanceToRectRight$1(oldClientRect, clientX));
      const newDistance = Math.min(distanceToRectLeft$1(clientRect, clientX), distanceToRectRight$1(clientRect, clientX));
      if (newDistance === oldDistance && isNodeClientRect(clientRect) && isContentEditableFalse$4(clientRect.node)) {
        return clientRect;
      }
      if (newDistance < oldDistance) {
        return clientRect;
      }
      return oldClientRect;
    });

    const getNodeClientRects = node => {
      const toArrayWithNode = clientRects => {
        return map$3(clientRects, rect => {
          const clientRect = clone$1(rect);
          clientRect.node = node;
          return clientRect;
        });
      };
      if (isElement$6(node)) {
        return toArrayWithNode(node.getClientRects());
      } else if (isText$a(node)) {
        const rng = node.ownerDocument.createRange();
        rng.setStart(node, 0);
        rng.setEnd(node, node.data.length);
        return toArrayWithNode(rng.getClientRects());
      } else {
        return [];
      }
    };
    const getClientRects = nodes => bind$3(nodes, getNodeClientRects);

    var VDirection;
    (function (VDirection) {
      VDirection[VDirection['Up'] = -1] = 'Up';
      VDirection[VDirection['Down'] = 1] = 'Down';
    }(VDirection || (VDirection = {})));
    const findUntil = (direction, root, predicateFn, node) => {
      let currentNode = node;
      while (currentNode = findNode(currentNode, direction, isEditableCaretCandidate$1, root)) {
        if (predicateFn(currentNode)) {
          return;
        }
      }
    };
    const walkUntil = (direction, isAboveFn, isBeflowFn, root, predicateFn, caretPosition) => {
      let line = 0;
      const result = [];
      const add = node => {
        let clientRects = getClientRects([node]);
        if (direction === -1) {
          clientRects = clientRects.reverse();
        }
        for (let i = 0; i < clientRects.length; i++) {
          const clientRect = clientRects[i];
          if (isBeflowFn(clientRect, targetClientRect)) {
            continue;
          }
          if (result.length > 0 && isAboveFn(clientRect, last$2(result))) {
            line++;
          }
          clientRect.line = line;
          if (predicateFn(clientRect)) {
            return true;
          }
          result.push(clientRect);
        }
        return false;
      };
      const targetClientRect = last$2(caretPosition.getClientRects());
      if (!targetClientRect) {
        return result;
      }
      const node = caretPosition.getNode();
      if (node) {
        add(node);
        findUntil(direction, root, add, node);
      }
      return result;
    };
    const aboveLineNumber = (lineNumber, clientRect) => clientRect.line > lineNumber;
    const isLineNumber = (lineNumber, clientRect) => clientRect.line === lineNumber;
    const upUntil = curry(walkUntil, VDirection.Up, isAbove$1, isBelow$1);
    const downUntil = curry(walkUntil, VDirection.Down, isBelow$1, isAbove$1);
    const getLastClientRect = caretPosition => {
      return last$2(caretPosition.getClientRects());
    };
    const positionsUntil = (direction, root, predicateFn, node) => {
      const caretWalker = CaretWalker(root);
      let walkFn;
      let isBelowFn;
      let isAboveFn;
      let caretPosition;
      const result = [];
      let line = 0;
      if (direction === 1) {
        walkFn = caretWalker.next;
        isBelowFn = isBelow$1;
        isAboveFn = isAbove$1;
        caretPosition = CaretPosition.after(node);
      } else {
        walkFn = caretWalker.prev;
        isBelowFn = isAbove$1;
        isAboveFn = isBelow$1;
        caretPosition = CaretPosition.before(node);
      }
      const targetClientRect = getLastClientRect(caretPosition);
      do {
        if (!caretPosition.isVisible()) {
          continue;
        }
        const rect = getLastClientRect(caretPosition);
        if (isAboveFn(rect, targetClientRect)) {
          continue;
        }
        if (result.length > 0 && isBelowFn(rect, last$2(result))) {
          line++;
        }
        const clientRect = clone$1(rect);
        clientRect.position = caretPosition;
        clientRect.line = line;
        if (predicateFn(clientRect)) {
          return result;
        }
        result.push(clientRect);
      } while (caretPosition = walkFn(caretPosition));
      return result;
    };
    const isAboveLine = lineNumber => clientRect => aboveLineNumber(lineNumber, clientRect);
    const isLine = lineNumber => clientRect => isLineNumber(lineNumber, clientRect);

    const moveToRange = (editor, rng) => {
      editor.selection.setRng(rng);
      scrollRangeIntoView(editor, editor.selection.getRng());
    };
    const renderRangeCaretOpt = (editor, range, scrollIntoView) => Optional.some(renderRangeCaret(editor, range, scrollIntoView));
    const moveHorizontally = (editor, direction, range, isBefore, isAfter, isElement) => {
      const forwards = direction === HDirection.Forwards;
      const caretWalker = CaretWalker(editor.getBody());
      const getNextPosFn = curry(getVisualCaretPosition, forwards ? caretWalker.next : caretWalker.prev);
      const isBeforeFn = forwards ? isBefore : isAfter;
      if (!range.collapsed) {
        const node = getSelectedNode(range);
        if (isElement(node)) {
          return showCaret(direction, editor, node, direction === HDirection.Backwards, false);
        } else if (isCefAtEdgeSelected(editor)) {
          const newRange = range.cloneRange();
          newRange.collapse(direction === HDirection.Backwards);
          return Optional.from(newRange);
        }
      }
      const caretPosition = getNormalizedRangeEndPoint(direction, editor.getBody(), range);
      if (isBeforeFn(caretPosition)) {
        return selectNode(editor, caretPosition.getNode(!forwards));
      }
      let nextCaretPosition = getNextPosFn(caretPosition);
      const rangeIsInContainerBlock = isRangeInCaretContainerBlock(range);
      if (!nextCaretPosition) {
        return rangeIsInContainerBlock ? Optional.some(range) : Optional.none();
      } else {
        nextCaretPosition = normalizePosition(forwards, nextCaretPosition);
      }
      if (isBeforeFn(nextCaretPosition)) {
        return showCaret(direction, editor, nextCaretPosition.getNode(!forwards), forwards, false);
      }
      const peekCaretPosition = getNextPosFn(nextCaretPosition);
      if (peekCaretPosition && isBeforeFn(peekCaretPosition)) {
        if (isMoveInsideSameBlock(nextCaretPosition, peekCaretPosition)) {
          return showCaret(direction, editor, peekCaretPosition.getNode(!forwards), forwards, false);
        }
      }
      if (rangeIsInContainerBlock) {
        return renderRangeCaretOpt(editor, nextCaretPosition.toRange(), false);
      }
      return Optional.none();
    };
    const moveVertically = (editor, direction, range, isBefore, isAfter, isElement) => {
      const caretPosition = getNormalizedRangeEndPoint(direction, editor.getBody(), range);
      const caretClientRect = last$2(caretPosition.getClientRects());
      const forwards = direction === VDirection.Down;
      const root = editor.getBody();
      if (!caretClientRect) {
        return Optional.none();
      }
      if (isCefAtEdgeSelected(editor)) {
        const caretPosition = forwards ? CaretPosition.fromRangeEnd(range) : CaretPosition.fromRangeStart(range);
        const getClosestFn = !forwards ? getClosestPositionAbove : getClosestPositionBelow;
        return getClosestFn(root, caretPosition).orThunk(() => Optional.from(caretPosition)).map(pos => pos.toRange());
      }
      const walkerFn = forwards ? downUntil : upUntil;
      const linePositions = walkerFn(root, isAboveLine(1), caretPosition);
      const nextLinePositions = filter$5(linePositions, isLine(1));
      const clientX = caretClientRect.left;
      const nextLineRect = findClosestClientRect(nextLinePositions, clientX);
      if (nextLineRect && isElement(nextLineRect.node)) {
        const dist1 = Math.abs(clientX - nextLineRect.left);
        const dist2 = Math.abs(clientX - nextLineRect.right);
        return showCaret(direction, editor, nextLineRect.node, dist1 < dist2, false);
      }
      let currentNode;
      if (isBefore(caretPosition)) {
        currentNode = caretPosition.getNode();
      } else if (isAfter(caretPosition)) {
        currentNode = caretPosition.getNode(true);
      } else {
        currentNode = getSelectedNode(range);
      }
      if (currentNode) {
        const caretPositions = positionsUntil(direction, root, isAboveLine(1), currentNode);
        let closestNextLineRect = findClosestClientRect(filter$5(caretPositions, isLine(1)), clientX);
        if (closestNextLineRect) {
          return renderRangeCaretOpt(editor, closestNextLineRect.position.toRange(), false);
        }
        closestNextLineRect = last$2(filter$5(caretPositions, isLine(0)));
        if (closestNextLineRect) {
          return renderRangeCaretOpt(editor, closestNextLineRect.position.toRange(), false);
        }
      }
      if (nextLinePositions.length === 0) {
        return getLineEndPoint(editor, forwards).filter(forwards ? isAfter : isBefore).map(pos => renderRangeCaret(editor, pos.toRange(), false));
      }
      return Optional.none();
    };
    const getLineEndPoint = (editor, forward) => {
      const rng = editor.selection.getRng();
      const from = forward ? CaretPosition.fromRangeEnd(rng) : CaretPosition.fromRangeStart(rng);
      const host = getEditingHost(from.container(), editor.getBody());
      if (forward) {
        const lineInfo = getPositionsUntilNextLine(host, from);
        return last$3(lineInfo.positions);
      } else {
        const lineInfo = getPositionsUntilPreviousLine(host, from);
        return head(lineInfo.positions);
      }
    };
    const moveToLineEndPoint$3 = (editor, forward, isElementPosition) => getLineEndPoint(editor, forward).filter(isElementPosition).exists(pos => {
      editor.selection.setRng(pos.toRange());
      return true;
    });

    const setCaretPosition = (editor, pos) => {
      const rng = editor.dom.createRng();
      rng.setStart(pos.container(), pos.offset());
      rng.setEnd(pos.container(), pos.offset());
      editor.selection.setRng(rng);
    };
    const setSelected = (state, elm) => {
      if (state) {
        elm.setAttribute('data-mce-selected', 'inline-boundary');
      } else {
        elm.removeAttribute('data-mce-selected');
      }
    };
    const renderCaretLocation = (editor, caret, location) => renderCaret(caret, location).map(pos => {
      setCaretPosition(editor, pos);
      return location;
    });
    const getPositionFromRange = (range, root, forward) => {
      const start = CaretPosition.fromRangeStart(range);
      if (range.collapsed) {
        return start;
      } else {
        const end = CaretPosition.fromRangeEnd(range);
        return forward ? prevPosition(root, end).getOr(end) : nextPosition(root, start).getOr(start);
      }
    };
    const findLocation = (editor, caret, forward) => {
      const rootNode = editor.getBody();
      const from = getPositionFromRange(editor.selection.getRng(), rootNode, forward);
      const isInlineTarget$1 = curry(isInlineTarget, editor);
      const location = findLocation$1(forward, isInlineTarget$1, rootNode, from);
      return location.bind(location => renderCaretLocation(editor, caret, location));
    };
    const toggleInlines = (isInlineTarget, dom, elms) => {
      const inlineBoundaries = map$3(descendants(SugarElement.fromDom(dom.getRoot()), '*[data-mce-selected="inline-boundary"]'), e => e.dom);
      const selectedInlines = filter$5(inlineBoundaries, isInlineTarget);
      const targetInlines = filter$5(elms, isInlineTarget);
      each$e(difference(selectedInlines, targetInlines), curry(setSelected, false));
      each$e(difference(targetInlines, selectedInlines), curry(setSelected, true));
    };
    const safeRemoveCaretContainer = (editor, caret) => {
      const caretValue = caret.get();
      if (editor.selection.isCollapsed() && !editor.composing && caretValue) {
        const pos = CaretPosition.fromRangeStart(editor.selection.getRng());
        if (CaretPosition.isTextPosition(pos) && !isAtZwsp(pos)) {
          setCaretPosition(editor, removeAndReposition(caretValue, pos));
          caret.set(null);
        }
      }
    };
    const renderInsideInlineCaret = (isInlineTarget, editor, caret, elms) => {
      if (editor.selection.isCollapsed()) {
        const inlines = filter$5(elms, isInlineTarget);
        each$e(inlines, _inline => {
          const pos = CaretPosition.fromRangeStart(editor.selection.getRng());
          readLocation(isInlineTarget, editor.getBody(), pos).bind(location => renderCaretLocation(editor, caret, location));
        });
      }
    };
    const move$3 = (editor, caret, forward) => isInlineBoundariesEnabled(editor) ? findLocation(editor, caret, forward).isSome() : false;
    const moveWord = (forward, editor, _caret) => isInlineBoundariesEnabled(editor) ? moveByWord(forward, editor) : false;
    const setupSelectedState = editor => {
      const caret = Cell(null);
      const isInlineTarget$1 = curry(isInlineTarget, editor);
      editor.on('NodeChange', e => {
        if (isInlineBoundariesEnabled(editor)) {
          toggleInlines(isInlineTarget$1, editor.dom, e.parents);
          safeRemoveCaretContainer(editor, caret);
          renderInsideInlineCaret(isInlineTarget$1, editor, caret, e.parents);
        }
      });
      return caret;
    };
    const moveNextWord = curry(moveWord, true);
    const movePrevWord = curry(moveWord, false);
    const moveToLineEndPoint$2 = (editor, forward, caret) => {
      if (isInlineBoundariesEnabled(editor)) {
        const linePoint = getLineEndPoint(editor, forward).getOrThunk(() => {
          const rng = editor.selection.getRng();
          return forward ? CaretPosition.fromRangeEnd(rng) : CaretPosition.fromRangeStart(rng);
        });
        return readLocation(curry(isInlineTarget, editor), editor.getBody(), linePoint).exists(loc => {
          const outsideLoc = outside(loc);
          return renderCaret(caret, outsideLoc).exists(pos => {
            setCaretPosition(editor, pos);
            return true;
          });
        });
      } else {
        return false;
      }
    };

    const rangeFromPositions = (from, to) => {
      const range = document.createRange();
      range.setStart(from.container(), from.offset());
      range.setEnd(to.container(), to.offset());
      return range;
    };
    const hasOnlyTwoOrLessPositionsLeft = elm => lift2(firstPositionIn(elm), lastPositionIn(elm), (firstPos, lastPos) => {
      const normalizedFirstPos = normalizePosition(true, firstPos);
      const normalizedLastPos = normalizePosition(false, lastPos);
      return nextPosition(elm, normalizedFirstPos).forall(pos => pos.isEqual(normalizedLastPos));
    }).getOr(true);
    const setCaretLocation = (editor, caret) => location => renderCaret(caret, location).map(pos => () => setCaretPosition(editor, pos));
    const deleteFromTo = (editor, caret, from, to) => {
      const rootNode = editor.getBody();
      const isInlineTarget$1 = curry(isInlineTarget, editor);
      editor.undoManager.ignore(() => {
        editor.selection.setRng(rangeFromPositions(from, to));
        execNativeDeleteCommand(editor);
        readLocation(isInlineTarget$1, rootNode, CaretPosition.fromRangeStart(editor.selection.getRng())).map(inside).bind(setCaretLocation(editor, caret)).each(call);
      });
      editor.nodeChanged();
    };
    const rescope = (rootNode, node) => {
      const parentBlock = getParentBlock$3(node, rootNode);
      return parentBlock ? parentBlock : rootNode;
    };
    const backspaceDeleteCollapsed = (editor, caret, forward, from) => {
      const rootNode = rescope(editor.getBody(), from.container());
      const isInlineTarget$1 = curry(isInlineTarget, editor);
      const fromLocation = readLocation(isInlineTarget$1, rootNode, from);
      const location = fromLocation.bind(location => {
        if (forward) {
          return location.fold(constant(Optional.some(inside(location))), Optional.none, constant(Optional.some(outside(location))), Optional.none);
        } else {
          return location.fold(Optional.none, constant(Optional.some(outside(location))), Optional.none, constant(Optional.some(inside(location))));
        }
      });
      return location.map(setCaretLocation(editor, caret)).getOrThunk(() => {
        const toPosition = navigate(forward, rootNode, from);
        const toLocation = toPosition.bind(pos => readLocation(isInlineTarget$1, rootNode, pos));
        return lift2(fromLocation, toLocation, () => findRootInline(isInlineTarget$1, rootNode, from).bind(elm => {
          if (hasOnlyTwoOrLessPositionsLeft(elm)) {
            return Optional.some(() => {
              deleteElement$2(editor, forward, SugarElement.fromDom(elm));
            });
          } else {
            return Optional.none();
          }
        })).getOrThunk(() => toLocation.bind(() => toPosition.map(to => {
          return () => {
            if (forward) {
              deleteFromTo(editor, caret, from, to);
            } else {
              deleteFromTo(editor, caret, to, from);
            }
          };
        })));
      });
    };
    const backspaceDelete$4 = (editor, caret, forward) => {
      if (editor.selection.isCollapsed() && isInlineBoundariesEnabled(editor)) {
        const from = CaretPosition.fromRangeStart(editor.selection.getRng());
        return backspaceDeleteCollapsed(editor, caret, forward, from);
      }
      return Optional.none();
    };

    const hasMultipleChildren = elm => childNodesCount(elm) > 1;
    const getParentsUntil = (editor, pred) => {
      const rootElm = SugarElement.fromDom(editor.getBody());
      const startElm = SugarElement.fromDom(editor.selection.getStart());
      const parents = parentsAndSelf(startElm, rootElm);
      return findIndex$2(parents, pred).fold(constant(parents), index => parents.slice(0, index));
    };
    const hasOnlyOneChild = elm => childNodesCount(elm) === 1;
    const getParentInlinesUntilMultichildInline = editor => getParentsUntil(editor, elm => editor.schema.isBlock(name(elm)) || hasMultipleChildren(elm));
    const getParentInlines = editor => getParentsUntil(editor, el => editor.schema.isBlock(name(el)));
    const getFormatNodes = (editor, parentInlines) => {
      const isFormatElement$1 = curry(isFormatElement, editor);
      return bind$3(parentInlines, elm => isFormatElement$1(elm) ? [elm.dom] : []);
    };
    const getFormatNodesAtStart = editor => {
      const parentInlines = getParentInlines(editor);
      return getFormatNodes(editor, parentInlines);
    };
    const deleteLastPosition = (forward, editor, target, parentInlines) => {
      const formatNodes = getFormatNodes(editor, parentInlines);
      if (formatNodes.length === 0) {
        deleteElement$2(editor, forward, target);
      } else {
        const pos = replaceWithCaretFormat(target.dom, formatNodes);
        editor.selection.setRng(pos.toRange());
      }
    };
    const deleteCaret$1 = (editor, forward) => {
      const parentInlines = filter$5(getParentInlinesUntilMultichildInline(editor), hasOnlyOneChild);
      return last$3(parentInlines).bind(target => {
        const fromPos = CaretPosition.fromRangeStart(editor.selection.getRng());
        if (willDeleteLastPositionInElement(forward, fromPos, target.dom) && !isEmptyCaretFormatElement(target)) {
          return Optional.some(() => deleteLastPosition(forward, editor, target, parentInlines));
        } else {
          return Optional.none();
        }
      });
    };
    const isBrInEmptyElement = (editor, elm) => {
      const parentElm = elm.parentElement;
      return isBr$6(elm) && !isNull(parentElm) && editor.dom.isEmpty(parentElm);
    };
    const isEmptyCaret = elm => isEmptyCaretFormatElement(SugarElement.fromDom(elm));
    const createCaretFormatAtStart = (editor, formatNodes) => {
      const startElm = editor.selection.getStart();
      const pos = isBrInEmptyElement(editor, startElm) || isEmptyCaret(startElm) ? replaceWithCaretFormat(startElm, formatNodes) : createCaretFormatAtStart$1(editor.selection.getRng(), formatNodes);
      editor.selection.setRng(pos.toRange());
    };
    const updateCaretFormat = (editor, updateFormats) => {
      const missingFormats = difference(updateFormats, getFormatNodesAtStart(editor));
      if (missingFormats.length > 0) {
        createCaretFormatAtStart(editor, missingFormats);
      }
    };
    const rangeStartsAtTextContainer = rng => isText$a(rng.startContainer);
    const rangeStartsAtStartOfTextContainer = rng => rng.startOffset === 0 && rangeStartsAtTextContainer(rng);
    const rangeStartParentIsFormatElement = (editor, rng) => {
      const startParent = rng.startContainer.parentElement;
      return !isNull(startParent) && isFormatElement(editor, SugarElement.fromDom(startParent));
    };
    const rangeStartAndEndHaveSameParent = rng => {
      const startParent = rng.startContainer.parentNode;
      const endParent = rng.endContainer.parentNode;
      return !isNull(startParent) && !isNull(endParent) && startParent.isEqualNode(endParent);
    };
    const rangeEndsAtEndOfEndContainer = rng => {
      const endContainer = rng.endContainer;
      return rng.endOffset === (isText$a(endContainer) ? endContainer.length : endContainer.childNodes.length);
    };
    const rangeEndsAtEndOfStartContainer = rng => rangeStartAndEndHaveSameParent(rng) && rangeEndsAtEndOfEndContainer(rng);
    const rangeEndsAfterEndOfStartContainer = rng => !rng.endContainer.isEqualNode(rng.commonAncestorContainer);
    const rangeEndsAtOrAfterEndOfStartContainer = rng => rangeEndsAtEndOfStartContainer(rng) || rangeEndsAfterEndOfStartContainer(rng);
    const requiresDeleteRangeOverride = editor => {
      const rng = editor.selection.getRng();
      return rangeStartsAtStartOfTextContainer(rng) && rangeStartParentIsFormatElement(editor, rng) && rangeEndsAtOrAfterEndOfStartContainer(rng);
    };
    const deleteRange$1 = editor => {
      if (requiresDeleteRangeOverride(editor)) {
        const formatNodes = getFormatNodesAtStart(editor);
        return Optional.some(() => {
          execNativeDeleteCommand(editor);
          updateCaretFormat(editor, formatNodes);
        });
      } else {
        return Optional.none();
      }
    };
    const backspaceDelete$3 = (editor, forward) => editor.selection.isCollapsed() ? deleteCaret$1(editor, forward) : deleteRange$1(editor);
    const hasAncestorInlineCaret = (elm, schema) => ancestor$1(elm, node => isCaretNode(node.dom), el => schema.isBlock(name(el)));
    const hasAncestorInlineCaretAtStart = editor => hasAncestorInlineCaret(SugarElement.fromDom(editor.selection.getStart()), editor.schema);
    const requiresRefreshCaretOverride = editor => {
      const rng = editor.selection.getRng();
      return rng.collapsed && (rangeStartsAtTextContainer(rng) || editor.dom.isEmpty(rng.startContainer)) && !hasAncestorInlineCaretAtStart(editor);
    };
    const refreshCaret = editor => {
      if (requiresRefreshCaretOverride(editor)) {
        createCaretFormatAtStart(editor, []);
      }
      return true;
    };

    const deleteElement = (editor, forward, element) => {
      if (isNonNullable(element)) {
        return Optional.some(() => {
          editor._selectionOverrides.hideFakeCaret();
          deleteElement$2(editor, forward, SugarElement.fromDom(element));
        });
      } else {
        return Optional.none();
      }
    };
    const deleteCaret = (editor, forward) => {
      const isNearMedia = forward ? isBeforeMedia : isAfterMedia;
      const direction = forward ? HDirection.Forwards : HDirection.Backwards;
      const fromPos = getNormalizedRangeEndPoint(direction, editor.getBody(), editor.selection.getRng());
      if (isNearMedia(fromPos)) {
        return deleteElement(editor, forward, fromPos.getNode(!forward));
      } else {
        return Optional.from(normalizePosition(forward, fromPos)).filter(pos => isNearMedia(pos) && isMoveInsideSameBlock(fromPos, pos)).bind(pos => deleteElement(editor, forward, pos.getNode(!forward)));
      }
    };
    const deleteRange = (editor, forward) => {
      const selectedNode = editor.selection.getNode();
      return isMedia$2(selectedNode) ? deleteElement(editor, forward, selectedNode) : Optional.none();
    };
    const backspaceDelete$2 = (editor, forward) => editor.selection.isCollapsed() ? deleteCaret(editor, forward) : deleteRange(editor, forward);

    const isEditable = target => closest$4(target, elm => isContentEditableTrue$3(elm.dom) || isContentEditableFalse$b(elm.dom)).exists(elm => isContentEditableTrue$3(elm.dom));
    const parseIndentValue = value => toInt(value !== null && value !== void 0 ? value : '').getOr(0);
    const getIndentStyleName = (useMargin, element) => {
      const indentStyleName = useMargin || isTable$1(element) ? 'margin' : 'padding';
      const suffix = get$7(element, 'direction') === 'rtl' ? '-right' : '-left';
      return indentStyleName + suffix;
    };
    const indentElement = (dom, command, useMargin, value, unit, element) => {
      const indentStyleName = getIndentStyleName(useMargin, SugarElement.fromDom(element));
      const parsedValue = parseIndentValue(dom.getStyle(element, indentStyleName));
      if (command === 'outdent') {
        const styleValue = Math.max(0, parsedValue - value);
        dom.setStyle(element, indentStyleName, styleValue ? styleValue + unit : '');
      } else {
        const styleValue = parsedValue + value + unit;
        dom.setStyle(element, indentStyleName, styleValue);
      }
    };
    const validateBlocks = (editor, blocks) => forall(blocks, block => {
      const indentStyleName = getIndentStyleName(shouldIndentUseMargin(editor), block);
      const intentValue = getRaw(block, indentStyleName).map(parseIndentValue).getOr(0);
      const contentEditable = editor.dom.getContentEditable(block.dom);
      return contentEditable !== 'false' && intentValue > 0;
    });
    const canOutdent = editor => {
      const blocks = getBlocksToIndent(editor);
      return !editor.mode.isReadOnly() && (blocks.length > 1 || validateBlocks(editor, blocks));
    };
    const isListComponent = el => isList(el) || isListItem$1(el);
    const parentIsListComponent = el => parent(el).exists(isListComponent);
    const getBlocksToIndent = editor => filter$5(fromDom$1(editor.selection.getSelectedBlocks()), el => !isListComponent(el) && !parentIsListComponent(el) && isEditable(el));
    const handle = (editor, command) => {
      var _a, _b;
      const {dom} = editor;
      const indentation = getIndentation(editor);
      const indentUnit = (_b = (_a = /[a-z%]+$/i.exec(indentation)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : 'px';
      const indentValue = parseIndentValue(indentation);
      const useMargin = shouldIndentUseMargin(editor);
      each$e(getBlocksToIndent(editor), block => {
        indentElement(dom, command, useMargin, indentValue, indentUnit, block.dom);
      });
    };
    const indent = editor => handle(editor, 'indent');
    const outdent = editor => handle(editor, 'outdent');

    const backspaceDelete$1 = editor => {
      if (editor.selection.isCollapsed() && canOutdent(editor)) {
        const dom = editor.dom;
        const rng = editor.selection.getRng();
        const pos = CaretPosition.fromRangeStart(rng);
        const block = dom.getParent(rng.startContainer, dom.isBlock);
        if (block !== null && isAtStartOfBlock(SugarElement.fromDom(block), pos, editor.schema)) {
          return Optional.some(() => outdent(editor));
        }
      }
      return Optional.none();
    };

    const findAction = (editor, caret, forward) => findMap([
      backspaceDelete$1,
      backspaceDelete$6,
      backspaceDelete$7,
      (editor, forward) => backspaceDelete$4(editor, caret, forward),
      backspaceDelete$9,
      backspaceDelete$a,
      backspaceDelete$5,
      backspaceDelete$2,
      backspaceDelete$8,
      backspaceDelete$3
    ], item => item(editor, forward)).filter(_ => editor.selection.isEditable());
    const deleteCommand = (editor, caret) => {
      const result = findAction(editor, caret, false);
      result.fold(() => {
        if (editor.selection.isEditable()) {
          execNativeDeleteCommand(editor);
          paddEmptyBody(editor);
        }
      }, call);
    };
    const forwardDeleteCommand = (editor, caret) => {
      const result = findAction(editor, caret, true);
      result.fold(() => {
        if (editor.selection.isEditable()) {
          execNativeForwardDeleteCommand(editor);
        }
      }, call);
    };
    const setup$q = (editor, caret) => {
      editor.addCommand('delete', () => {
        deleteCommand(editor, caret);
      });
      editor.addCommand('forwardDelete', () => {
        forwardDeleteCommand(editor, caret);
      });
    };

    const SIGNIFICANT_MOVE = 5;
    const LONGPRESS_DELAY = 400;
    const getTouch = event => {
      if (event.touches === undefined || event.touches.length !== 1) {
        return Optional.none();
      }
      return Optional.some(event.touches[0]);
    };
    const isFarEnough = (touch, data) => {
      const distX = Math.abs(touch.clientX - data.x);
      const distY = Math.abs(touch.clientY - data.y);
      return distX > SIGNIFICANT_MOVE || distY > SIGNIFICANT_MOVE;
    };
    const setup$p = editor => {
      const startData = value$2();
      const longpressFired = Cell(false);
      const debounceLongpress = last$1(e => {
        editor.dispatch('longpress', {
          ...e,
          type: 'longpress'
        });
        longpressFired.set(true);
      }, LONGPRESS_DELAY);
      editor.on('touchstart', e => {
        getTouch(e).each(touch => {
          debounceLongpress.cancel();
          const data = {
            x: touch.clientX,
            y: touch.clientY,
            target: e.target
          };
          debounceLongpress.throttle(e);
          longpressFired.set(false);
          startData.set(data);
        });
      }, true);
      editor.on('touchmove', e => {
        debounceLongpress.cancel();
        getTouch(e).each(touch => {
          startData.on(data => {
            if (isFarEnough(touch, data)) {
              startData.clear();
              longpressFired.set(false);
              editor.dispatch('longpresscancel');
            }
          });
        });
      }, true);
      editor.on('touchend touchcancel', e => {
        debounceLongpress.cancel();
        if (e.type === 'touchcancel') {
          return;
        }
        startData.get().filter(data => data.target.isEqualNode(e.target)).each(() => {
          if (longpressFired.get()) {
            e.preventDefault();
          } else {
            editor.dispatch('tap', {
              ...e,
              type: 'tap'
            });
          }
        });
      }, true);
    };

    const isBlockElement = (blockElements, node) => has$2(blockElements, node.nodeName);
    const isValidTarget = (schema, node) => {
      if (isText$a(node)) {
        return true;
      } else if (isElement$6(node)) {
        return !isBlockElement(schema.getBlockElements(), node) && !isBookmarkNode$1(node) && !isTransparentBlock(schema, node) && !isNonHtmlElementRoot(node);
      } else {
        return false;
      }
    };
    const hasBlockParent = (blockElements, root, node) => {
      return exists(parents(SugarElement.fromDom(node), SugarElement.fromDom(root)), elm => {
        return isBlockElement(blockElements, elm.dom);
      });
    };
    const shouldRemoveTextNode = (blockElements, node) => {
      if (isText$a(node)) {
        if (node.data.length === 0) {
          return true;
        } else if (/^\s+$/.test(node.data)) {
          return !node.nextSibling || isBlockElement(blockElements, node.nextSibling) || isNonHtmlElementRoot(node.nextSibling);
        }
      }
      return false;
    };
    const createRootBlock = editor => editor.dom.create(getForcedRootBlock(editor), getForcedRootBlockAttrs(editor));
    const addRootBlocks = editor => {
      const dom = editor.dom, selection = editor.selection;
      const schema = editor.schema;
      const blockElements = schema.getBlockElements();
      const startNode = selection.getStart();
      const rootNode = editor.getBody();
      let rootBlockNode;
      let tempNode;
      let wrapped = false;
      const forcedRootBlock = getForcedRootBlock(editor);
      if (!startNode || !isElement$6(startNode)) {
        return;
      }
      const rootNodeName = rootNode.nodeName.toLowerCase();
      if (!schema.isValidChild(rootNodeName, forcedRootBlock.toLowerCase()) || hasBlockParent(blockElements, rootNode, startNode)) {
        return;
      }
      const rng = selection.getRng();
      const {startContainer, startOffset, endContainer, endOffset} = rng;
      const restoreSelection = hasFocus(editor);
      let node = rootNode.firstChild;
      while (node) {
        if (isElement$6(node)) {
          updateElement(schema, node);
        }
        if (isValidTarget(schema, node)) {
          if (shouldRemoveTextNode(blockElements, node)) {
            tempNode = node;
            node = node.nextSibling;
            dom.remove(tempNode);
            continue;
          }
          if (!rootBlockNode) {
            rootBlockNode = createRootBlock(editor);
            rootNode.insertBefore(rootBlockNode, node);
            wrapped = true;
          }
          tempNode = node;
          node = node.nextSibling;
          rootBlockNode.appendChild(tempNode);
        } else {
          rootBlockNode = null;
          node = node.nextSibling;
        }
      }
      if (wrapped && restoreSelection) {
        rng.setStart(startContainer, startOffset);
        rng.setEnd(endContainer, endOffset);
        selection.setRng(rng);
        editor.nodeChanged();
      }
    };
    const insertEmptyLine = (editor, root, insertBlock) => {
      const block = SugarElement.fromDom(createRootBlock(editor));
      const br = createPaddingBr();
      append$1(block, br);
      insertBlock(root, block);
      const rng = document.createRange();
      rng.setStartBefore(br.dom);
      rng.setEndBefore(br.dom);
      return rng;
    };
    const setup$o = editor => {
      editor.on('NodeChange', curry(addRootBlocks, editor));
    };

    const hasClass = checkClassName => node => (' ' + node.attr('class') + ' ').indexOf(checkClassName) !== -1;
    const replaceMatchWithSpan = (editor, content, cls) => {
      return function (match) {
        const args = arguments, index = args[args.length - 2];
        const prevChar = index > 0 ? content.charAt(index - 1) : '';
        if (prevChar === '"') {
          return match;
        }
        if (prevChar === '>') {
          const findStartTagIndex = content.lastIndexOf('<', index);
          if (findStartTagIndex !== -1) {
            const tagHtml = content.substring(findStartTagIndex, index);
            if (tagHtml.indexOf('contenteditable="false"') !== -1) {
              return match;
            }
          }
        }
        return '<span class="' + cls + '" data-mce-content="' + editor.dom.encode(args[0]) + '">' + editor.dom.encode(typeof args[1] === 'string' ? args[1] : args[0]) + '</span>';
      };
    };
    const convertRegExpsToNonEditable = (editor, nonEditableRegExps, e) => {
      let i = nonEditableRegExps.length, content = e.content;
      if (e.format === 'raw') {
        return;
      }
      while (i--) {
        content = content.replace(nonEditableRegExps[i], replaceMatchWithSpan(editor, content, getNonEditableClass(editor)));
      }
      e.content = content;
    };
    const isValidContent = (nonEditableRegExps, content) => {
      return forall(nonEditableRegExps, re => {
        const matches = content.match(re);
        return matches !== null && matches[0].length === content.length;
      });
    };
    const setup$n = editor => {
      const contentEditableAttrName = 'contenteditable';
      const editClass = ' ' + Tools.trim(getEditableClass(editor)) + ' ';
      const nonEditClass = ' ' + Tools.trim(getNonEditableClass(editor)) + ' ';
      const hasEditClass = hasClass(editClass);
      const hasNonEditClass = hasClass(nonEditClass);
      const nonEditableRegExps = getNonEditableRegExps(editor);
      if (nonEditableRegExps.length > 0) {
        editor.on('BeforeSetContent', e => {
          convertRegExpsToNonEditable(editor, nonEditableRegExps, e);
        });
      }
      editor.parser.addAttributeFilter('class', nodes => {
        let i = nodes.length;
        while (i--) {
          const node = nodes[i];
          if (hasEditClass(node)) {
            node.attr(contentEditableAttrName, 'true');
          } else if (hasNonEditClass(node)) {
            node.attr(contentEditableAttrName, 'false');
          }
        }
      });
      editor.serializer.addAttributeFilter(contentEditableAttrName, nodes => {
        let i = nodes.length;
        while (i--) {
          const node = nodes[i];
          if (!hasEditClass(node) && !hasNonEditClass(node)) {
            continue;
          }
          const content = node.attr('data-mce-content');
          if (nonEditableRegExps.length > 0 && content) {
            if (isValidContent(nonEditableRegExps, content)) {
              node.name = '#text';
              node.type = 3;
              node.raw = true;
              node.value = content;
            } else {
              node.remove();
            }
          } else {
            node.attr(contentEditableAttrName, null);
          }
        }
      });
    };

    const findBlockCaretContainer = editor => descendant$1(SugarElement.fromDom(editor.getBody()), '*[data-mce-caret]').map(elm => elm.dom).getOrNull();
    const showBlockCaretContainer = (editor, blockCaretContainer) => {
      if (blockCaretContainer.hasAttribute('data-mce-caret')) {
        showCaretContainerBlock(blockCaretContainer);
        editor.selection.setRng(editor.selection.getRng());
        editor.selection.scrollIntoView(blockCaretContainer);
      }
    };
    const handleBlockContainer = (editor, e) => {
      const blockCaretContainer = findBlockCaretContainer(editor);
      if (!blockCaretContainer) {
        return;
      }
      if (e.type === 'compositionstart') {
        e.preventDefault();
        e.stopPropagation();
        showBlockCaretContainer(editor, blockCaretContainer);
        return;
      }
      if (hasContent(blockCaretContainer)) {
        showBlockCaretContainer(editor, blockCaretContainer);
        editor.undoManager.add();
      }
    };
    const setup$m = editor => {
      editor.on('keyup compositionstart', curry(handleBlockContainer, editor));
    };

    const isContentEditableFalse$3 = isContentEditableFalse$b;
    const moveToCeFalseHorizontally = (direction, editor, range) => moveHorizontally(editor, direction, range, isBeforeContentEditableFalse, isAfterContentEditableFalse, isContentEditableFalse$3);
    const moveToCeFalseVertically = (direction, editor, range) => {
      const isBefore = caretPosition => isBeforeContentEditableFalse(caretPosition) || isBeforeTable(caretPosition);
      const isAfter = caretPosition => isAfterContentEditableFalse(caretPosition) || isAfterTable(caretPosition);
      return moveVertically(editor, direction, range, isBefore, isAfter, isContentEditableFalse$3);
    };
    const createTextBlock = editor => {
      const textBlock = editor.dom.create(getForcedRootBlock(editor));
      textBlock.innerHTML = '<br data-mce-bogus="1">';
      return textBlock;
    };
    const exitPreBlock = (editor, direction, range) => {
      const caretWalker = CaretWalker(editor.getBody());
      const getVisualCaretPosition$1 = curry(getVisualCaretPosition, direction === 1 ? caretWalker.next : caretWalker.prev);
      if (range.collapsed) {
        const pre = editor.dom.getParent(range.startContainer, 'PRE');
        if (!pre) {
          return;
        }
        const caretPos = getVisualCaretPosition$1(CaretPosition.fromRangeStart(range));
        if (!caretPos) {
          const newBlock = SugarElement.fromDom(createTextBlock(editor));
          if (direction === 1) {
            after$4(SugarElement.fromDom(pre), newBlock);
          } else {
            before$3(SugarElement.fromDom(pre), newBlock);
          }
          editor.selection.select(newBlock.dom, true);
          editor.selection.collapse();
        }
      }
    };
    const getHorizontalRange = (editor, forward) => {
      const direction = forward ? HDirection.Forwards : HDirection.Backwards;
      const range = editor.selection.getRng();
      return moveToCeFalseHorizontally(direction, editor, range).orThunk(() => {
        exitPreBlock(editor, direction, range);
        return Optional.none();
      });
    };
    const getVerticalRange = (editor, down) => {
      const direction = down ? 1 : -1;
      const range = editor.selection.getRng();
      return moveToCeFalseVertically(direction, editor, range).orThunk(() => {
        exitPreBlock(editor, direction, range);
        return Optional.none();
      });
    };
    const flipDirection = (selection, forward) => {
      const elm = forward ? selection.getEnd(true) : selection.getStart(true);
      return isRtl(elm) ? !forward : forward;
    };
    const moveH$2 = (editor, forward) => getHorizontalRange(editor, flipDirection(editor.selection, forward)).exists(newRange => {
      moveToRange(editor, newRange);
      return true;
    });
    const moveV$4 = (editor, down) => getVerticalRange(editor, down).exists(newRange => {
      moveToRange(editor, newRange);
      return true;
    });
    const moveToLineEndPoint$1 = (editor, forward) => {
      const isCefPosition = forward ? isAfterContentEditableFalse : isBeforeContentEditableFalse;
      return moveToLineEndPoint$3(editor, forward, isCefPosition);
    };
    const selectToEndPoint = (editor, forward) => getEdgeCefPosition(editor, !forward).map(pos => {
      const rng = pos.toRange();
      const curRng = editor.selection.getRng();
      if (forward) {
        rng.setStart(curRng.startContainer, curRng.startOffset);
      } else {
        rng.setEnd(curRng.endContainer, curRng.endOffset);
      }
      return rng;
    }).exists(rng => {
      moveToRange(editor, rng);
      return true;
    });

    const isTarget = node => contains$2(['figcaption'], name(node));
    const getClosestTargetBlock = (pos, root, schema) => {
      const isRoot = curry(eq, root);
      return closest$4(SugarElement.fromDom(pos.container()), el => schema.isBlock(name(el)), isRoot).filter(isTarget);
    };
    const isAtFirstOrLastLine = (root, forward, pos) => forward ? isAtLastLine(root.dom, pos) : isAtFirstLine(root.dom, pos);
    const moveCaretToNewEmptyLine = (editor, forward) => {
      const root = SugarElement.fromDom(editor.getBody());
      const pos = CaretPosition.fromRangeStart(editor.selection.getRng());
      return getClosestTargetBlock(pos, root, editor.schema).exists(() => {
        if (isAtFirstOrLastLine(root, forward, pos)) {
          const insertFn = forward ? append$1 : prepend;
          const rng = insertEmptyLine(editor, root, insertFn);
          editor.selection.setRng(rng);
          return true;
        } else {
          return false;
        }
      });
    };
    const moveV$3 = (editor, forward) => {
      if (editor.selection.isCollapsed()) {
        return moveCaretToNewEmptyLine(editor, forward);
      } else {
        return false;
      }
    };

    const moveUp = (editor, details, summary) => {
      const rng = editor.selection.getRng();
      const pos = CaretPosition.fromRangeStart(rng);
      const root = editor.getBody();
      if (root.firstChild === details && isAtFirstLine(summary, pos)) {
        editor.execCommand('InsertNewBlockBefore');
        return true;
      } else {
        return false;
      }
    };
    const moveDown = (editor, details) => {
      const rng = editor.selection.getRng();
      const pos = CaretPosition.fromRangeStart(rng);
      const root = editor.getBody();
      if (root.lastChild === details && isAtLastLine(details, pos)) {
        editor.execCommand('InsertNewBlockAfter');
        return true;
      } else {
        return false;
      }
    };
    const move$2 = (editor, forward) => {
      if (forward) {
        return Optional.from(editor.dom.getParent(editor.selection.getNode(), 'details')).map(details => moveDown(editor, details)).getOr(false);
      } else {
        return Optional.from(editor.dom.getParent(editor.selection.getNode(), 'summary')).bind(summary => Optional.from(editor.dom.getParent(summary, 'details')).map(details => moveUp(editor, details, summary))).getOr(false);
      }
    };
    const moveV$2 = (editor, forward) => move$2(editor, forward);

    const baseKeyPattern = {
      shiftKey: false,
      altKey: false,
      ctrlKey: false,
      metaKey: false,
      keyCode: 0
    };
    const defaultPatterns = patterns => map$3(patterns, pattern => ({
      ...baseKeyPattern,
      ...pattern
    }));
    const defaultDelayedPatterns = patterns => map$3(patterns, pattern => ({
      ...baseKeyPattern,
      ...pattern
    }));
    const matchesEvent = (pattern, evt) => evt.keyCode === pattern.keyCode && evt.shiftKey === pattern.shiftKey && evt.altKey === pattern.altKey && evt.ctrlKey === pattern.ctrlKey && evt.metaKey === pattern.metaKey;
    const match$1 = (patterns, evt) => bind$3(defaultPatterns(patterns), pattern => matchesEvent(pattern, evt) ? [pattern] : []);
    const matchDelayed = (patterns, evt) => bind$3(defaultDelayedPatterns(patterns), pattern => matchesEvent(pattern, evt) ? [pattern] : []);
    const action = (f, ...x) => () => f.apply(null, x);
    const execute = (patterns, evt) => find$2(match$1(patterns, evt), pattern => pattern.action());
    const executeWithDelayedAction = (patterns, evt) => findMap(matchDelayed(patterns, evt), pattern => pattern.action());

    const moveH$1 = (editor, forward) => {
      const direction = forward ? HDirection.Forwards : HDirection.Backwards;
      const range = editor.selection.getRng();
      return moveHorizontally(editor, direction, range, isBeforeMedia, isAfterMedia, isMedia$2).exists(newRange => {
        moveToRange(editor, newRange);
        return true;
      });
    };
    const moveV$1 = (editor, down) => {
      const direction = down ? 1 : -1;
      const range = editor.selection.getRng();
      return moveVertically(editor, direction, range, isBeforeMedia, isAfterMedia, isMedia$2).exists(newRange => {
        moveToRange(editor, newRange);
        return true;
      });
    };
    const moveToLineEndPoint = (editor, forward) => {
      const isNearMedia = forward ? isAfterMedia : isBeforeMedia;
      return moveToLineEndPoint$3(editor, forward, isNearMedia);
    };

    const adt = Adt.generate([
      { none: ['current'] },
      { first: ['current'] },
      {
        middle: [
          'current',
          'target'
        ]
      },
      { last: ['current'] }
    ]);
    const none = current => adt.none(current);
    const CellLocation = {
      ...adt,
      none
    };

    const firstLayer = (scope, selector) => {
      return filterFirstLayer(scope, selector, always);
    };
    const filterFirstLayer = (scope, selector, predicate) => {
      return bind$3(children$1(scope), x => {
        if (is$1(x, selector)) {
          return predicate(x) ? [x] : [];
        } else {
          return filterFirstLayer(x, selector, predicate);
        }
      });
    };

    const lookup$1 = (tags, element, isRoot = never) => {
      if (isRoot(element)) {
        return Optional.none();
      }
      if (contains$2(tags, name(element))) {
        return Optional.some(element);
      }
      const isRootOrUpperTable = elm => is$1(elm, 'table') || isRoot(elm);
      return ancestor$3(element, tags.join(','), isRootOrUpperTable);
    };
    const cell = (element, isRoot) => lookup$1([
      'td',
      'th'
    ], element, isRoot);
    const cells = ancestor => firstLayer(ancestor, 'th,td');
    const table = (element, isRoot) => closest$3(element, 'table', isRoot);

    const walk = (all, current, index, direction, isEligible = always) => {
      const forwards = direction === 1;
      if (!forwards && index <= 0) {
        return CellLocation.first(all[0]);
      } else if (forwards && index >= all.length - 1) {
        return CellLocation.last(all[all.length - 1]);
      } else {
        const newIndex = index + direction;
        const elem = all[newIndex];
        return isEligible(elem) ? CellLocation.middle(current, elem) : walk(all, current, newIndex, direction, isEligible);
      }
    };
    const detect = (current, isRoot) => {
      return table(current, isRoot).bind(table => {
        const all = cells(table);
        const index = findIndex$2(all, x => eq(current, x));
        return index.map(index => ({
          index,
          all
        }));
      });
    };
    const next = (current, isEligible, isRoot) => {
      const detection = detect(current, isRoot);
      return detection.fold(() => {
        return CellLocation.none(current);
      }, info => {
        return walk(info.all, current, info.index, 1, isEligible);
      });
    };
    const prev = (current, isEligible, isRoot) => {
      const detection = detect(current, isRoot);
      return detection.fold(() => {
        return CellLocation.none();
      }, info => {
        return walk(info.all, current, info.index, -1, isEligible);
      });
    };

    const deflate = (rect, delta) => ({
      left: rect.left - delta,
      top: rect.top - delta,
      right: rect.right + delta * 2,
      bottom: rect.bottom + delta * 2,
      width: rect.width + delta,
      height: rect.height + delta
    });
    const getCorners = (getYAxisValue, tds) => bind$3(tds, td => {
      const rect = deflate(clone$1(td.getBoundingClientRect()), -1);
      return [
        {
          x: rect.left,
          y: getYAxisValue(rect),
          cell: td
        },
        {
          x: rect.right,
          y: getYAxisValue(rect),
          cell: td
        }
      ];
    });
    const findClosestCorner = (corners, x, y) => foldl(corners, (acc, newCorner) => acc.fold(() => Optional.some(newCorner), oldCorner => {
      const oldDist = Math.sqrt(Math.abs(oldCorner.x - x) + Math.abs(oldCorner.y - y));
      const newDist = Math.sqrt(Math.abs(newCorner.x - x) + Math.abs(newCorner.y - y));
      return Optional.some(newDist < oldDist ? newCorner : oldCorner);
    }), Optional.none());
    const getClosestCell = (getYAxisValue, isTargetCorner, table, x, y) => {
      const cells = descendants(SugarElement.fromDom(table), 'td,th,caption').map(e => e.dom);
      const corners = filter$5(getCorners(getYAxisValue, cells), corner => isTargetCorner(corner, y));
      return findClosestCorner(corners, x, y).map(corner => corner.cell);
    };
    const getBottomValue = rect => rect.bottom;
    const getTopValue = rect => rect.top;
    const isAbove = (corner, y) => corner.y < y;
    const isBelow = (corner, y) => corner.y > y;
    const getClosestCellAbove = curry(getClosestCell, getBottomValue, isAbove);
    const getClosestCellBelow = curry(getClosestCell, getTopValue, isBelow);
    const findClosestPositionInAboveCell = (table, pos) => head(pos.getClientRects()).bind(rect => getClosestCellAbove(table, rect.left, rect.top)).bind(cell => findClosestHorizontalPosition(getLastLinePositions(cell), pos));
    const findClosestPositionInBelowCell = (table, pos) => last$3(pos.getClientRects()).bind(rect => getClosestCellBelow(table, rect.left, rect.top)).bind(cell => findClosestHorizontalPosition(getFirstLinePositions(cell), pos));

    const hasNextBreak = (getPositionsUntil, scope, lineInfo) => lineInfo.breakAt.exists(breakPos => getPositionsUntil(scope, breakPos).breakAt.isSome());
    const startsWithWrapBreak = lineInfo => lineInfo.breakType === BreakType.Wrap && lineInfo.positions.length === 0;
    const startsWithBrBreak = lineInfo => lineInfo.breakType === BreakType.Br && lineInfo.positions.length === 1;
    const isAtTableCellLine = (getPositionsUntil, scope, pos) => {
      const lineInfo = getPositionsUntil(scope, pos);
      if (startsWithWrapBreak(lineInfo) || !isBr$6(pos.getNode()) && startsWithBrBreak(lineInfo)) {
        return !hasNextBreak(getPositionsUntil, scope, lineInfo);
      } else {
        return lineInfo.breakAt.isNone();
      }
    };
    const isAtFirstTableCellLine = curry(isAtTableCellLine, getPositionsUntilPreviousLine);
    const isAtLastTableCellLine = curry(isAtTableCellLine, getPositionsUntilNextLine);
    const isCaretAtStartOrEndOfTable = (forward, rng, table) => {
      const caretPos = CaretPosition.fromRangeStart(rng);
      return positionIn(!forward, table).exists(pos => pos.isEqual(caretPos));
    };
    const navigateHorizontally = (editor, forward, table, _td) => {
      const rng = editor.selection.getRng();
      const direction = forward ? 1 : -1;
      if (isFakeCaretTableBrowser() && isCaretAtStartOrEndOfTable(forward, rng, table)) {
        showCaret(direction, editor, table, !forward, false).each(newRng => {
          moveToRange(editor, newRng);
        });
        return true;
      }
      return false;
    };
    const getClosestAbovePosition = (root, table, start) => findClosestPositionInAboveCell(table, start).orThunk(() => head(start.getClientRects()).bind(rect => findClosestHorizontalPositionFromPoint(getPositionsAbove(root, CaretPosition.before(table)), rect.left))).getOr(CaretPosition.before(table));
    const getClosestBelowPosition = (root, table, start) => findClosestPositionInBelowCell(table, start).orThunk(() => head(start.getClientRects()).bind(rect => findClosestHorizontalPositionFromPoint(getPositionsBelow(root, CaretPosition.after(table)), rect.left))).getOr(CaretPosition.after(table));
    const getTable = (previous, pos) => {
      const node = pos.getNode(previous);
      return isTable$2(node) ? Optional.some(node) : Optional.none();
    };
    const renderBlock = (down, editor, table) => {
      editor.undoManager.transact(() => {
        const insertFn = down ? after$4 : before$3;
        const rng = insertEmptyLine(editor, SugarElement.fromDom(table), insertFn);
        moveToRange(editor, rng);
      });
    };
    const moveCaret = (editor, down, pos) => {
      const table = down ? getTable(true, pos) : getTable(false, pos);
      const last = down === false;
      table.fold(() => moveToRange(editor, pos.toRange()), table => positionIn(last, editor.getBody()).filter(lastPos => lastPos.isEqual(pos)).fold(() => moveToRange(editor, pos.toRange()), _ => renderBlock(down, editor, table)));
    };
    const navigateVertically = (editor, down, table, td) => {
      const rng = editor.selection.getRng();
      const pos = CaretPosition.fromRangeStart(rng);
      const root = editor.getBody();
      if (!down && isAtFirstTableCellLine(td, pos)) {
        const newPos = getClosestAbovePosition(root, table, pos);
        moveCaret(editor, down, newPos);
        return true;
      } else if (down && isAtLastTableCellLine(td, pos)) {
        const newPos = getClosestBelowPosition(root, table, pos);
        moveCaret(editor, down, newPos);
        return true;
      } else {
        return false;
      }
    };
    const move$1 = (editor, forward, mover) => Optional.from(editor.dom.getParent(editor.selection.getNode(), 'td,th')).bind(td => Optional.from(editor.dom.getParent(td, 'table')).map(table => mover(editor, forward, table, td))).getOr(false);
    const moveH = (editor, forward) => move$1(editor, forward, navigateHorizontally);
    const moveV = (editor, forward) => move$1(editor, forward, navigateVertically);
    const getCellFirstCursorPosition = cell => {
      const selection = SimSelection.exact(cell, 0, cell, 0);
      return toNative(selection);
    };
    const tabGo = (editor, isRoot, cell) => {
      return cell.fold(Optional.none, Optional.none, (_current, next) => {
        return first(next).map(cell => {
          return getCellFirstCursorPosition(cell);
        });
      }, current => {
        editor.execCommand('mceTableInsertRowAfter');
        return tabForward(editor, isRoot, current);
      });
    };
    const tabForward = (editor, isRoot, cell) => tabGo(editor, isRoot, next(cell, isEditable$2));
    const tabBackward = (editor, isRoot, cell) => tabGo(editor, isRoot, prev(cell, isEditable$2));
    const handleTab = (editor, forward) => {
      const rootElements = [
        'table',
        'li',
        'dl'
      ];
      const body = SugarElement.fromDom(editor.getBody());
      const isRoot = element => {
        const name$1 = name(element);
        return eq(element, body) || contains$2(rootElements, name$1);
      };
      const rng = editor.selection.getRng();
      const container = SugarElement.fromDom(!forward ? rng.startContainer : rng.endContainer);
      return cell(container, isRoot).map(cell => {
        table(cell, isRoot).each(table => {
          editor.model.table.clearSelectedCells(table.dom);
        });
        editor.selection.collapse(!forward);
        const navigation = !forward ? tabBackward : tabForward;
        const rng = navigation(editor, isRoot, cell);
        rng.each(range => {
          editor.selection.setRng(range);
        });
        return true;
      }).getOr(false);
    };

    const executeKeydownOverride$4 = (editor, caret, evt) => {
      const isMac = Env.os.isMacOS() || Env.os.isiOS();
      execute([
        {
          keyCode: VK.RIGHT,
          action: action(moveH$2, editor, true)
        },
        {
          keyCode: VK.LEFT,
          action: action(moveH$2, editor, false)
        },
        {
          keyCode: VK.UP,
          action: action(moveV$4, editor, false)
        },
        {
          keyCode: VK.DOWN,
          action: action(moveV$4, editor, true)
        },
        ...isMac ? [
          {
            keyCode: VK.UP,
            action: action(selectToEndPoint, editor, false),
            metaKey: true,
            shiftKey: true
          },
          {
            keyCode: VK.DOWN,
            action: action(selectToEndPoint, editor, true),
            metaKey: true,
            shiftKey: true
          }
        ] : [],
        {
          keyCode: VK.RIGHT,
          action: action(moveH, editor, true)
        },
        {
          keyCode: VK.LEFT,
          action: action(moveH, editor, false)
        },
        {
          keyCode: VK.UP,
          action: action(moveV, editor, false)
        },
        {
          keyCode: VK.DOWN,
          action: action(moveV, editor, true)
        },
        {
          keyCode: VK.UP,
          action: action(moveV, editor, false)
        },
        {
          keyCode: VK.UP,
          action: action(moveV$2, editor, false)
        },
        {
          keyCode: VK.DOWN,
          action: action(moveV$2, editor, true)
        },
        {
          keyCode: VK.RIGHT,
          action: action(moveH$1, editor, true)
        },
        {
          keyCode: VK.LEFT,
          action: action(moveH$1, editor, false)
        },
        {
          keyCode: VK.UP,
          action: action(moveV$1, editor, false)
        },
        {
          keyCode: VK.DOWN,
          action: action(moveV$1, editor, true)
        },
        {
          keyCode: VK.RIGHT,
          action: action(move$3, editor, caret, true)
        },
        {
          keyCode: VK.LEFT,
          action: action(move$3, editor, caret, false)
        },
        {
          keyCode: VK.RIGHT,
          ctrlKey: !isMac,
          altKey: isMac,
          action: action(moveNextWord, editor, caret)
        },
        {
          keyCode: VK.LEFT,
          ctrlKey: !isMac,
          altKey: isMac,
          action: action(movePrevWord, editor, caret)
        },
        {
          keyCode: VK.UP,
          action: action(moveV$3, editor, false)
        },
        {
          keyCode: VK.DOWN,
          action: action(moveV$3, editor, true)
        }
      ], evt).each(_ => {
        evt.preventDefault();
      });
    };
    const setup$l = (editor, caret) => {
      editor.on('keydown', evt => {
        if (!evt.isDefaultPrevented()) {
          executeKeydownOverride$4(editor, caret, evt);
        }
      });
    };

    const point = (container, offset) => ({
      container,
      offset
    });

    const DOM$7 = DOMUtils.DOM;
    const alwaysNext = startNode => node => startNode === node ? -1 : 0;
    const isBoundary = dom => node => dom.isBlock(node) || contains$2([
      'BR',
      'IMG',
      'HR',
      'INPUT'
    ], node.nodeName) || dom.getContentEditable(node) === 'false';
    const textBefore = (node, offset, rootNode) => {
      if (isText$a(node) && offset >= 0) {
        return Optional.some(point(node, offset));
      } else {
        const textSeeker = TextSeeker(DOM$7);
        return Optional.from(textSeeker.backwards(node, offset, alwaysNext(node), rootNode)).map(prev => point(prev.container, prev.container.data.length));
      }
    };
    const textAfter = (node, offset, rootNode) => {
      if (isText$a(node) && offset >= node.length) {
        return Optional.some(point(node, offset));
      } else {
        const textSeeker = TextSeeker(DOM$7);
        return Optional.from(textSeeker.forwards(node, offset, alwaysNext(node), rootNode)).map(prev => point(prev.container, 0));
      }
    };
    const scanLeft = (node, offset, rootNode) => {
      if (!isText$a(node)) {
        return Optional.none();
      }
      const text = node.data;
      if (offset >= 0 && offset <= text.length) {
        return Optional.some(point(node, offset));
      } else {
        const textSeeker = TextSeeker(DOM$7);
        return Optional.from(textSeeker.backwards(node, offset, alwaysNext(node), rootNode)).bind(prev => {
          const prevText = prev.container.data;
          return scanLeft(prev.container, offset + prevText.length, rootNode);
        });
      }
    };
    const scanRight = (node, offset, rootNode) => {
      if (!isText$a(node)) {
        return Optional.none();
      }
      const text = node.data;
      if (offset <= text.length) {
        return Optional.some(point(node, offset));
      } else {
        const textSeeker = TextSeeker(DOM$7);
        return Optional.from(textSeeker.forwards(node, offset, alwaysNext(node), rootNode)).bind(next => scanRight(next.container, offset - text.length, rootNode));
      }
    };
    const repeatLeft = (dom, node, offset, process, rootNode) => {
      const search = TextSeeker(dom, isBoundary(dom));
      return Optional.from(search.backwards(node, offset, process, rootNode));
    };

    const isValidTextRange = rng => rng.collapsed && isText$a(rng.startContainer);
    const getText = rng => trim$2(rng.toString().replace(/\u00A0/g, ' '));
    const isWhitespace = chr => chr !== '' && ' \xA0\f\n\r\t\x0B'.indexOf(chr) !== -1;

    const stripTrigger = (text, trigger) => text.substring(trigger.length);
    const findTrigger = (text, index, trigger) => {
      let i;
      const firstChar = trigger.charAt(0);
      for (i = index - 1; i >= 0; i--) {
        const char = text.charAt(i);
        if (isWhitespace(char)) {
          return Optional.none();
        }
        if (firstChar === char && contains$1(text, trigger, i, index)) {
          break;
        }
      }
      return Optional.some(i);
    };
    const findStart = (dom, initRange, trigger, minChars = 0) => {
      if (!isValidTextRange(initRange)) {
        return Optional.none();
      }
      const buffer = {
        text: '',
        offset: 0
      };
      const findTriggerIndex = (element, offset, text) => {
        buffer.text = text + buffer.text;
        buffer.offset += offset;
        return findTrigger(buffer.text, buffer.offset, trigger).getOr(offset);
      };
      const root = dom.getParent(initRange.startContainer, dom.isBlock) || dom.getRoot();
      return repeatLeft(dom, initRange.startContainer, initRange.startOffset, findTriggerIndex, root).bind(spot => {
        const range = initRange.cloneRange();
        range.setStart(spot.container, spot.offset);
        range.setEnd(initRange.endContainer, initRange.endOffset);
        if (range.collapsed) {
          return Optional.none();
        }
        const text = getText(range);
        const triggerIndex = text.lastIndexOf(trigger);
        if (triggerIndex !== 0 || stripTrigger(text, trigger).length < minChars) {
          return Optional.none();
        } else {
          return Optional.some({
            text: stripTrigger(text, trigger),
            range,
            trigger
          });
        }
      });
    };
    const getContext = (dom, initRange, trigger, minChars = 0) => detect$1(SugarElement.fromDom(initRange.startContainer)).fold(() => findStart(dom, initRange, trigger, minChars), elm => {
      const range = dom.createRng();
      range.selectNode(elm.dom);
      const text = getText(range);
      return Optional.some({
        range,
        text: stripTrigger(text, trigger),
        trigger
      });
    });

    const isText$1 = node => node.nodeType === TEXT;
    const isElement = node => node.nodeType === ELEMENT;
    const toLast = node => {
      if (isText$1(node)) {
        return point(node, node.data.length);
      } else {
        const children = node.childNodes;
        return children.length > 0 ? toLast(children[children.length - 1]) : point(node, children.length);
      }
    };
    const toLeaf = (node, offset) => {
      const children = node.childNodes;
      if (children.length > 0 && offset < children.length) {
        return toLeaf(children[offset], 0);
      } else if (children.length > 0 && isElement(node) && children.length === offset) {
        return toLast(children[children.length - 1]);
      } else {
        return point(node, offset);
      }
    };

    const isPreviousCharContent = (dom, leaf) => {
      var _a;
      const root = (_a = dom.getParent(leaf.container, dom.isBlock)) !== null && _a !== void 0 ? _a : dom.getRoot();
      return repeatLeft(dom, leaf.container, leaf.offset, (_element, offset) => offset === 0 ? -1 : offset, root).filter(spot => {
        const char = spot.container.data.charAt(spot.offset - 1);
        return !isWhitespace(char);
      }).isSome();
    };
    const isStartOfWord = dom => rng => {
      const leaf = toLeaf(rng.startContainer, rng.startOffset);
      return !isPreviousCharContent(dom, leaf);
    };
    const getTriggerContext = (dom, initRange, database) => findMap(database.triggers, trigger => getContext(dom, initRange, trigger));
    const lookup = (editor, getDatabase) => {
      const database = getDatabase();
      const rng = editor.selection.getRng();
      return getTriggerContext(editor.dom, rng, database).bind(context => lookupWithContext(editor, getDatabase, context));
    };
    const lookupWithContext = (editor, getDatabase, context, fetchOptions = {}) => {
      var _a;
      const database = getDatabase();
      const rng = editor.selection.getRng();
      const startText = (_a = rng.startContainer.nodeValue) !== null && _a !== void 0 ? _a : '';
      const autocompleters = filter$5(database.lookupByTrigger(context.trigger), autocompleter => context.text.length >= autocompleter.minChars && autocompleter.matches.getOrThunk(() => isStartOfWord(editor.dom))(context.range, startText, context.text));
      if (autocompleters.length === 0) {
        return Optional.none();
      }
      const lookupData = Promise.all(map$3(autocompleters, ac => {
        const fetchResult = ac.fetch(context.text, ac.maxResults, fetchOptions);
        return fetchResult.then(results => ({
          matchText: context.text,
          items: results,
          columns: ac.columns,
          onAction: ac.onAction,
          highlightOn: ac.highlightOn
        }));
      }));
      return Optional.some({
        lookupData,
        context
      });
    };

    var SimpleResultType;
    (function (SimpleResultType) {
      SimpleResultType[SimpleResultType['Error'] = 0] = 'Error';
      SimpleResultType[SimpleResultType['Value'] = 1] = 'Value';
    }(SimpleResultType || (SimpleResultType = {})));
    const fold$1 = (res, onError, onValue) => res.stype === SimpleResultType.Error ? onError(res.serror) : onValue(res.svalue);
    const partition = results => {
      const values = [];
      const errors = [];
      each$e(results, obj => {
        fold$1(obj, err => errors.push(err), val => values.push(val));
      });
      return {
        values,
        errors
      };
    };
    const mapError = (res, f) => {
      if (res.stype === SimpleResultType.Error) {
        return {
          stype: SimpleResultType.Error,
          serror: f(res.serror)
        };
      } else {
        return res;
      }
    };
    const map = (res, f) => {
      if (res.stype === SimpleResultType.Value) {
        return {
          stype: SimpleResultType.Value,
          svalue: f(res.svalue)
        };
      } else {
        return res;
      }
    };
    const bind$1 = (res, f) => {
      if (res.stype === SimpleResultType.Value) {
        return f(res.svalue);
      } else {
        return res;
      }
    };
    const bindError = (res, f) => {
      if (res.stype === SimpleResultType.Error) {
        return f(res.serror);
      } else {
        return res;
      }
    };
    const svalue = v => ({
      stype: SimpleResultType.Value,
      svalue: v
    });
    const serror = e => ({
      stype: SimpleResultType.Error,
      serror: e
    });
    const toResult = res => fold$1(res, Result.error, Result.value);
    const fromResult = res => res.fold(serror, svalue);
    const SimpleResult = {
      fromResult,
      toResult,
      svalue,
      partition,
      serror,
      bind: bind$1,
      bindError,
      map,
      mapError,
      fold: fold$1
    };

    const formatObj = input => {
      return isObject(input) && keys(input).length > 100 ? ' removed due to size' : JSON.stringify(input, null, 2);
    };
    const formatErrors = errors => {
      const es = errors.length > 10 ? errors.slice(0, 10).concat([{
          path: [],
          getErrorInfo: constant('... (only showing first ten failures)')
        }]) : errors;
      return map$3(es, e => {
        return 'Failed path: (' + e.path.join(' > ') + ')\n' + e.getErrorInfo();
      });
    };

    const nu = (path, getErrorInfo) => {
      return SimpleResult.serror([{
          path,
          getErrorInfo
        }]);
    };
    const missingRequired = (path, key, obj) => nu(path, () => 'Could not find valid *required* value for "' + key + '" in ' + formatObj(obj));
    const missingKey = (path, key) => nu(path, () => 'Choice schema did not contain choice key: "' + key + '"');
    const missingBranch = (path, branches, branch) => nu(path, () => 'The chosen schema: "' + branch + '" did not exist in branches: ' + formatObj(branches));
    const custom = (path, err) => nu(path, constant(err));

    const chooseFrom = (path, input, branches, ch) => {
      const fields = get$a(branches, ch);
      return fields.fold(() => missingBranch(path, branches, ch), vp => vp.extract(path.concat(['branch: ' + ch]), input));
    };
    const choose$1 = (key, branches) => {
      const extract = (path, input) => {
        const choice = get$a(input, key);
        return choice.fold(() => missingKey(path, key), chosen => chooseFrom(path, input, branches, chosen));
      };
      const toString = () => 'chooseOn(' + key + '). Possible values: ' + keys(branches);
      return {
        extract,
        toString
      };
    };

    const shallow = (old, nu) => {
      return nu;
    };
    const deep = (old, nu) => {
      const bothObjects = isPlainObject(old) && isPlainObject(nu);
      return bothObjects ? deepMerge(old, nu) : nu;
    };
    const baseMerge = merger => {
      return (...objects) => {
        if (objects.length === 0) {
          throw new Error(`Can't merge zero objects`);
        }
        const ret = {};
        for (let j = 0; j < objects.length; j++) {
          const curObject = objects[j];
          for (const key in curObject) {
            if (has$2(curObject, key)) {
              ret[key] = merger(ret[key], curObject[key]);
            }
          }
        }
        return ret;
      };
    };
    const deepMerge = baseMerge(deep);
    const merge = baseMerge(shallow);

    const required = () => ({
      tag: 'required',
      process: {}
    });
    const defaultedThunk = fallbackThunk => ({
      tag: 'defaultedThunk',
      process: fallbackThunk
    });
    const defaulted$1 = fallback => defaultedThunk(constant(fallback));
    const asOption = () => ({
      tag: 'option',
      process: {}
    });

    const mergeValues = (values, base) => values.length > 0 ? SimpleResult.svalue(deepMerge(base, merge.apply(undefined, values))) : SimpleResult.svalue(base);
    const mergeErrors = errors => compose(SimpleResult.serror, flatten)(errors);
    const consolidateObj = (objects, base) => {
      const partition = SimpleResult.partition(objects);
      return partition.errors.length > 0 ? mergeErrors(partition.errors) : mergeValues(partition.values, base);
    };
    const consolidateArr = objects => {
      const partitions = SimpleResult.partition(objects);
      return partitions.errors.length > 0 ? mergeErrors(partitions.errors) : SimpleResult.svalue(partitions.values);
    };
    const ResultCombine = {
      consolidateObj,
      consolidateArr
    };

    const field$1 = (key, newKey, presence, prop) => ({
      tag: 'field',
      key,
      newKey,
      presence,
      prop
    });
    const customField$1 = (newKey, instantiator) => ({
      tag: 'custom',
      newKey,
      instantiator
    });
    const fold = (value, ifField, ifCustom) => {
      switch (value.tag) {
      case 'field':
        return ifField(value.key, value.newKey, value.presence, value.prop);
      case 'custom':
        return ifCustom(value.newKey, value.instantiator);
      }
    };

    const value = validator => {
      const extract = (path, val) => {
        return SimpleResult.bindError(validator(val), err => custom(path, err));
      };
      const toString = constant('val');
      return {
        extract,
        toString
      };
    };
    const anyValue$1 = value(SimpleResult.svalue);

    const requiredAccess = (path, obj, key, bundle) => get$a(obj, key).fold(() => missingRequired(path, key, obj), bundle);
    const fallbackAccess = (obj, key, fallback, bundle) => {
      const v = get$a(obj, key).getOrThunk(() => fallback(obj));
      return bundle(v);
    };
    const optionAccess = (obj, key, bundle) => bundle(get$a(obj, key));
    const optionDefaultedAccess = (obj, key, fallback, bundle) => {
      const opt = get$a(obj, key).map(val => val === true ? fallback(obj) : val);
      return bundle(opt);
    };
    const extractField = (field, path, obj, key, prop) => {
      const bundle = av => prop.extract(path.concat([key]), av);
      const bundleAsOption = optValue => optValue.fold(() => SimpleResult.svalue(Optional.none()), ov => {
        const result = prop.extract(path.concat([key]), ov);
        return SimpleResult.map(result, Optional.some);
      });
      switch (field.tag) {
      case 'required':
        return requiredAccess(path, obj, key, bundle);
      case 'defaultedThunk':
        return fallbackAccess(obj, key, field.process, bundle);
      case 'option':
        return optionAccess(obj, key, bundleAsOption);
      case 'defaultedOptionThunk':
        return optionDefaultedAccess(obj, key, field.process, bundleAsOption);
      case 'mergeWithThunk': {
          return fallbackAccess(obj, key, constant({}), v => {
            const result = deepMerge(field.process(obj), v);
            return bundle(result);
          });
        }
      }
    };
    const extractFields = (path, obj, fields) => {
      const success = {};
      const errors = [];
      for (const field of fields) {
        fold(field, (key, newKey, presence, prop) => {
          const result = extractField(presence, path, obj, key, prop);
          SimpleResult.fold(result, err => {
            errors.push(...err);
          }, res => {
            success[newKey] = res;
          });
        }, (newKey, instantiator) => {
          success[newKey] = instantiator(obj);
        });
      }
      return errors.length > 0 ? SimpleResult.serror(errors) : SimpleResult.svalue(success);
    };
    const objOf = values => {
      const extract = (path, o) => extractFields(path, o, values);
      const toString = () => {
        const fieldStrings = map$3(values, value => fold(value, (key, _okey, _presence, prop) => key + ' -> ' + prop.toString(), (newKey, _instantiator) => 'state(' + newKey + ')'));
        return 'obj{\n' + fieldStrings.join('\n') + '}';
      };
      return {
        extract,
        toString
      };
    };
    const arrOf = prop => {
      const extract = (path, array) => {
        const results = map$3(array, (a, i) => prop.extract(path.concat(['[' + i + ']']), a));
        return ResultCombine.consolidateArr(results);
      };
      const toString = () => 'array(' + prop.toString() + ')';
      return {
        extract,
        toString
      };
    };

    const valueOf = validator => value(v => validator(v).fold(SimpleResult.serror, SimpleResult.svalue));
    const extractValue = (label, prop, obj) => {
      const res = prop.extract([label], obj);
      return SimpleResult.mapError(res, errs => ({
        input: obj,
        errors: errs
      }));
    };
    const asRaw = (label, prop, obj) => SimpleResult.toResult(extractValue(label, prop, obj));
    const formatError = errInfo => {
      return 'Errors: \n' + formatErrors(errInfo.errors).join('\n') + '\n\nInput object: ' + formatObj(errInfo.input);
    };
    const choose = (key, branches) => choose$1(key, map$2(branches, objOf));

    const anyValue = constant(anyValue$1);
    const typedValue = (validator, expectedType) => value(a => {
      const actualType = typeof a;
      return validator(a) ? SimpleResult.svalue(a) : SimpleResult.serror(`Expected type: ${ expectedType } but got: ${ actualType }`);
    });
    const number = typedValue(isNumber, 'number');
    const string = typedValue(isString, 'string');
    const boolean = typedValue(isBoolean, 'boolean');
    const functionProcessor = typedValue(isFunction, 'function');

    const field = field$1;
    const customField = customField$1;
    const validateEnum = values => valueOf(value => contains$2(values, value) ? Result.value(value) : Result.error(`Unsupported value: "${ value }", choose one of "${ values.join(', ') }".`));
    const requiredOf = (key, schema) => field(key, key, required(), schema);
    const requiredString = key => requiredOf(key, string);
    const requiredFunction = key => requiredOf(key, functionProcessor);
    const requiredArrayOf = (key, schema) => field(key, key, required(), arrOf(schema));
    const optionOf = (key, schema) => field(key, key, asOption(), schema);
    const optionString = key => optionOf(key, string);
    const optionFunction = key => optionOf(key, functionProcessor);
    const defaulted = (key, fallback) => field(key, key, defaulted$1(fallback), anyValue());
    const defaultedOf = (key, fallback, schema) => field(key, key, defaulted$1(fallback), schema);
    const defaultedNumber = (key, fallback) => defaultedOf(key, fallback, number);
    const defaultedString = (key, fallback) => defaultedOf(key, fallback, string);
    const defaultedStringEnum = (key, fallback, values) => defaultedOf(key, fallback, validateEnum(values));
    const defaultedBoolean = (key, fallback) => defaultedOf(key, fallback, boolean);
    const defaultedFunction = (key, fallback) => defaultedOf(key, fallback, functionProcessor);
    const defaultedArrayOf = (key, fallback, schema) => defaultedOf(key, fallback, arrOf(schema));

    const type = requiredString('type');
    const fetch$1 = requiredFunction('fetch');
    const onAction = requiredFunction('onAction');
    const onSetup = defaultedFunction('onSetup', () => noop);
    const optionalText = optionString('text');
    const optionalIcon = optionString('icon');
    const optionalTooltip = optionString('tooltip');
    const optionalLabel = optionString('label');
    const active = defaultedBoolean('active', false);
    const enabled = defaultedBoolean('enabled', true);
    const primary = defaultedBoolean('primary', false);
    const defaultedColumns = num => defaulted('columns', num);
    const defaultedType = type => defaultedString('type', type);

    const autocompleterSchema = objOf([
      type,
      requiredString('trigger'),
      defaultedNumber('minChars', 1),
      defaultedColumns(1),
      defaultedNumber('maxResults', 10),
      optionFunction('matches'),
      fetch$1,
      onAction,
      defaultedArrayOf('highlightOn', [], string)
    ]);
    const createAutocompleter = spec => asRaw('Autocompleter', autocompleterSchema, {
      trigger: spec.ch,
      ...spec
    });

    const baseToolbarButtonFields = [
      enabled,
      optionalTooltip,
      optionalIcon,
      optionalText,
      onSetup
    ];

    const baseToolbarToggleButtonFields = [active].concat(baseToolbarButtonFields);

    const contextBarFields = [
      defaultedFunction('predicate', never),
      defaultedStringEnum('scope', 'node', [
        'node',
        'editor'
      ]),
      defaultedStringEnum('position', 'selection', [
        'node',
        'selection',
        'line'
      ])
    ];

    const contextButtonFields = baseToolbarButtonFields.concat([
      defaultedType('contextformbutton'),
      primary,
      onAction,
      customField('original', identity)
    ]);
    const contextToggleButtonFields = baseToolbarToggleButtonFields.concat([
      defaultedType('contextformbutton'),
      primary,
      onAction,
      customField('original', identity)
    ]);
    const launchButtonFields = baseToolbarButtonFields.concat([defaultedType('contextformbutton')]);
    const launchToggleButtonFields = baseToolbarToggleButtonFields.concat([defaultedType('contextformtogglebutton')]);
    const toggleOrNormal = choose('type', {
      contextformbutton: contextButtonFields,
      contextformtogglebutton: contextToggleButtonFields
    });
    objOf([
      defaultedType('contextform'),
      defaultedFunction('initValue', constant('')),
      optionalLabel,
      requiredArrayOf('commands', toggleOrNormal),
      optionOf('launch', choose('type', {
        contextformbutton: launchButtonFields,
        contextformtogglebutton: launchToggleButtonFields
      }))
    ].concat(contextBarFields));

    const register$2 = editor => {
      const popups = editor.ui.registry.getAll().popups;
      const dataset = map$2(popups, popup => createAutocompleter(popup).fold(err => {
        throw new Error(formatError(err));
      }, identity));
      const triggers = stringArray(mapToArray(dataset, v => v.trigger));
      const datasetValues = values(dataset);
      const lookupByTrigger = trigger => filter$5(datasetValues, dv => dv.trigger === trigger);
      return {
        dataset,
        triggers,
        lookupByTrigger
      };
    };

    const setupEditorInput = (editor, api) => {
      const update = last$1(api.load, 50);
      editor.on('keypress compositionend', e => {
        if (e.which === 27) {
          return;
        }
        update.throttle();
      });
      editor.on('keydown', e => {
        const keyCode = e.which;
        if (keyCode === 8) {
          update.throttle();
        } else if (keyCode === 27) {
          api.cancelIfNecessary();
        }
      });
      editor.on('remove', update.cancel);
    };
    const setup$k = editor => {
      const activeAutocompleter = value$2();
      const uiActive = Cell(false);
      const isActive = activeAutocompleter.isSet;
      const cancelIfNecessary = () => {
        if (isActive()) {
          removeAutocompleterDecoration(editor);
          fireAutocompleterEnd(editor);
          uiActive.set(false);
          activeAutocompleter.clear();
        }
      };
      const commenceIfNecessary = context => {
        if (!isActive()) {
          addAutocompleterDecoration(editor, context.range);
          activeAutocompleter.set({
            trigger: context.trigger,
            matchLength: context.text.length
          });
        }
      };
      const getAutocompleters = cached(() => register$2(editor));
      const doLookup = fetchOptions => activeAutocompleter.get().map(ac => getContext(editor.dom, editor.selection.getRng(), ac.trigger).bind(newContext => lookupWithContext(editor, getAutocompleters, newContext, fetchOptions))).getOrThunk(() => lookup(editor, getAutocompleters));
      const load = fetchOptions => {
        doLookup(fetchOptions).fold(cancelIfNecessary, lookupInfo => {
          commenceIfNecessary(lookupInfo.context);
          lookupInfo.lookupData.then(lookupData => {
            activeAutocompleter.get().map(ac => {
              const context = lookupInfo.context;
              if (ac.trigger === context.trigger) {
                if (context.text.length - ac.matchLength >= 10) {
                  cancelIfNecessary();
                } else {
                  activeAutocompleter.set({
                    ...ac,
                    matchLength: context.text.length
                  });
                  if (uiActive.get()) {
                    fireAutocompleterUpdate(editor, { lookupData });
                  } else {
                    uiActive.set(true);
                    fireAutocompleterStart(editor, { lookupData });
                  }
                }
              }
            });
          });
        });
      };
      editor.addCommand('mceAutocompleterReload', (_ui, value) => {
        const fetchOptions = isObject(value) ? value.fetchOptions : {};
        load(fetchOptions);
      });
      editor.addCommand('mceAutocompleterClose', cancelIfNecessary);
      setupEditorInput(editor, {
        cancelIfNecessary,
        load
      });
    };

    const browser$1 = detect$2().browser;
    const isSafari = browser$1.isSafari();
    const emptyNodeContents = node => fillWithPaddingBr(SugarElement.fromDom(node));
    const isEntireNodeSelected = (rng, node) => {
      var _a;
      return rng.startOffset === 0 && rng.endOffset === ((_a = node.textContent) === null || _a === void 0 ? void 0 : _a.length);
    };
    const getParentDetailsElementAtPos = (dom, pos) => Optional.from(dom.getParent(pos.container(), 'details'));
    const isInDetailsElement = (dom, pos) => getParentDetailsElementAtPos(dom, pos).isSome();
    const getDetailsElements = (dom, rng) => {
      const startDetails = Optional.from(dom.getParent(rng.startContainer, 'details'));
      const endDetails = Optional.from(dom.getParent(rng.endContainer, 'details'));
      if (startDetails.isSome() || endDetails.isSome()) {
        const startSummary = startDetails.bind(details => Optional.from(dom.select('summary', details)[0]));
        return Optional.some({
          startSummary,
          startDetails,
          endDetails
        });
      } else {
        return Optional.none();
      }
    };
    const isCaretInTheBeginningOf = (caretPos, element) => firstPositionIn(element).exists(pos => pos.isEqual(caretPos));
    const isCaretInTheEndOf = (caretPos, element) => {
      return lastPositionIn(element).exists(pos => {
        if (isBr$6(pos.getNode())) {
          return prevPosition(element, pos).exists(pos2 => pos2.isEqual(caretPos)) || pos.isEqual(caretPos);
        } else {
          return pos.isEqual(caretPos);
        }
      });
    };
    const isCaretAtStartOfSummary = (caretPos, detailsElements) => detailsElements.startSummary.exists(summary => isCaretInTheBeginningOf(caretPos, summary));
    const isCaretAtEndOfSummary = (caretPos, detailsElements) => detailsElements.startSummary.exists(summary => isCaretInTheEndOf(caretPos, summary));
    const isCaretInFirstPositionInBody = (caretPos, detailsElements) => detailsElements.startDetails.exists(details => prevPosition(details, caretPos).forall(pos => detailsElements.startSummary.exists(summary => !summary.contains(caretPos.container()) && summary.contains(pos.container()))));
    const isCaretInLastPositionInBody = (root, caretPos, detailsElements) => detailsElements.startDetails.exists(details => nextPosition(root, caretPos).forall(pos => !details.contains(pos.container())));
    const setCaretToPosition = (editor, position) => {
      const node = position.getNode();
      if (!isUndefined(node)) {
        editor.selection.setCursorLocation(node, position.offset());
      }
    };
    const moveCaretToDetailsPos = (editor, pos, forward) => {
      const details = editor.dom.getParent(pos.container(), 'details');
      if (details && !details.open) {
        const summary = editor.dom.select('summary', details)[0];
        if (summary) {
          const newPos = forward ? firstPositionIn(summary) : lastPositionIn(summary);
          newPos.each(pos => setCaretToPosition(editor, pos));
        }
      } else {
        setCaretToPosition(editor, pos);
      }
    };
    const isPartialDelete = (rng, detailsElements) => {
      const containsStart = element => element.contains(rng.startContainer);
      const containsEnd = element => element.contains(rng.endContainer);
      const startInSummary = detailsElements.startSummary.exists(containsStart);
      const endInSummary = detailsElements.startSummary.exists(containsEnd);
      const isPartiallySelectedDetailsElements = detailsElements.startDetails.forall(startDetails => detailsElements.endDetails.forall(endDetails => startDetails !== endDetails));
      const isInPartiallySelectedSummary = (startInSummary || endInSummary) && !(startInSummary && endInSummary);
      return isInPartiallySelectedSummary || isPartiallySelectedDetailsElements;
    };
    const shouldPreventDeleteIntoDetails = (editor, forward, granularity) => {
      const {dom, selection} = editor;
      const root = editor.getBody();
      if (granularity === 'character') {
        const caretPos = CaretPosition.fromRangeStart(selection.getRng());
        const parentBlock = dom.getParent(caretPos.container(), dom.isBlock);
        const parentDetailsAtCaret = getParentDetailsElementAtPos(dom, caretPos);
        const inEmptyParentBlock = parentBlock && dom.isEmpty(parentBlock);
        const isFirstBlock = isNull(parentBlock === null || parentBlock === void 0 ? void 0 : parentBlock.previousSibling);
        const isLastBlock = isNull(parentBlock === null || parentBlock === void 0 ? void 0 : parentBlock.nextSibling);
        if (inEmptyParentBlock) {
          const firstOrLast = forward ? isLastBlock : isFirstBlock;
          if (firstOrLast) {
            const isBeforeAfterDetails = navigate(!forward, root, caretPos).exists(pos => {
              return isInDetailsElement(dom, pos) && !equals(parentDetailsAtCaret, getParentDetailsElementAtPos(dom, pos));
            });
            if (isBeforeAfterDetails) {
              return true;
            }
          }
        }
        return navigate(forward, root, caretPos).fold(never, pos => {
          const parentDetailsAtNewPos = getParentDetailsElementAtPos(dom, pos);
          if (isInDetailsElement(dom, pos) && !equals(parentDetailsAtCaret, parentDetailsAtNewPos)) {
            if (!forward) {
              moveCaretToDetailsPos(editor, pos, false);
            }
            if (parentBlock && inEmptyParentBlock) {
              if (forward && isFirstBlock) {
                return true;
              } else if (!forward && isLastBlock) {
                return true;
              }
              moveCaretToDetailsPos(editor, pos, forward);
              editor.dom.remove(parentBlock);
            }
            return true;
          } else {
            return false;
          }
        });
      } else {
        return false;
      }
    };
    const shouldPreventDeleteSummaryAction = (editor, detailElements, forward, granularity) => {
      const selection = editor.selection;
      const rng = selection.getRng();
      const caretPos = CaretPosition.fromRangeStart(rng);
      const root = editor.getBody();
      if (granularity === 'selection') {
        return isPartialDelete(rng, detailElements);
      } else if (forward) {
        return isCaretAtEndOfSummary(caretPos, detailElements) || isCaretInLastPositionInBody(root, caretPos, detailElements);
      } else {
        return isCaretAtStartOfSummary(caretPos, detailElements) || isCaretInFirstPositionInBody(caretPos, detailElements);
      }
    };
    const shouldPreventDeleteAction = (editor, forward, granularity) => getDetailsElements(editor.dom, editor.selection.getRng()).fold(() => shouldPreventDeleteIntoDetails(editor, forward, granularity), detailsElements => shouldPreventDeleteSummaryAction(editor, detailsElements, forward, granularity) || shouldPreventDeleteIntoDetails(editor, forward, granularity));
    const handleDeleteActionSafari = (editor, forward, granularity) => {
      const selection = editor.selection;
      const node = selection.getNode();
      const rng = selection.getRng();
      const caretPos = CaretPosition.fromRangeStart(rng);
      if (isSummary$1(node)) {
        if (granularity === 'selection' && isEntireNodeSelected(rng, node) || willDeleteLastPositionInElement(forward, caretPos, node)) {
          emptyNodeContents(node);
        } else {
          editor.undoManager.transact(() => {
            const sel = selection.getSel();
            let {anchorNode, anchorOffset, focusNode, focusOffset} = sel !== null && sel !== void 0 ? sel : {};
            const applySelection = () => {
              if (isNonNullable(anchorNode) && isNonNullable(anchorOffset) && isNonNullable(focusNode) && isNonNullable(focusOffset)) {
                sel === null || sel === void 0 ? void 0 : sel.setBaseAndExtent(anchorNode, anchorOffset, focusNode, focusOffset);
              }
            };
            const updateSelection = () => {
              anchorNode = sel === null || sel === void 0 ? void 0 : sel.anchorNode;
              anchorOffset = sel === null || sel === void 0 ? void 0 : sel.anchorOffset;
              focusNode = sel === null || sel === void 0 ? void 0 : sel.focusNode;
              focusOffset = sel === null || sel === void 0 ? void 0 : sel.focusOffset;
            };
            const appendAllChildNodes = (from, to) => {
              each$e(from.childNodes, child => {
                if (isNode(child)) {
                  to.appendChild(child);
                }
              });
            };
            const container = editor.dom.create('span', { 'data-mce-bogus': '1' });
            appendAllChildNodes(node, container);
            node.appendChild(container);
            applySelection();
            if (granularity === 'word' || granularity === 'line') {
              sel === null || sel === void 0 ? void 0 : sel.modify('extend', forward ? 'right' : 'left', granularity);
            }
            if (!selection.isCollapsed() && isEntireNodeSelected(selection.getRng(), container)) {
              emptyNodeContents(node);
            } else {
              editor.execCommand(forward ? 'ForwardDelete' : 'Delete');
              updateSelection();
              appendAllChildNodes(container, node);
              applySelection();
            }
            editor.dom.remove(container);
          });
        }
        return true;
      } else {
        return false;
      }
    };
    const backspaceDelete = (editor, forward, granularity) => shouldPreventDeleteAction(editor, forward, granularity) || isSafari && handleDeleteActionSafari(editor, forward, granularity) ? Optional.some(noop) : Optional.none();

    const createAndFireInputEvent = eventType => (editor, inputType, specifics = {}) => {
      const target = editor.getBody();
      const overrides = {
        bubbles: true,
        composed: true,
        data: null,
        isComposing: false,
        detail: 0,
        view: null,
        target,
        currentTarget: target,
        eventPhase: Event.AT_TARGET,
        originalTarget: target,
        explicitOriginalTarget: target,
        isTrusted: false,
        srcElement: target,
        cancelable: false,
        preventDefault: noop,
        inputType
      };
      const input = clone$3(new InputEvent(eventType));
      return editor.dispatch(eventType, {
        ...input,
        ...overrides,
        ...specifics
      });
    };
    const fireInputEvent = createAndFireInputEvent('input');
    const fireBeforeInputEvent = createAndFireInputEvent('beforeinput');

    const platform$2 = detect$2();
    const os = platform$2.os;
    const isMacOSOriOS = os.isMacOS() || os.isiOS();
    const browser = platform$2.browser;
    const isFirefox = browser.isFirefox();
    const executeKeydownOverride$3 = (editor, caret, evt) => {
      const inputType = evt.keyCode === VK.BACKSPACE ? 'deleteContentBackward' : 'deleteContentForward';
      const isCollapsed = editor.selection.isCollapsed();
      const unmodifiedGranularity = isCollapsed ? 'character' : 'selection';
      const getModifiedGranularity = isWord => {
        if (isCollapsed) {
          return isWord ? 'word' : 'line';
        } else {
          return 'selection';
        }
      };
      executeWithDelayedAction([
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$1, editor)
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$6, editor, false)
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$6, editor, true)
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$7, editor, false)
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$7, editor, true)
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$4, editor, caret, false)
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$4, editor, caret, true)
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$a, editor, false)
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$a, editor, true)
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete, editor, false, unmodifiedGranularity)
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete, editor, true, unmodifiedGranularity)
        },
        ...isMacOSOriOS ? [
          {
            keyCode: VK.BACKSPACE,
            altKey: true,
            action: action(backspaceDelete, editor, false, getModifiedGranularity(true))
          },
          {
            keyCode: VK.DELETE,
            altKey: true,
            action: action(backspaceDelete, editor, true, getModifiedGranularity(true))
          },
          {
            keyCode: VK.BACKSPACE,
            metaKey: true,
            action: action(backspaceDelete, editor, false, getModifiedGranularity(false))
          }
        ] : [
          {
            keyCode: VK.BACKSPACE,
            ctrlKey: true,
            action: action(backspaceDelete, editor, false, getModifiedGranularity(true))
          },
          {
            keyCode: VK.DELETE,
            ctrlKey: true,
            action: action(backspaceDelete, editor, true, getModifiedGranularity(true))
          }
        ],
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$5, editor, false)
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$5, editor, true)
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$2, editor, false)
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$2, editor, true)
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$8, editor, false)
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$8, editor, true)
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$9, editor, false)
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$9, editor, true)
        },
        {
          keyCode: VK.BACKSPACE,
          action: action(backspaceDelete$3, editor, false)
        },
        {
          keyCode: VK.DELETE,
          action: action(backspaceDelete$3, editor, true)
        }
      ], evt).filter(_ => editor.selection.isEditable()).each(applyAction => {
        evt.preventDefault();
        const beforeInput = fireBeforeInputEvent(editor, inputType);
        if (!beforeInput.isDefaultPrevented()) {
          applyAction();
          fireInputEvent(editor, inputType);
        }
      });
    };
    const executeKeyupOverride = (editor, evt, isBackspaceKeydown) => execute([
      {
        keyCode: VK.BACKSPACE,
        action: action(paddEmptyElement, editor)
      },
      {
        keyCode: VK.DELETE,
        action: action(paddEmptyElement, editor)
      },
      ...isMacOSOriOS ? [
        {
          keyCode: VK.BACKSPACE,
          altKey: true,
          action: action(refreshCaret, editor)
        },
        {
          keyCode: VK.DELETE,
          altKey: true,
          action: action(refreshCaret, editor)
        },
        ...isBackspaceKeydown ? [{
            keyCode: isFirefox ? 224 : 91,
            action: action(refreshCaret, editor)
          }] : []
      ] : [
        {
          keyCode: VK.BACKSPACE,
          ctrlKey: true,
          action: action(refreshCaret, editor)
        },
        {
          keyCode: VK.DELETE,
          ctrlKey: true,
          action: action(refreshCaret, editor)
        }
      ]
    ], evt);
    const setup$j = (editor, caret) => {
      let isBackspaceKeydown = false;
      editor.on('keydown', evt => {
        isBackspaceKeydown = evt.keyCode === VK.BACKSPACE;
        if (!evt.isDefaultPrevented()) {
          executeKeydownOverride$3(editor, caret, evt);
        }
      });
      editor.on('keyup', evt => {
        if (!evt.isDefaultPrevented()) {
          executeKeyupOverride(editor, evt, isBackspaceKeydown);
        }
        isBackspaceKeydown = false;
      });
    };

    const firstNonWhiteSpaceNodeSibling = node => {
      while (node) {
        if (isElement$6(node) || isText$a(node) && node.data && /[\r\n\s]/.test(node.data)) {
          return node;
        }
        node = node.nextSibling;
      }
      return null;
    };
    const moveToCaretPosition = (editor, root) => {
      const dom = editor.dom;
      const moveCaretBeforeOnEnterElementsMap = editor.schema.getMoveCaretBeforeOnEnterElements();
      if (!root) {
        return;
      }
      if (/^(LI|DT|DD)$/.test(root.nodeName)) {
        const firstChild = firstNonWhiteSpaceNodeSibling(root.firstChild);
        if (firstChild && /^(UL|OL|DL)$/.test(firstChild.nodeName)) {
          root.insertBefore(dom.doc.createTextNode(nbsp), root.firstChild);
        }
      }
      const rng = dom.createRng();
      root.normalize();
      if (root.hasChildNodes()) {
        const walker = new DomTreeWalker(root, root);
        let lastNode = root;
        let node;
        while (node = walker.current()) {
          if (isText$a(node)) {
            rng.setStart(node, 0);
            rng.setEnd(node, 0);
            break;
          }
          if (moveCaretBeforeOnEnterElementsMap[node.nodeName.toLowerCase()]) {
            rng.setStartBefore(node);
            rng.setEndBefore(node);
            break;
          }
          lastNode = node;
          node = walker.next();
        }
        if (!node) {
          rng.setStart(lastNode, 0);
          rng.setEnd(lastNode, 0);
        }
      } else {
        if (isBr$6(root)) {
          if (root.nextSibling && dom.isBlock(root.nextSibling)) {
            rng.setStartBefore(root);
            rng.setEndBefore(root);
          } else {
            rng.setStartAfter(root);
            rng.setEndAfter(root);
          }
        } else {
          rng.setStart(root, 0);
          rng.setEnd(root, 0);
        }
      }
      editor.selection.setRng(rng);
      scrollRangeIntoView(editor, rng);
    };
    const getEditableRoot = (dom, node) => {
      const root = dom.getRoot();
      let editableRoot;
      let parent = node;
      while (parent !== root && parent && dom.getContentEditable(parent) !== 'false') {
        if (dom.getContentEditable(parent) === 'true') {
          editableRoot = parent;
          break;
        }
        parent = parent.parentNode;
      }
      return parent !== root ? editableRoot : root;
    };
    const getParentBlock$1 = editor => {
      return Optional.from(editor.dom.getParent(editor.selection.getStart(true), editor.dom.isBlock));
    };
    const getParentBlockName = editor => {
      return getParentBlock$1(editor).fold(constant(''), parentBlock => {
        return parentBlock.nodeName.toUpperCase();
      });
    };
    const isListItemParentBlock = editor => {
      return getParentBlock$1(editor).filter(elm => {
        return isListItem$1(SugarElement.fromDom(elm));
      }).isSome();
    };
    const emptyBlock = elm => {
      elm.innerHTML = '<br data-mce-bogus="1">';
    };
    const applyAttributes = (editor, node, forcedRootBlockAttrs) => {
      const dom = editor.dom;
      Optional.from(forcedRootBlockAttrs.style).map(dom.parseStyle).each(attrStyles => {
        const currentStyles = getAllRaw(SugarElement.fromDom(node));
        const newStyles = {
          ...currentStyles,
          ...attrStyles
        };
        dom.setStyles(node, newStyles);
      });
      const attrClassesOpt = Optional.from(forcedRootBlockAttrs.class).map(attrClasses => attrClasses.split(/\s+/));
      const currentClassesOpt = Optional.from(node.className).map(currentClasses => filter$5(currentClasses.split(/\s+/), clazz => clazz !== ''));
      lift2(attrClassesOpt, currentClassesOpt, (attrClasses, currentClasses) => {
        const filteredClasses = filter$5(currentClasses, clazz => !contains$2(attrClasses, clazz));
        const newClasses = [
          ...attrClasses,
          ...filteredClasses
        ];
        dom.setAttrib(node, 'class', newClasses.join(' '));
      });
      const appliedAttrs = [
        'style',
        'class'
      ];
      const remainingAttrs = filter$4(forcedRootBlockAttrs, (_, attrs) => !contains$2(appliedAttrs, attrs));
      dom.setAttribs(node, remainingAttrs);
    };
    const setForcedBlockAttrs = (editor, node) => {
      const forcedRootBlockName = getForcedRootBlock(editor);
      if (forcedRootBlockName.toLowerCase() === node.tagName.toLowerCase()) {
        const forcedRootBlockAttrs = getForcedRootBlockAttrs(editor);
        applyAttributes(editor, node, forcedRootBlockAttrs);
      }
    };
    const createNewBlock = (editor, container, parentBlock, editableRoot, keepStyles = true, name, styles) => {
      const dom = editor.dom;
      const schema = editor.schema;
      const newBlockName = getForcedRootBlock(editor);
      const parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : '';
      let node = container;
      const textInlineElements = schema.getTextInlineElements();
      let block;
      if (name || parentBlockName === 'TABLE' || parentBlockName === 'HR') {
        block = dom.create(name || newBlockName, styles || {});
      } else {
        block = parentBlock.cloneNode(false);
      }
      let caretNode = block;
      if (!keepStyles) {
        dom.setAttrib(block, 'style', null);
        dom.setAttrib(block, 'class', null);
      } else {
        do {
          if (textInlineElements[node.nodeName]) {
            if (isCaretNode(node) || isBookmarkNode$1(node)) {
              continue;
            }
            const clonedNode = node.cloneNode(false);
            dom.setAttrib(clonedNode, 'id', '');
            if (block.hasChildNodes()) {
              clonedNode.appendChild(block.firstChild);
              block.appendChild(clonedNode);
            } else {
              caretNode = clonedNode;
              block.appendChild(clonedNode);
            }
          }
        } while ((node = node.parentNode) && node !== editableRoot);
      }
      setForcedBlockAttrs(editor, block);
      emptyBlock(caretNode);
      return block;
    };

    const getDetailsRoot = (editor, element) => editor.dom.getParent(element, isDetails);
    const isAtDetailsEdge = (root, element, isTextBlock) => {
      let node = element;
      while (node && node !== root && isNull(node.nextSibling)) {
        const parent = node.parentElement;
        if (!parent || !isTextBlock(parent)) {
          return isDetails(parent);
        }
        node = parent;
      }
      return false;
    };
    const isLastEmptyBlockInDetails = (editor, shiftKey, element) => !shiftKey && element.nodeName.toLowerCase() === getForcedRootBlock(editor) && editor.dom.isEmpty(element) && isAtDetailsEdge(editor.getBody(), element, el => has$2(editor.schema.getTextBlockElements(), el.nodeName.toLowerCase()));
    const insertNewLine = (editor, createNewBlock, parentBlock) => {
      var _a, _b, _c;
      const newBlock = createNewBlock(getForcedRootBlock(editor));
      const root = getDetailsRoot(editor, parentBlock);
      if (!root) {
        return;
      }
      editor.dom.insertAfter(newBlock, root);
      moveToCaretPosition(editor, newBlock);
      if (((_c = (_b = (_a = parentBlock.parentElement) === null || _a === void 0 ? void 0 : _a.childNodes) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0) > 1) {
        editor.dom.remove(parentBlock);
      }
    };

    const hasFirstChild = (elm, name) => {
      return elm.firstChild && elm.firstChild.nodeName === name;
    };
    const isFirstChild = elm => {
      var _a;
      return ((_a = elm.parentNode) === null || _a === void 0 ? void 0 : _a.firstChild) === elm;
    };
    const hasParent = (elm, parentName) => {
      const parentNode = elm === null || elm === void 0 ? void 0 : elm.parentNode;
      return isNonNullable(parentNode) && parentNode.nodeName === parentName;
    };
    const isListBlock = elm => {
      return isNonNullable(elm) && /^(OL|UL|LI)$/.test(elm.nodeName);
    };
    const isListItem = elm => {
      return isNonNullable(elm) && /^(LI|DT|DD)$/.test(elm.nodeName);
    };
    const isNestedList = elm => {
      return isListBlock(elm) && isListBlock(elm.parentNode);
    };
    const getContainerBlock = containerBlock => {
      const containerBlockParent = containerBlock.parentNode;
      return isListItem(containerBlockParent) ? containerBlockParent : containerBlock;
    };
    const isFirstOrLastLi = (containerBlock, parentBlock, first) => {
      let node = containerBlock[first ? 'firstChild' : 'lastChild'];
      while (node) {
        if (isElement$6(node)) {
          break;
        }
        node = node[first ? 'nextSibling' : 'previousSibling'];
      }
      return node === parentBlock;
    };
    const getStyles = elm => foldl(mapToArray(getAllRaw(SugarElement.fromDom(elm)), (style, styleName) => `${ styleName }: ${ style };`), (acc, s) => acc + s, '');
    const insert$4 = (editor, createNewBlock, containerBlock, parentBlock, newBlockName) => {
      const dom = editor.dom;
      const rng = editor.selection.getRng();
      const containerParent = containerBlock.parentNode;
      if (containerBlock === editor.getBody() || !containerParent) {
        return;
      }
      if (isNestedList(containerBlock)) {
        newBlockName = 'LI';
      }
      const parentBlockStyles = isListItem(parentBlock) ? getStyles(parentBlock) : undefined;
      let newBlock = isListItem(parentBlock) && parentBlockStyles ? createNewBlock(newBlockName, { style: getStyles(parentBlock) }) : createNewBlock(newBlockName);
      if (isFirstOrLastLi(containerBlock, parentBlock, true) && isFirstOrLastLi(containerBlock, parentBlock, false)) {
        if (hasParent(containerBlock, 'LI')) {
          const containerBlockParent = getContainerBlock(containerBlock);
          dom.insertAfter(newBlock, containerBlockParent);
          if (isFirstChild(containerBlock)) {
            dom.remove(containerBlockParent);
          } else {
            dom.remove(containerBlock);
          }
        } else {
          dom.replace(newBlock, containerBlock);
        }
      } else if (isFirstOrLastLi(containerBlock, parentBlock, true)) {
        if (hasParent(containerBlock, 'LI')) {
          dom.insertAfter(newBlock, getContainerBlock(containerBlock));
          newBlock.appendChild(dom.doc.createTextNode(' '));
          newBlock.appendChild(containerBlock);
        } else {
          containerParent.insertBefore(newBlock, containerBlock);
        }
        dom.remove(parentBlock);
      } else if (isFirstOrLastLi(containerBlock, parentBlock, false)) {
        dom.insertAfter(newBlock, getContainerBlock(containerBlock));
        dom.remove(parentBlock);
      } else {
        containerBlock = getContainerBlock(containerBlock);
        const tmpRng = rng.cloneRange();
        tmpRng.setStartAfter(parentBlock);
        tmpRng.setEndAfter(containerBlock);
        const fragment = tmpRng.extractContents();
        if (newBlockName === 'LI' && hasFirstChild(fragment, 'LI')) {
          const previousChildren = filter$5(map$3(newBlock.children, SugarElement.fromDom), not(isTag('br')));
          newBlock = fragment.firstChild;
          dom.insertAfter(fragment, containerBlock);
          each$e(previousChildren, child => prepend(SugarElement.fromDom(newBlock), child));
          if (parentBlockStyles) {
            newBlock.setAttribute('style', parentBlockStyles);
          }
        } else {
          dom.insertAfter(fragment, containerBlock);
          dom.insertAfter(newBlock, containerBlock);
        }
        dom.remove(parentBlock);
      }
      moveToCaretPosition(editor, newBlock);
    };

    const trimZwsp = fragment => {
      each$e(descendants$1(SugarElement.fromDom(fragment), isText$b), text => {
        const rawNode = text.dom;
        rawNode.nodeValue = trim$2(rawNode.data);
      });
    };
    const isWithinNonEditableList = (editor, node) => {
      const parentList = editor.dom.getParent(node, 'ol,ul,dl');
      return parentList !== null && editor.dom.getContentEditableParent(parentList) === 'false';
    };
    const isEmptyAnchor = (dom, elm) => {
      return elm && elm.nodeName === 'A' && dom.isEmpty(elm);
    };
    const containerAndSiblingName = (container, nodeName) => {
      return container.nodeName === nodeName || container.previousSibling && container.previousSibling.nodeName === nodeName;
    };
    const canSplitBlock = (dom, node) => {
      return isNonNullable(node) && dom.isBlock(node) && !/^(TD|TH|CAPTION|FORM)$/.test(node.nodeName) && !/^(fixed|absolute)/i.test(node.style.position) && dom.isEditable(node.parentNode) && dom.getContentEditable(node) !== 'false';
    };
    const trimInlineElementsOnLeftSideOfBlock = (dom, nonEmptyElementsMap, block) => {
      var _a;
      const firstChilds = [];
      if (!block) {
        return;
      }
      let currentNode = block;
      while (currentNode = currentNode.firstChild) {
        if (dom.isBlock(currentNode)) {
          return;
        }
        if (isElement$6(currentNode) && !nonEmptyElementsMap[currentNode.nodeName.toLowerCase()]) {
          firstChilds.push(currentNode);
        }
      }
      let i = firstChilds.length;
      while (i--) {
        currentNode = firstChilds[i];
        if (!currentNode.hasChildNodes() || currentNode.firstChild === currentNode.lastChild && ((_a = currentNode.firstChild) === null || _a === void 0 ? void 0 : _a.nodeValue) === '') {
          dom.remove(currentNode);
        } else {
          if (isEmptyAnchor(dom, currentNode)) {
            dom.remove(currentNode);
          }
        }
      }
    };
    const normalizeZwspOffset = (start, container, offset) => {
      if (!isText$a(container)) {
        return offset;
      } else if (start) {
        return offset === 1 && container.data.charAt(offset - 1) === ZWSP$1 ? 0 : offset;
      } else {
        return offset === container.data.length - 1 && container.data.charAt(offset) === ZWSP$1 ? container.data.length : offset;
      }
    };
    const includeZwspInRange = rng => {
      const newRng = rng.cloneRange();
      newRng.setStart(rng.startContainer, normalizeZwspOffset(true, rng.startContainer, rng.startOffset));
      newRng.setEnd(rng.endContainer, normalizeZwspOffset(false, rng.endContainer, rng.endOffset));
      return newRng;
    };
    const trimLeadingLineBreaks = node => {
      let currentNode = node;
      do {
        if (isText$a(currentNode)) {
          currentNode.data = currentNode.data.replace(/^[\r\n]+/, '');
        }
        currentNode = currentNode.firstChild;
      } while (currentNode);
    };
    const wrapSelfAndSiblingsInDefaultBlock = (editor, newBlockName, rng, container, offset) => {
      var _a, _b;
      const dom = editor.dom;
      const editableRoot = (_a = getEditableRoot(dom, container)) !== null && _a !== void 0 ? _a : dom.getRoot();
      let parentBlock = dom.getParent(container, dom.isBlock);
      if (!parentBlock || !canSplitBlock(dom, parentBlock)) {
        parentBlock = parentBlock || editableRoot;
        if (!parentBlock.hasChildNodes()) {
          const newBlock = dom.create(newBlockName);
          setForcedBlockAttrs(editor, newBlock);
          parentBlock.appendChild(newBlock);
          rng.setStart(newBlock, 0);
          rng.setEnd(newBlock, 0);
          return newBlock;
        }
        let node = container;
        while (node && node.parentNode !== parentBlock) {
          node = node.parentNode;
        }
        let startNode;
        while (node && !dom.isBlock(node)) {
          startNode = node;
          node = node.previousSibling;
        }
        const startNodeName = (_b = startNode === null || startNode === void 0 ? void 0 : startNode.parentElement) === null || _b === void 0 ? void 0 : _b.nodeName;
        if (startNode && startNodeName && editor.schema.isValidChild(startNodeName, newBlockName.toLowerCase())) {
          const startNodeParent = startNode.parentNode;
          const newBlock = dom.create(newBlockName);
          setForcedBlockAttrs(editor, newBlock);
          startNodeParent.insertBefore(newBlock, startNode);
          node = startNode;
          while (node && !dom.isBlock(node)) {
            const next = node.nextSibling;
            newBlock.appendChild(node);
            node = next;
          }
          rng.setStart(container, offset);
          rng.setEnd(container, offset);
        }
      }
      return container;
    };
    const addBrToBlockIfNeeded = (dom, block) => {
      block.normalize();
      const lastChild = block.lastChild;
      if (!lastChild || isElement$6(lastChild) && /^(left|right)$/gi.test(dom.getStyle(lastChild, 'float', true))) {
        dom.add(block, 'br');
      }
    };
    const shouldEndContainer = (editor, container) => {
      const optionValue = shouldEndContainerOnEmptyBlock(editor);
      if (isNullable(container)) {
        return false;
      } else if (isString(optionValue)) {
        return contains$2(Tools.explode(optionValue), container.nodeName.toLowerCase());
      } else {
        return optionValue;
      }
    };
    const insert$3 = (editor, evt) => {
      let container;
      let offset;
      let parentBlockName;
      let containerBlock;
      let isAfterLastNodeInContainer = false;
      const dom = editor.dom;
      const schema = editor.schema, nonEmptyElementsMap = schema.getNonEmptyElements();
      const rng = editor.selection.getRng();
      const newBlockName = getForcedRootBlock(editor);
      const start = SugarElement.fromDom(rng.startContainer);
      const child = child$1(start, rng.startOffset);
      const isCef = child.exists(element => isHTMLElement$1(element) && !isEditable$2(element));
      const collapsedAndCef = rng.collapsed && isCef;
      const createNewBlock$1 = (name, styles) => {
        return createNewBlock(editor, container, parentBlock, editableRoot, shouldKeepStyles(editor), name, styles);
      };
      const isCaretAtStartOrEndOfBlock = start => {
        const normalizedOffset = normalizeZwspOffset(start, container, offset);
        if (isText$a(container) && (start ? normalizedOffset > 0 : normalizedOffset < container.data.length)) {
          return false;
        }
        if (container.parentNode === parentBlock && isAfterLastNodeInContainer && !start) {
          return true;
        }
        if (start && isElement$6(container) && container === parentBlock.firstChild) {
          return true;
        }
        if (containerAndSiblingName(container, 'TABLE') || containerAndSiblingName(container, 'HR')) {
          return isAfterLastNodeInContainer && !start || !isAfterLastNodeInContainer && start;
        }
        const walker = new DomTreeWalker(container, parentBlock);
        if (isText$a(container)) {
          if (start && normalizedOffset === 0) {
            walker.prev();
          } else if (!start && normalizedOffset === container.data.length) {
            walker.next();
          }
        }
        let node;
        while (node = walker.current()) {
          if (isElement$6(node)) {
            if (!node.getAttribute('data-mce-bogus')) {
              const name = node.nodeName.toLowerCase();
              if (nonEmptyElementsMap[name] && name !== 'br') {
                return false;
              }
            }
          } else if (isText$a(node) && !isWhitespaceText(node.data)) {
            return false;
          }
          if (start) {
            walker.prev();
          } else {
            walker.next();
          }
        }
        return true;
      };
      const insertNewBlockAfter = () => {
        let block;
        if (/^(H[1-6]|PRE|FIGURE)$/.test(parentBlockName) && containerBlockName !== 'HGROUP') {
          block = createNewBlock$1(newBlockName);
        } else {
          block = createNewBlock$1();
        }
        if (shouldEndContainer(editor, containerBlock) && canSplitBlock(dom, containerBlock) && dom.isEmpty(parentBlock, undefined, { includeZwsp: true })) {
          block = dom.split(containerBlock, parentBlock);
        } else {
          dom.insertAfter(block, parentBlock);
        }
        moveToCaretPosition(editor, block);
        return block;
      };
      normalize$2(dom, rng).each(normRng => {
        rng.setStart(normRng.startContainer, normRng.startOffset);
        rng.setEnd(normRng.endContainer, normRng.endOffset);
      });
      container = rng.startContainer;
      offset = rng.startOffset;
      const shiftKey = !!(evt && evt.shiftKey);
      const ctrlKey = !!(evt && evt.ctrlKey);
      if (isElement$6(container) && container.hasChildNodes() && !collapsedAndCef) {
        isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
        container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
        if (isAfterLastNodeInContainer && isText$a(container)) {
          offset = container.data.length;
        } else {
          offset = 0;
        }
      }
      const editableRoot = getEditableRoot(dom, container);
      if (!editableRoot || isWithinNonEditableList(editor, container)) {
        return;
      }
      if (!shiftKey) {
        container = wrapSelfAndSiblingsInDefaultBlock(editor, newBlockName, rng, container, offset);
      }
      let parentBlock = dom.getParent(container, dom.isBlock) || dom.getRoot();
      containerBlock = isNonNullable(parentBlock === null || parentBlock === void 0 ? void 0 : parentBlock.parentNode) ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
      parentBlockName = parentBlock ? parentBlock.nodeName.toUpperCase() : '';
      const containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : '';
      if (containerBlockName === 'LI' && !ctrlKey) {
        const liBlock = containerBlock;
        parentBlock = liBlock;
        containerBlock = liBlock.parentNode;
        parentBlockName = containerBlockName;
      }
      if (isElement$6(containerBlock) && isLastEmptyBlockInDetails(editor, shiftKey, parentBlock)) {
        return insertNewLine(editor, createNewBlock$1, parentBlock);
      }
      if (/^(LI|DT|DD)$/.test(parentBlockName) && isElement$6(containerBlock)) {
        if (dom.isEmpty(parentBlock)) {
          insert$4(editor, createNewBlock$1, containerBlock, parentBlock, newBlockName);
          return;
        }
      }
      if (!collapsedAndCef && (parentBlock === editor.getBody() || !canSplitBlock(dom, parentBlock))) {
        return;
      }
      const parentBlockParent = parentBlock.parentNode;
      let newBlock;
      if (collapsedAndCef) {
        newBlock = createNewBlock$1(newBlockName);
        child.fold(() => {
          append$1(start, SugarElement.fromDom(newBlock));
        }, child => {
          before$3(child, SugarElement.fromDom(newBlock));
        });
        editor.selection.setCursorLocation(newBlock, 0);
      } else if (isCaretContainerBlock$1(parentBlock)) {
        newBlock = showCaretContainerBlock(parentBlock);
        if (dom.isEmpty(parentBlock)) {
          emptyBlock(parentBlock);
        }
        setForcedBlockAttrs(editor, newBlock);
        moveToCaretPosition(editor, newBlock);
      } else if (isCaretAtStartOrEndOfBlock(false)) {
        newBlock = insertNewBlockAfter();
      } else if (isCaretAtStartOrEndOfBlock(true) && parentBlockParent) {
        newBlock = parentBlockParent.insertBefore(createNewBlock$1(), parentBlock);
        const isNearChildren = hasChildNodes(SugarElement.fromDom(rng.startContainer)) && rng.collapsed;
        moveToCaretPosition(editor, containerAndSiblingName(parentBlock, 'HR') || isNearChildren ? newBlock : parentBlock);
      } else {
        const tmpRng = includeZwspInRange(rng).cloneRange();
        tmpRng.setEndAfter(parentBlock);
        const fragment = tmpRng.extractContents();
        trimZwsp(fragment);
        trimLeadingLineBreaks(fragment);
        newBlock = fragment.firstChild;
        dom.insertAfter(fragment, parentBlock);
        trimInlineElementsOnLeftSideOfBlock(dom, nonEmptyElementsMap, newBlock);
        addBrToBlockIfNeeded(dom, parentBlock);
        if (dom.isEmpty(parentBlock)) {
          emptyBlock(parentBlock);
        }
        newBlock.normalize();
        if (dom.isEmpty(newBlock)) {
          dom.remove(newBlock);
          insertNewBlockAfter();
        } else {
          setForcedBlockAttrs(editor, newBlock);
          moveToCaretPosition(editor, newBlock);
        }
      }
      dom.setAttrib(newBlock, 'id', '');
      editor.dispatch('NewBlock', { newBlock });
    };
    const fakeEventName$1 = 'insertParagraph';
    const blockbreak = {
      insert: insert$3,
      fakeEventName: fakeEventName$1
    };

    const hasRightSideContent = (schema, container, parentBlock) => {
      const walker = new DomTreeWalker(container, parentBlock);
      let node;
      const nonEmptyElementsMap = schema.getNonEmptyElements();
      while (node = walker.next()) {
        if (nonEmptyElementsMap[node.nodeName.toLowerCase()] || isText$a(node) && node.length > 0) {
          return true;
        }
      }
      return false;
    };
    const moveSelectionToBr = (editor, brElm, extraBr) => {
      const rng = editor.dom.createRng();
      if (!extraBr) {
        rng.setStartAfter(brElm);
        rng.setEndAfter(brElm);
      } else {
        rng.setStartBefore(brElm);
        rng.setEndBefore(brElm);
      }
      editor.selection.setRng(rng);
      scrollRangeIntoView(editor, rng);
    };
    const insertBrAtCaret = (editor, evt) => {
      const selection = editor.selection;
      const dom = editor.dom;
      const rng = selection.getRng();
      let brElm;
      let extraBr = false;
      normalize$2(dom, rng).each(normRng => {
        rng.setStart(normRng.startContainer, normRng.startOffset);
        rng.setEnd(normRng.endContainer, normRng.endOffset);
      });
      let offset = rng.startOffset;
      let container = rng.startContainer;
      if (isElement$6(container) && container.hasChildNodes()) {
        const isAfterLastNodeInContainer = offset > container.childNodes.length - 1;
        container = container.childNodes[Math.min(offset, container.childNodes.length - 1)] || container;
        if (isAfterLastNodeInContainer && isText$a(container)) {
          offset = container.data.length;
        } else {
          offset = 0;
        }
      }
      let parentBlock = dom.getParent(container, dom.isBlock);
      const containerBlock = parentBlock && parentBlock.parentNode ? dom.getParent(parentBlock.parentNode, dom.isBlock) : null;
      const containerBlockName = containerBlock ? containerBlock.nodeName.toUpperCase() : '';
      const isControlKey = !!(evt && evt.ctrlKey);
      if (containerBlockName === 'LI' && !isControlKey) {
        parentBlock = containerBlock;
      }
      if (isText$a(container) && offset >= container.data.length) {
        if (!hasRightSideContent(editor.schema, container, parentBlock || dom.getRoot())) {
          brElm = dom.create('br');
          rng.insertNode(brElm);
          rng.setStartAfter(brElm);
          rng.setEndAfter(brElm);
          extraBr = true;
        }
      }
      brElm = dom.create('br');
      rangeInsertNode(dom, rng, brElm);
      moveSelectionToBr(editor, brElm, extraBr);
      editor.undoManager.add();
    };
    const insertBrBefore = (editor, inline) => {
      const br = SugarElement.fromTag('br');
      before$3(SugarElement.fromDom(inline), br);
      editor.undoManager.add();
    };
    const insertBrAfter = (editor, inline) => {
      if (!hasBrAfter(editor.getBody(), inline)) {
        after$4(SugarElement.fromDom(inline), SugarElement.fromTag('br'));
      }
      const br = SugarElement.fromTag('br');
      after$4(SugarElement.fromDom(inline), br);
      moveSelectionToBr(editor, br.dom, false);
      editor.undoManager.add();
    };
    const isBeforeBr = pos => {
      return isBr$6(pos.getNode());
    };
    const hasBrAfter = (rootNode, startNode) => {
      if (isBeforeBr(CaretPosition.after(startNode))) {
        return true;
      } else {
        return nextPosition(rootNode, CaretPosition.after(startNode)).map(pos => {
          return isBr$6(pos.getNode());
        }).getOr(false);
      }
    };
    const isAnchorLink = elm => {
      return elm && elm.nodeName === 'A' && 'href' in elm;
    };
    const isInsideAnchor = location => {
      return location.fold(never, isAnchorLink, isAnchorLink, never);
    };
    const readInlineAnchorLocation = editor => {
      const isInlineTarget$1 = curry(isInlineTarget, editor);
      const position = CaretPosition.fromRangeStart(editor.selection.getRng());
      return readLocation(isInlineTarget$1, editor.getBody(), position).filter(isInsideAnchor);
    };
    const insertBrOutsideAnchor = (editor, location) => {
      location.fold(noop, curry(insertBrBefore, editor), curry(insertBrAfter, editor), noop);
    };
    const insert$2 = (editor, evt) => {
      const anchorLocation = readInlineAnchorLocation(editor);
      if (anchorLocation.isSome()) {
        anchorLocation.each(curry(insertBrOutsideAnchor, editor));
      } else {
        insertBrAtCaret(editor, evt);
      }
    };
    const fakeEventName = 'insertLineBreak';
    const linebreak = {
      insert: insert$2,
      fakeEventName
    };

    const matchesSelector = (editor, selector) => {
      return getParentBlock$1(editor).filter(parentBlock => {
        return selector.length > 0 && is$1(SugarElement.fromDom(parentBlock), selector);
      }).isSome();
    };
    const shouldInsertBr = editor => {
      return matchesSelector(editor, getBrNewLineSelector(editor));
    };
    const shouldBlockNewLine$1 = editor => {
      return matchesSelector(editor, getNoNewLineSelector(editor));
    };

    const newLineAction = Adt.generate([
      { br: [] },
      { block: [] },
      { none: [] }
    ]);
    const shouldBlockNewLine = (editor, _shiftKey) => {
      return shouldBlockNewLine$1(editor);
    };
    const inListBlock = requiredState => {
      return (editor, _shiftKey) => {
        return isListItemParentBlock(editor) === requiredState;
      };
    };
    const inBlock = (blockName, requiredState) => (editor, _shiftKey) => {
      const state = getParentBlockName(editor) === blockName.toUpperCase();
      return state === requiredState;
    };
    const inCefBlock = editor => {
      const editableRoot = getEditableRoot(editor.dom, editor.selection.getStart());
      return isNullable(editableRoot);
    };
    const inPreBlock = requiredState => inBlock('pre', requiredState);
    const inSummaryBlock = () => inBlock('summary', true);
    const shouldPutBrInPre = requiredState => {
      return (editor, _shiftKey) => {
        return shouldPutBrInPre$1(editor) === requiredState;
      };
    };
    const inBrContext = (editor, _shiftKey) => {
      return shouldInsertBr(editor);
    };
    const hasShiftKey = (_editor, shiftKey) => {
      return shiftKey;
    };
    const canInsertIntoEditableRoot = editor => {
      const forcedRootBlock = getForcedRootBlock(editor);
      const rootEditable = getEditableRoot(editor.dom, editor.selection.getStart());
      return isNonNullable(rootEditable) && editor.schema.isValidChild(rootEditable.nodeName, forcedRootBlock);
    };
    const isInRootWithEmptyOrCEF = editor => {
      const rng = editor.selection.getRng();
      const start = SugarElement.fromDom(rng.startContainer);
      const child = child$1(start, rng.startOffset);
      const isCefOpt = child.map(element => isHTMLElement$1(element) && !isEditable$2(element));
      return rng.collapsed && isCefOpt.getOr(true);
    };
    const match = (predicates, action) => {
      return (editor, shiftKey) => {
        const isMatch = foldl(predicates, (res, p) => {
          return res && p(editor, shiftKey);
        }, true);
        return isMatch ? Optional.some(action) : Optional.none();
      };
    };
    const getAction = (editor, evt) => {
      return evaluateUntil([
        match([shouldBlockNewLine], newLineAction.none()),
        match([
          inPreBlock(true),
          inCefBlock
        ], newLineAction.none()),
        match([inSummaryBlock()], newLineAction.br()),
        match([
          inPreBlock(true),
          shouldPutBrInPre(false),
          hasShiftKey
        ], newLineAction.br()),
        match([
          inPreBlock(true),
          shouldPutBrInPre(false)
        ], newLineAction.block()),
        match([
          inPreBlock(true),
          shouldPutBrInPre(true),
          hasShiftKey
        ], newLineAction.block()),
        match([
          inPreBlock(true),
          shouldPutBrInPre(true)
        ], newLineAction.br()),
        match([
          inListBlock(true),
          hasShiftKey
        ], newLineAction.br()),
        match([inListBlock(true)], newLineAction.block()),
        match([inBrContext], newLineAction.br()),
        match([hasShiftKey], newLineAction.br()),
        match([canInsertIntoEditableRoot], newLineAction.block()),
        match([isInRootWithEmptyOrCEF], newLineAction.block())
      ], [
        editor,
        !!(evt && evt.shiftKey)
      ]).getOr(newLineAction.none());
    };

    const insertBreak = (breakType, editor, evt) => {
      if (!editor.selection.isCollapsed()) {
        execEditorDeleteCommand(editor);
      }
      if (isNonNullable(evt)) {
        const event = fireBeforeInputEvent(editor, breakType.fakeEventName);
        if (event.isDefaultPrevented()) {
          return;
        }
      }
      breakType.insert(editor, evt);
      if (isNonNullable(evt)) {
        fireInputEvent(editor, breakType.fakeEventName);
      }
    };
    const insert$1 = (editor, evt) => {
      const br = () => insertBreak(linebreak, editor, evt);
      const block = () => insertBreak(blockbreak, editor, evt);
      const logicalAction = getAction(editor, evt);
      switch (getNewlineBehavior(editor)) {
      case 'linebreak':
        logicalAction.fold(br, br, noop);
        break;
      case 'block':
        logicalAction.fold(block, block, noop);
        break;
      case 'invert':
        logicalAction.fold(block, br, noop);
        break;
      default:
        logicalAction.fold(br, block, noop);
        break;
      }
    };

    const platform$1 = detect$2();
    const isIOSSafari = platform$1.os.isiOS() && platform$1.browser.isSafari();
    const handleEnterKeyEvent = (editor, event) => {
      if (event.isDefaultPrevented()) {
        return;
      }
      event.preventDefault();
      endTypingLevelIgnoreLocks(editor.undoManager);
      editor.undoManager.transact(() => {
        insert$1(editor, event);
      });
    };
    const isCaretAfterKoreanCharacter = rng => {
      if (!rng.collapsed) {
        return false;
      }
      const startContainer = rng.startContainer;
      if (isText$a(startContainer)) {
        const koreanCharRegex = /^[\uAC00-\uD7AF\u1100-\u11FF\u3130-\u318F\uA960-\uA97F\uD7B0-\uD7FF]$/;
        const char = startContainer.data.charAt(rng.startOffset - 1);
        return koreanCharRegex.test(char);
      } else {
        return false;
      }
    };
    const setup$i = editor => {
      let iOSSafariKeydownBookmark = Optional.none();
      const iOSSafariKeydownOverride = editor => {
        iOSSafariKeydownBookmark = Optional.some(editor.selection.getBookmark());
        editor.undoManager.add();
      };
      const iOSSafariKeyupOverride = (editor, event) => {
        editor.undoManager.undo();
        iOSSafariKeydownBookmark.fold(noop, b => editor.selection.moveToBookmark(b));
        handleEnterKeyEvent(editor, event);
        iOSSafariKeydownBookmark = Optional.none();
      };
      editor.on('keydown', event => {
        if (event.keyCode === VK.ENTER) {
          if (isIOSSafari && isCaretAfterKoreanCharacter(editor.selection.getRng())) {
            iOSSafariKeydownOverride(editor);
          } else {
            handleEnterKeyEvent(editor, event);
          }
        }
      });
      editor.on('keyup', event => {
        if (event.keyCode === VK.ENTER) {
          iOSSafariKeydownBookmark.each(() => iOSSafariKeyupOverride(editor, event));
        }
      });
    };

    const executeKeydownOverride$2 = (editor, caret, evt) => {
      const isMac = Env.os.isMacOS() || Env.os.isiOS();
      execute([
        {
          keyCode: VK.END,
          action: action(moveToLineEndPoint$1, editor, true)
        },
        {
          keyCode: VK.HOME,
          action: action(moveToLineEndPoint$1, editor, false)
        },
        ...!isMac ? [
          {
            keyCode: VK.HOME,
            action: action(selectToEndPoint, editor, false),
            ctrlKey: true,
            shiftKey: true
          },
          {
            keyCode: VK.END,
            action: action(selectToEndPoint, editor, true),
            ctrlKey: true,
            shiftKey: true
          }
        ] : [],
        {
          keyCode: VK.END,
          action: action(moveToLineEndPoint, editor, true)
        },
        {
          keyCode: VK.HOME,
          action: action(moveToLineEndPoint, editor, false)
        },
        {
          keyCode: VK.END,
          action: action(moveToLineEndPoint$2, editor, true, caret)
        },
        {
          keyCode: VK.HOME,
          action: action(moveToLineEndPoint$2, editor, false, caret)
        }
      ], evt).each(_ => {
        evt.preventDefault();
      });
    };
    const setup$h = (editor, caret) => {
      editor.on('keydown', evt => {
        if (!evt.isDefaultPrevented()) {
          executeKeydownOverride$2(editor, caret, evt);
        }
      });
    };

    const setup$g = editor => {
      editor.on('input', e => {
        if (!e.isComposing) {
          normalizeNbspsInEditor(editor);
        }
      });
    };

    const platform = detect$2();
    const executeKeyupAction = (editor, caret, evt) => {
      execute([
        {
          keyCode: VK.PAGE_UP,
          action: action(moveToLineEndPoint$2, editor, false, caret)
        },
        {
          keyCode: VK.PAGE_DOWN,
          action: action(moveToLineEndPoint$2, editor, true, caret)
        }
      ], evt);
    };
    const stopImmediatePropagation = e => e.stopImmediatePropagation();
    const isPageUpDown = evt => evt.keyCode === VK.PAGE_UP || evt.keyCode === VK.PAGE_DOWN;
    const setNodeChangeBlocker = (blocked, editor, block) => {
      if (block && !blocked.get()) {
        editor.on('NodeChange', stopImmediatePropagation, true);
      } else if (!block && blocked.get()) {
        editor.off('NodeChange', stopImmediatePropagation);
      }
      blocked.set(block);
    };
    const setup$f = (editor, caret) => {
      if (platform.os.isMacOS()) {
        return;
      }
      const blocked = Cell(false);
      editor.on('keydown', evt => {
        if (isPageUpDown(evt)) {
          setNodeChangeBlocker(blocked, editor, true);
        }
      });
      editor.on('keyup', evt => {
        if (!evt.isDefaultPrevented()) {
          executeKeyupAction(editor, caret, evt);
        }
        if (isPageUpDown(evt) && blocked.get()) {
          setNodeChangeBlocker(blocked, editor, false);
          editor.nodeChanged();
        }
      });
    };

    const setup$e = editor => {
      editor.on('beforeinput', e => {
        if (!editor.selection.isEditable() || exists(e.getTargetRanges(), rng => !isEditableRange(editor.dom, rng))) {
          e.preventDefault();
        }
      });
    };

    const insertTextAtPosition = (text, pos) => {
      const container = pos.container();
      const offset = pos.offset();
      if (isText$a(container)) {
        container.insertData(offset, text);
        return Optional.some(CaretPosition(container, offset + text.length));
      } else {
        return getElementFromPosition(pos).map(elm => {
          const textNode = SugarElement.fromText(text);
          if (pos.isAtEnd()) {
            after$4(elm, textNode);
          } else {
            before$3(elm, textNode);
          }
          return CaretPosition(textNode.dom, text.length);
        });
      }
    };
    const insertNbspAtPosition = curry(insertTextAtPosition, nbsp);
    const insertSpaceAtPosition = curry(insertTextAtPosition, ' ');

    const insertSpaceOrNbspAtPosition = (root, pos, schema) => needsToHaveNbsp(root, pos, schema) ? insertNbspAtPosition(pos) : insertSpaceAtPosition(pos);
    const locationToCaretPosition = root => location => location.fold(element => prevPosition(root.dom, CaretPosition.before(element)), element => firstPositionIn(element), element => lastPositionIn(element), element => nextPosition(root.dom, CaretPosition.after(element)));
    const insertInlineBoundarySpaceOrNbsp = (root, pos, schema) => checkPos => needsToHaveNbsp(root, checkPos, schema) ? insertNbspAtPosition(pos) : insertSpaceAtPosition(pos);
    const setSelection = editor => pos => {
      editor.selection.setRng(pos.toRange());
      editor.nodeChanged();
    };
    const isInsideSummary = (domUtils, node) => domUtils.isEditable(domUtils.getParent(node, 'summary'));
    const insertSpaceOrNbspAtSelection = editor => {
      const pos = CaretPosition.fromRangeStart(editor.selection.getRng());
      const root = SugarElement.fromDom(editor.getBody());
      if (editor.selection.isCollapsed()) {
        const isInlineTarget$1 = curry(isInlineTarget, editor);
        const caretPosition = CaretPosition.fromRangeStart(editor.selection.getRng());
        return readLocation(isInlineTarget$1, editor.getBody(), caretPosition).bind(locationToCaretPosition(root)).map(checkPos => () => insertInlineBoundarySpaceOrNbsp(root, pos, editor.schema)(checkPos).each(setSelection(editor)));
      } else {
        return Optional.none();
      }
    };
    const insertSpaceInSummaryAtSelectionOnFirefox = editor => {
      const insertSpaceThunk = () => {
        const root = SugarElement.fromDom(editor.getBody());
        if (!editor.selection.isCollapsed()) {
          editor.getDoc().execCommand('Delete');
        }
        const pos = CaretPosition.fromRangeStart(editor.selection.getRng());
        insertSpaceOrNbspAtPosition(root, pos, editor.schema).each(setSelection(editor));
      };
      return someIf(Env.browser.isFirefox() && editor.selection.isEditable() && isInsideSummary(editor.dom, editor.selection.getRng().startContainer), insertSpaceThunk);
    };

    const executeKeydownOverride$1 = (editor, evt) => {
      executeWithDelayedAction([
        {
          keyCode: VK.SPACEBAR,
          action: action(insertSpaceOrNbspAtSelection, editor)
        },
        {
          keyCode: VK.SPACEBAR,
          action: action(insertSpaceInSummaryAtSelectionOnFirefox, editor)
        }
      ], evt).each(applyAction => {
        evt.preventDefault();
        const event = fireBeforeInputEvent(editor, 'insertText', { data: ' ' });
        if (!event.isDefaultPrevented()) {
          applyAction();
          fireInputEvent(editor, 'insertText', { data: ' ' });
        }
      });
    };
    const setup$d = editor => {
      editor.on('keydown', evt => {
        if (!evt.isDefaultPrevented()) {
          executeKeydownOverride$1(editor, evt);
        }
      });
    };

    const tableTabNavigation = editor => {
      if (hasTableTabNavigation(editor)) {
        return [
          {
            keyCode: VK.TAB,
            action: action(handleTab, editor, true)
          },
          {
            keyCode: VK.TAB,
            shiftKey: true,
            action: action(handleTab, editor, false)
          }
        ];
      } else {
        return [];
      }
    };
    const executeKeydownOverride = (editor, evt) => {
      execute([...tableTabNavigation(editor)], evt).each(_ => {
        evt.preventDefault();
      });
    };
    const setup$c = editor => {
      editor.on('keydown', evt => {
        if (!evt.isDefaultPrevented()) {
          executeKeydownOverride(editor, evt);
        }
      });
    };

    const setup$b = editor => {
      editor.addShortcut('Meta+P', '', 'mcePrint');
      setup$k(editor);
      if (isRtc(editor)) {
        return Cell(null);
      } else {
        const caret = setupSelectedState(editor);
        setup$e(editor);
        setup$m(editor);
        setup$l(editor, caret);
        setup$j(editor, caret);
        setup$i(editor);
        setup$d(editor);
        setup$g(editor);
        setup$c(editor);
        setup$h(editor, caret);
        setup$f(editor, caret);
        return caret;
      }
    };

    class NodeChange {
      constructor(editor) {
        this.lastPath = [];
        this.editor = editor;
        let lastRng;
        const self = this;
        if (!('onselectionchange' in editor.getDoc())) {
          editor.on('NodeChange click mouseup keyup focus', e => {
            const nativeRng = editor.selection.getRng();
            const fakeRng = {
              startContainer: nativeRng.startContainer,
              startOffset: nativeRng.startOffset,
              endContainer: nativeRng.endContainer,
              endOffset: nativeRng.endOffset
            };
            if (e.type === 'nodechange' || !isEq$4(fakeRng, lastRng)) {
              editor.dispatch('SelectionChange');
            }
            lastRng = fakeRng;
          });
        }
        editor.on('contextmenu', () => {
          editor.dispatch('SelectionChange');
        });
        editor.on('SelectionChange', () => {
          const startElm = editor.selection.getStart(true);
          if (!startElm) {
            return;
          }
          if (hasAnyRanges(editor) && !self.isSameElementPath(startElm) && editor.dom.isChildOf(startElm, editor.getBody())) {
            editor.nodeChanged({ selectionChange: true });
          }
        });
        editor.on('mouseup', e => {
          if (!e.isDefaultPrevented() && hasAnyRanges(editor)) {
            if (editor.selection.getNode().nodeName === 'IMG') {
              Delay.setEditorTimeout(editor, () => {
                editor.nodeChanged();
              });
            } else {
              editor.nodeChanged();
            }
          }
        });
      }
      nodeChanged(args = {}) {
        const selection = this.editor.selection;
        let node;
        if (this.editor.initialized && selection && !shouldDisableNodeChange(this.editor) && !this.editor.mode.isReadOnly()) {
          const root = this.editor.getBody();
          node = selection.getStart(true) || root;
          if (node.ownerDocument !== this.editor.getDoc() || !this.editor.dom.isChildOf(node, root)) {
            node = root;
          }
          const parents = [];
          this.editor.dom.getParent(node, node => {
            if (node === root) {
              return true;
            } else {
              parents.push(node);
              return false;
            }
          });
          this.editor.dispatch('NodeChange', {
            ...args,
            element: node,
            parents
          });
        }
      }
      isSameElementPath(startElm) {
        let i;
        const editor = this.editor;
        const currentPath = reverse(editor.dom.getParents(startElm, always, editor.getBody()));
        if (currentPath.length === this.lastPath.length) {
          for (i = currentPath.length; i >= 0; i--) {
            if (currentPath[i] !== this.lastPath[i]) {
              break;
            }
          }
          if (i === -1) {
            this.lastPath = currentPath;
            return true;
          }
        }
        this.lastPath = currentPath;
        return false;
      }
    }

    const imageId = generate$1('image');
    const getDragImage = transfer => {
      const dt = transfer;
      return Optional.from(dt[imageId]);
    };
    const setDragImage = (transfer, imageData) => {
      const dt = transfer;
      dt[imageId] = imageData;
    };

    const eventId = generate$1('event');
    const getEvent = transfer => {
      const dt = transfer;
      return Optional.from(dt[eventId]);
    };
    const mkSetEventFn = type => transfer => {
      const dt = transfer;
      dt[eventId] = type;
    };
    const setEvent = (transfer, type) => mkSetEventFn(type)(transfer);
    const setDragstartEvent = mkSetEventFn(0);
    const setDropEvent = mkSetEventFn(2);
    const setDragendEvent = mkSetEventFn(1);
    const checkEvent = expectedType => transfer => {
      const dt = transfer;
      return Optional.from(dt[eventId]).exists(type => type === expectedType);
    };
    const isInDragStartEvent = checkEvent(0);

    const createEmptyFileList = () => Object.freeze({
      length: 0,
      item: _ => null
    });

    const modeId = generate$1('mode');
    const getMode = transfer => {
      const dt = transfer;
      return Optional.from(dt[modeId]);
    };
    const mkSetModeFn = mode => transfer => {
      const dt = transfer;
      dt[modeId] = mode;
    };
    const setMode$1 = (transfer, mode) => mkSetModeFn(mode)(transfer);
    const setReadWriteMode = mkSetModeFn(0);
    const setReadOnlyMode = mkSetModeFn(2);
    const setProtectedMode = mkSetModeFn(1);
    const checkMode = expectedMode => transfer => {
      const dt = transfer;
      return Optional.from(dt[modeId]).exists(mode => mode === expectedMode);
    };
    const isInReadWriteMode = checkMode(0);
    const isInProtectedMode = checkMode(1);

    const normalizeItems = (dataTransfer, itemsImpl) => ({
      ...itemsImpl,
      get length() {
        return itemsImpl.length;
      },
      add: (data, type) => {
        if (isInReadWriteMode(dataTransfer)) {
          if (isString(data)) {
            if (!isUndefined(type)) {
              return itemsImpl.add(data, type);
            }
          } else {
            return itemsImpl.add(data);
          }
        }
        return null;
      },
      remove: idx => {
        if (isInReadWriteMode(dataTransfer)) {
          itemsImpl.remove(idx);
        }
      },
      clear: () => {
        if (isInReadWriteMode(dataTransfer)) {
          itemsImpl.clear();
        }
      }
    });

    const validDropEffects = [
      'none',
      'copy',
      'link',
      'move'
    ];
    const validEffectAlloweds = [
      'none',
      'copy',
      'copyLink',
      'copyMove',
      'link',
      'linkMove',
      'move',
      'all',
      'uninitialized'
    ];
    const createDataTransfer = () => {
      const dataTransferImpl = new window.DataTransfer();
      let dropEffect = 'move';
      let effectAllowed = 'all';
      const dataTransfer = {
        get dropEffect() {
          return dropEffect;
        },
        set dropEffect(effect) {
          if (contains$2(validDropEffects, effect)) {
            dropEffect = effect;
          }
        },
        get effectAllowed() {
          return effectAllowed;
        },
        set effectAllowed(allowed) {
          if (isInDragStartEvent(dataTransfer) && contains$2(validEffectAlloweds, allowed)) {
            effectAllowed = allowed;
          }
        },
        get items() {
          return normalizeItems(dataTransfer, dataTransferImpl.items);
        },
        get files() {
          if (isInProtectedMode(dataTransfer)) {
            return createEmptyFileList();
          } else {
            return dataTransferImpl.files;
          }
        },
        get types() {
          return dataTransferImpl.types;
        },
        setDragImage: (image, x, y) => {
          if (isInReadWriteMode(dataTransfer)) {
            setDragImage(dataTransfer, {
              image,
              x,
              y
            });
            dataTransferImpl.setDragImage(image, x, y);
          }
        },
        getData: format => {
          if (isInProtectedMode(dataTransfer)) {
            return '';
          } else {
            return dataTransferImpl.getData(format);
          }
        },
        setData: (format, data) => {
          if (isInReadWriteMode(dataTransfer)) {
            dataTransferImpl.setData(format, data);
          }
        },
        clearData: format => {
          if (isInReadWriteMode(dataTransfer)) {
            dataTransferImpl.clearData(format);
          }
        }
      };
      setReadWriteMode(dataTransfer);
      return dataTransfer;
    };
    const cloneDataTransfer = original => {
      const clone = createDataTransfer();
      const originalMode = getMode(original);
      setReadOnlyMode(original);
      setDragstartEvent(clone);
      clone.dropEffect = original.dropEffect;
      clone.effectAllowed = original.effectAllowed;
      getDragImage(original).each(imageData => clone.setDragImage(imageData.image, imageData.x, imageData.y));
      each$e(original.types, type => {
        if (type !== 'Files') {
          clone.setData(type, original.getData(type));
        }
      });
      each$e(original.files, file => clone.items.add(file));
      getEvent(original).each(type => {
        setEvent(clone, type);
      });
      originalMode.each(mode => {
        setMode$1(original, mode);
        setMode$1(clone, mode);
      });
      return clone;
    };

    const getHtmlData = dataTransfer => {
      const html = dataTransfer.getData('text/html');
      return html === '' ? Optional.none() : Optional.some(html);
    };
    const setHtmlData = (dataTransfer, html) => dataTransfer.setData('text/html', html);

    const internalMimeType = 'x-tinymce/html';
    const internalHtmlMime = constant(internalMimeType);
    const internalMark = '<!-- ' + internalMimeType + ' -->';
    const mark = html => internalMark + html;
    const unmark = html => html.replace(internalMark, '');
    const isMarked = html => html.indexOf(internalMark) !== -1;

    const isPlainText = text => {
      return !/<(?:\/?(?!(?:div|p|br|span)>)\w+|(?:(?!(?:span style="white-space:\s?pre;?">)|br\s?\/>))\w+\s[^>]+)>/i.test(text);
    };
    const openContainer = (rootTag, rootAttrs) => {
      let tag = '<' + rootTag;
      const attrs = mapToArray(rootAttrs, (value, key) => key + '="' + Entities.encodeAllRaw(value) + '"');
      if (attrs.length) {
        tag += ' ' + attrs.join(' ');
      }
      return tag + '>';
    };
    const toBlockElements = (text, rootTag, rootAttrs) => {
      const blocks = text.split(/\n\n/);
      const tagOpen = openContainer(rootTag, rootAttrs);
      const tagClose = '</' + rootTag + '>';
      const paragraphs = map$3(blocks, p => {
        return p.split(/\n/).join('<br />');
      });
      const stitch = p => {
        return tagOpen + p + tagClose;
      };
      return paragraphs.length === 1 ? paragraphs[0] : map$3(paragraphs, stitch).join('');
    };

    const pasteBinDefaultContent = '%MCEPASTEBIN%';
    const create$6 = (editor, lastRngCell) => {
      const {dom, selection} = editor;
      const body = editor.getBody();
      lastRngCell.set(selection.getRng());
      const pasteBinElm = dom.add(editor.getBody(), 'div', {
        'id': 'mcepastebin',
        'class': 'mce-pastebin',
        'contentEditable': true,
        'data-mce-bogus': 'all',
        'style': 'position: fixed; top: 50%; width: 10px; height: 10px; overflow: hidden; opacity: 0'
      }, pasteBinDefaultContent);
      if (Env.browser.isFirefox()) {
        dom.setStyle(pasteBinElm, 'left', dom.getStyle(body, 'direction', true) === 'rtl' ? 65535 : -65535);
      }
      dom.bind(pasteBinElm, 'beforedeactivate focusin focusout', e => {
        e.stopPropagation();
      });
      pasteBinElm.focus();
      selection.select(pasteBinElm, true);
    };
    const remove = (editor, lastRngCell) => {
      const dom = editor.dom;
      if (getEl(editor)) {
        let pasteBinClone;
        const lastRng = lastRngCell.get();
        while (pasteBinClone = getEl(editor)) {
          dom.remove(pasteBinClone);
          dom.unbind(pasteBinClone);
        }
        if (lastRng) {
          editor.selection.setRng(lastRng);
        }
      }
      lastRngCell.set(null);
    };
    const getEl = editor => editor.dom.get('mcepastebin');
    const isPasteBin = elm => isNonNullable(elm) && elm.id === 'mcepastebin';
    const getHtml = editor => {
      const dom = editor.dom;
      const copyAndRemove = (toElm, fromElm) => {
        toElm.appendChild(fromElm);
        dom.remove(fromElm, true);
      };
      const [pasteBinElm, ...pasteBinClones] = filter$5(editor.getBody().childNodes, isPasteBin);
      each$e(pasteBinClones, pasteBinClone => {
        copyAndRemove(pasteBinElm, pasteBinClone);
      });
      const dirtyWrappers = dom.select('div[id=mcepastebin]', pasteBinElm);
      for (let i = dirtyWrappers.length - 1; i >= 0; i--) {
        const cleanWrapper = dom.create('div');
        pasteBinElm.insertBefore(cleanWrapper, dirtyWrappers[i]);
        copyAndRemove(cleanWrapper, dirtyWrappers[i]);
      }
      return pasteBinElm ? pasteBinElm.innerHTML : '';
    };
    const isDefaultPasteBinContent = content => content === pasteBinDefaultContent;
    const PasteBin = editor => {
      const lastRng = Cell(null);
      return {
        create: () => create$6(editor, lastRng),
        remove: () => remove(editor, lastRng),
        getEl: () => getEl(editor),
        getHtml: () => getHtml(editor),
        getLastRng: lastRng.get
      };
    };

    const filter$1 = (content, items) => {
      Tools.each(items, v => {
        if (is$4(v, RegExp)) {
          content = content.replace(v, '');
        } else {
          content = content.replace(v[0], v[1]);
        }
      });
      return content;
    };
    const innerText = html => {
      const schema = Schema();
      const domParser = DomParser({}, schema);
      let text = '';
      const voidElements = schema.getVoidElements();
      const ignoreElements = Tools.makeMap('script noscript style textarea video audio iframe object', ' ');
      const blockElements = schema.getBlockElements();
      const walk = node => {
        const name = node.name, currentNode = node;
        if (name === 'br') {
          text += '\n';
          return;
        }
        if (name === 'wbr') {
          return;
        }
        if (voidElements[name]) {
          text += ' ';
        }
        if (ignoreElements[name]) {
          text += ' ';
          return;
        }
        if (node.type === 3) {
          text += node.value;
        }
        if (!(node.name in schema.getVoidElements())) {
          let currentNode = node.firstChild;
          if (currentNode) {
            do {
              walk(currentNode);
            } while (currentNode = currentNode.next);
          }
        }
        if (blockElements[name] && currentNode.next) {
          text += '\n';
          if (name === 'p') {
            text += '\n';
          }
        }
      };
      html = filter$1(html, [/<!\[[^\]]+\]>/g]);
      walk(domParser.parse(html));
      return text;
    };
    const trimHtml = html => {
      const trimSpaces = (all, s1, s2) => {
        if (!s1 && !s2) {
          return ' ';
        }
        return nbsp;
      };
      html = filter$1(html, [
        /^[\s\S]*<body[^>]*>\s*|\s*<\/body[^>]*>[\s\S]*$/ig,
        /<!--StartFragment-->|<!--EndFragment-->/g,
        [
          /( ?)<span class="Apple-converted-space">\u00a0<\/span>( ?)/g,
          trimSpaces
        ],
        /<br class="Apple-interchange-newline">/g,
        /<br>$/i
      ]);
      return html;
    };
    const createIdGenerator = prefix => {
      let count = 0;
      return () => {
        return prefix + count++;
      };
    };
    const getImageMimeType = ext => {
      const lowerExt = ext.toLowerCase();
      const mimeOverrides = {
        jpg: 'jpeg',
        jpe: 'jpeg',
        jfi: 'jpeg',
        jif: 'jpeg',
        jfif: 'jpeg',
        pjpeg: 'jpeg',
        pjp: 'jpeg',
        svg: 'svg+xml'
      };
      return Tools.hasOwn(mimeOverrides, lowerExt) ? 'image/' + mimeOverrides[lowerExt] : 'image/' + lowerExt;
    };

    const preProcess = (editor, html) => {
      const parser = DomParser({
        sanitize: shouldSanitizeXss(editor),
        sandbox_iframes: shouldSandboxIframes(editor)
      }, editor.schema);
      parser.addNodeFilter('meta', nodes => {
        Tools.each(nodes, node => {
          node.remove();
        });
      });
      const fragment = parser.parse(html, {
        forced_root_block: false,
        isRootContent: true
      });
      return HtmlSerializer({ validate: true }, editor.schema).serialize(fragment);
    };
    const processResult = (content, cancelled) => ({
      content,
      cancelled
    });
    const postProcessFilter = (editor, html, internal) => {
      const tempBody = editor.dom.create('div', { style: 'display:none' }, html);
      const postProcessArgs = firePastePostProcess(editor, tempBody, internal);
      return processResult(postProcessArgs.node.innerHTML, postProcessArgs.isDefaultPrevented());
    };
    const filterContent = (editor, content, internal) => {
      const preProcessArgs = firePastePreProcess(editor, content, internal);
      const filteredContent = preProcess(editor, preProcessArgs.content);
      if (editor.hasEventListeners('PastePostProcess') && !preProcessArgs.isDefaultPrevented()) {
        return postProcessFilter(editor, filteredContent, internal);
      } else {
        return processResult(filteredContent, preProcessArgs.isDefaultPrevented());
      }
    };
    const process = (editor, html, internal) => {
      return filterContent(editor, html, internal);
    };

    const pasteHtml$1 = (editor, html) => {
      editor.insertContent(html, {
        merge: shouldPasteMergeFormats(editor),
        paste: true
      });
      return true;
    };
    const isAbsoluteUrl = url => /^https?:\/\/[\w\-\/+=.,!;:&%@^~(){}?#]+$/i.test(url);
    const isImageUrl = (editor, url) => {
      return isAbsoluteUrl(url) && exists(getAllowedImageFileTypes(editor), type => endsWith(url.toLowerCase(), `.${ type.toLowerCase() }`));
    };
    const createImage = (editor, url, pasteHtmlFn) => {
      editor.undoManager.extra(() => {
        pasteHtmlFn(editor, url);
      }, () => {
        editor.insertContent('<img src="' + url + '">');
      });
      return true;
    };
    const createLink = (editor, url, pasteHtmlFn) => {
      editor.undoManager.extra(() => {
        pasteHtmlFn(editor, url);
      }, () => {
        editor.execCommand('mceInsertLink', false, url);
      });
      return true;
    };
    const linkSelection = (editor, html, pasteHtmlFn) => !editor.selection.isCollapsed() && isAbsoluteUrl(html) ? createLink(editor, html, pasteHtmlFn) : false;
    const insertImage = (editor, html, pasteHtmlFn) => isImageUrl(editor, html) ? createImage(editor, html, pasteHtmlFn) : false;
    const smartInsertContent = (editor, html) => {
      Tools.each([
        linkSelection,
        insertImage,
        pasteHtml$1
      ], action => {
        return !action(editor, html, pasteHtml$1);
      });
    };
    const insertContent = (editor, html, pasteAsText) => {
      if (pasteAsText || !isSmartPasteEnabled(editor)) {
        pasteHtml$1(editor, html);
      } else {
        smartInsertContent(editor, html);
      }
    };

    const uniqueId = createIdGenerator('mceclip');
    const createPasteDataTransfer = html => {
      const dataTransfer = createDataTransfer();
      setHtmlData(dataTransfer, html);
      setReadOnlyMode(dataTransfer);
      return dataTransfer;
    };
    const doPaste = (editor, content, internal, pasteAsText, shouldSimulateInputEvent) => {
      const res = process(editor, content, internal);
      if (!res.cancelled) {
        const content = res.content;
        const doPasteAction = () => insertContent(editor, content, pasteAsText);
        if (shouldSimulateInputEvent) {
          const args = fireBeforeInputEvent(editor, 'insertFromPaste', { dataTransfer: createPasteDataTransfer(content) });
          if (!args.isDefaultPrevented()) {
            doPasteAction();
            fireInputEvent(editor, 'insertFromPaste');
          }
        } else {
          doPasteAction();
        }
      }
    };
    const pasteHtml = (editor, html, internalFlag, shouldSimulateInputEvent) => {
      const internal = internalFlag ? internalFlag : isMarked(html);
      doPaste(editor, unmark(html), internal, false, shouldSimulateInputEvent);
    };
    const pasteText = (editor, text, shouldSimulateInputEvent) => {
      const encodedText = editor.dom.encode(text).replace(/\r\n/g, '\n');
      const normalizedText = normalize$4(encodedText, getPasteTabSpaces(editor));
      const html = toBlockElements(normalizedText, getForcedRootBlock(editor), getForcedRootBlockAttrs(editor));
      doPaste(editor, html, false, true, shouldSimulateInputEvent);
    };
    const getDataTransferItems = dataTransfer => {
      const items = {};
      if (dataTransfer && dataTransfer.types) {
        for (let i = 0; i < dataTransfer.types.length; i++) {
          const contentType = dataTransfer.types[i];
          try {
            items[contentType] = dataTransfer.getData(contentType);
          } catch (ex) {
            items[contentType] = '';
          }
        }
      }
      return items;
    };
    const hasContentType = (clipboardContent, mimeType) => mimeType in clipboardContent && clipboardContent[mimeType].length > 0;
    const hasHtmlOrText = content => hasContentType(content, 'text/html') || hasContentType(content, 'text/plain');
    const extractFilename = (editor, str) => {
      const m = str.match(/([\s\S]+?)(?:\.[a-z0-9.]+)$/i);
      return isNonNullable(m) ? editor.dom.encode(m[1]) : undefined;
    };
    const createBlobInfo = (editor, blobCache, file, base64) => {
      const id = uniqueId();
      const useFileName = shouldReuseFileName(editor) && isNonNullable(file.name);
      const name = useFileName ? extractFilename(editor, file.name) : id;
      const filename = useFileName ? file.name : undefined;
      const blobInfo = blobCache.create(id, file, base64, name, filename);
      blobCache.add(blobInfo);
      return blobInfo;
    };
    const pasteImage = (editor, imageItem) => {
      parseDataUri(imageItem.uri).each(({data, type, base64Encoded}) => {
        const base64 = base64Encoded ? data : btoa(data);
        const file = imageItem.file;
        const blobCache = editor.editorUpload.blobCache;
        const existingBlobInfo = blobCache.getByData(base64, type);
        const blobInfo = existingBlobInfo !== null && existingBlobInfo !== void 0 ? existingBlobInfo : createBlobInfo(editor, blobCache, file, base64);
        pasteHtml(editor, `<img src="${ blobInfo.blobUri() }">`, false, true);
      });
    };
    const isClipboardEvent = event => event.type === 'paste';
    const readFilesAsDataUris = items => Promise.all(map$3(items, file => {
      return blobToDataUri(file).then(uri => ({
        file,
        uri
      }));
    }));
    const isImage = editor => {
      const allowedExtensions = getAllowedImageFileTypes(editor);
      return file => startsWith(file.type, 'image/') && exists(allowedExtensions, extension => {
        return getImageMimeType(extension) === file.type;
      });
    };
    const getImagesFromDataTransfer = (editor, dataTransfer) => {
      const items = dataTransfer.items ? bind$3(from(dataTransfer.items), item => {
        return item.kind === 'file' ? [item.getAsFile()] : [];
      }) : [];
      const files = dataTransfer.files ? from(dataTransfer.files) : [];
      return filter$5(items.length > 0 ? items : files, isImage(editor));
    };
    const pasteImageData = (editor, e, rng) => {
      const dataTransfer = isClipboardEvent(e) ? e.clipboardData : e.dataTransfer;
      if (shouldPasteDataImages(editor) && dataTransfer) {
        const images = getImagesFromDataTransfer(editor, dataTransfer);
        if (images.length > 0) {
          e.preventDefault();
          readFilesAsDataUris(images).then(fileResults => {
            if (rng) {
              editor.selection.setRng(rng);
            }
            each$e(fileResults, result => {
              pasteImage(editor, result);
            });
          });
          return true;
        }
      }
      return false;
    };
    const isBrokenAndroidClipboardEvent = e => {
      var _a, _b;
      return Env.os.isAndroid() && ((_b = (_a = e.clipboardData) === null || _a === void 0 ? void 0 : _a.items) === null || _b === void 0 ? void 0 : _b.length) === 0;
    };
    const isKeyboardPasteEvent = e => VK.metaKeyPressed(e) && e.keyCode === 86 || e.shiftKey && e.keyCode === 45;
    const insertClipboardContent = (editor, clipboardContent, html, plainTextMode, shouldSimulateInputEvent) => {
      let content = trimHtml(html);
      const isInternal = hasContentType(clipboardContent, internalHtmlMime()) || isMarked(html);
      const isPlainTextHtml = !isInternal && isPlainText(content);
      const isAbsoluteUrl$1 = isAbsoluteUrl(content);
      if (isDefaultPasteBinContent(content) || !content.length || isPlainTextHtml && !isAbsoluteUrl$1) {
        plainTextMode = true;
      }
      if (plainTextMode || isAbsoluteUrl$1) {
        if (hasContentType(clipboardContent, 'text/plain') && isPlainTextHtml) {
          content = clipboardContent['text/plain'];
        } else {
          content = innerText(content);
        }
      }
      if (isDefaultPasteBinContent(content)) {
        return;
      }
      if (plainTextMode) {
        pasteText(editor, content, shouldSimulateInputEvent);
      } else {
        pasteHtml(editor, content, isInternal, shouldSimulateInputEvent);
      }
    };
    const registerEventHandlers = (editor, pasteBin, pasteFormat) => {
      let keyboardPastePlainTextState;
      const getLastRng = () => pasteBin.getLastRng() || editor.selection.getRng();
      editor.on('keydown', e => {
        if (isKeyboardPasteEvent(e) && !e.isDefaultPrevented()) {
          keyboardPastePlainTextState = e.shiftKey && e.keyCode === 86;
        }
      });
      editor.on('paste', e => {
        if (e.isDefaultPrevented() || isBrokenAndroidClipboardEvent(e)) {
          return;
        }
        const plainTextMode = pasteFormat.get() === 'text' || keyboardPastePlainTextState;
        keyboardPastePlainTextState = false;
        const clipboardContent = getDataTransferItems(e.clipboardData);
        if (!hasHtmlOrText(clipboardContent) && pasteImageData(editor, e, getLastRng())) {
          return;
        }
        if (hasContentType(clipboardContent, 'text/html')) {
          e.preventDefault();
          insertClipboardContent(editor, clipboardContent, clipboardContent['text/html'], plainTextMode, true);
        } else if (hasContentType(clipboardContent, 'text/plain') && hasContentType(clipboardContent, 'text/uri-list')) {
          e.preventDefault();
          insertClipboardContent(editor, clipboardContent, clipboardContent['text/plain'], plainTextMode, true);
        } else {
          pasteBin.create();
          Delay.setEditorTimeout(editor, () => {
            const html = pasteBin.getHtml();
            pasteBin.remove();
            insertClipboardContent(editor, clipboardContent, html, plainTextMode, false);
          }, 0);
        }
      });
    };
    const registerDataImageFilter = editor => {
      const isWebKitFakeUrl = src => startsWith(src, 'webkit-fake-url');
      const isDataUri = src => startsWith(src, 'data:');
      const isPasteInsert = args => {
        var _a;
        return ((_a = args.data) === null || _a === void 0 ? void 0 : _a.paste) === true;
      };
      editor.parser.addNodeFilter('img', (nodes, name, args) => {
        if (!shouldPasteDataImages(editor) && isPasteInsert(args)) {
          for (const node of nodes) {
            const src = node.attr('src');
            if (isString(src) && !node.attr('data-mce-object') && src !== Env.transparentSrc) {
              if (isWebKitFakeUrl(src)) {
                node.remove();
              } else if (!shouldAllowHtmlDataUrls(editor) && isDataUri(src)) {
                node.remove();
              }
            }
          }
        }
      });
    };
    const registerEventsAndFilters = (editor, pasteBin, pasteFormat) => {
      registerEventHandlers(editor, pasteBin, pasteFormat);
      registerDataImageFilter(editor);
    };

    const togglePlainTextPaste = (editor, pasteFormat) => {
      if (pasteFormat.get() === 'text') {
        pasteFormat.set('html');
        firePastePlainTextToggle(editor, false);
      } else {
        pasteFormat.set('text');
        firePastePlainTextToggle(editor, true);
      }
      editor.focus();
    };
    const register$1 = (editor, pasteFormat) => {
      editor.addCommand('mceTogglePlainTextPaste', () => {
        togglePlainTextPaste(editor, pasteFormat);
      });
      editor.addCommand('mceInsertClipboardContent', (ui, value) => {
        if (value.html) {
          pasteHtml(editor, value.html, value.internal, false);
        }
        if (value.text) {
          pasteText(editor, value.text, false);
        }
      });
    };

    const setHtml5Clipboard = (clipboardData, html, text) => {
      if (clipboardData) {
        try {
          clipboardData.clearData();
          clipboardData.setData('text/html', html);
          clipboardData.setData('text/plain', text);
          clipboardData.setData(internalHtmlMime(), html);
          return true;
        } catch (e) {
          return false;
        }
      } else {
        return false;
      }
    };
    const setClipboardData = (evt, data, fallback, done) => {
      if (setHtml5Clipboard(evt.clipboardData, data.html, data.text)) {
        evt.preventDefault();
        done();
      } else {
        fallback(data.html, done);
      }
    };
    const fallback = editor => (html, done) => {
      const {dom, selection} = editor;
      const outer = dom.create('div', {
        'contenteditable': 'false',
        'data-mce-bogus': 'all'
      });
      const inner = dom.create('div', { contenteditable: 'true' }, html);
      dom.setStyles(outer, {
        position: 'fixed',
        top: '0',
        left: '-3000px',
        width: '1000px',
        overflow: 'hidden'
      });
      outer.appendChild(inner);
      dom.add(editor.getBody(), outer);
      const range = selection.getRng();
      inner.focus();
      const offscreenRange = dom.createRng();
      offscreenRange.selectNodeContents(inner);
      selection.setRng(offscreenRange);
      Delay.setEditorTimeout(editor, () => {
        selection.setRng(range);
        dom.remove(outer);
        done();
      }, 0);
    };
    const getData = editor => ({
      html: mark(editor.selection.getContent({ contextual: true })),
      text: editor.selection.getContent({ format: 'text' })
    });
    const isTableSelection = editor => !!editor.dom.getParent(editor.selection.getStart(), 'td[data-mce-selected],th[data-mce-selected]', editor.getBody());
    const hasSelectedContent = editor => !editor.selection.isCollapsed() || isTableSelection(editor);
    const cut = editor => evt => {
      if (!evt.isDefaultPrevented() && hasSelectedContent(editor) && editor.selection.isEditable()) {
        setClipboardData(evt, getData(editor), fallback(editor), () => {
          if (Env.browser.isChromium() || Env.browser.isFirefox()) {
            const rng = editor.selection.getRng();
            Delay.setEditorTimeout(editor, () => {
              editor.selection.setRng(rng);
              editor.execCommand('Delete');
            }, 0);
          } else {
            editor.execCommand('Delete');
          }
        });
      }
    };
    const copy = editor => evt => {
      if (!evt.isDefaultPrevented() && hasSelectedContent(editor)) {
        setClipboardData(evt, getData(editor), fallback(editor), noop);
      }
    };
    const register = editor => {
      editor.on('cut', cut(editor));
      editor.on('copy', copy(editor));
    };

    const getCaretRangeFromEvent = (editor, e) => {
      var _a, _b;
      return RangeUtils.getCaretRangeFromPoint((_a = e.clientX) !== null && _a !== void 0 ? _a : 0, (_b = e.clientY) !== null && _b !== void 0 ? _b : 0, editor.getDoc());
    };
    const isPlainTextFileUrl = content => {
      const plainTextContent = content['text/plain'];
      return plainTextContent ? plainTextContent.indexOf('file://') === 0 : false;
    };
    const setFocusedRange = (editor, rng) => {
      editor.focus();
      if (rng) {
        editor.selection.setRng(rng);
      }
    };
    const hasImage = dataTransfer => exists(dataTransfer.files, file => /^image\//.test(file.type));
    const needsCustomInternalDrop = (dom, schema, target, dropContent) => {
      const parentTransparent = dom.getParent(target, node => isTransparentBlock(schema, node));
      const inSummary = !isNull(dom.getParent(target, 'summary'));
      if (inSummary) {
        return true;
      } else if (parentTransparent && has$2(dropContent, 'text/html')) {
        const fragment = new DOMParser().parseFromString(dropContent['text/html'], 'text/html').body;
        return !isNull(fragment.querySelector(parentTransparent.nodeName.toLowerCase()));
      } else {
        return false;
      }
    };
    const setupSummaryDeleteByDragFix = editor => {
      editor.on('input', e => {
        const hasNoSummary = el => isNull(el.querySelector('summary'));
        if (e.inputType === 'deleteByDrag') {
          const brokenDetailElements = filter$5(editor.dom.select('details'), hasNoSummary);
          each$e(brokenDetailElements, details => {
            if (isBr$6(details.firstChild)) {
              details.firstChild.remove();
            }
            const summary = editor.dom.create('summary');
            summary.appendChild(createPaddingBr().dom);
            details.prepend(summary);
          });
        }
      });
    };
    const setup$a = (editor, draggingInternallyState) => {
      if (shouldPasteBlockDrop(editor)) {
        editor.on('dragend dragover draggesture dragdrop drop drag', e => {
          e.preventDefault();
          e.stopPropagation();
        });
      }
      if (!shouldPasteDataImages(editor)) {
        editor.on('drop', e => {
          const dataTransfer = e.dataTransfer;
          if (dataTransfer && hasImage(dataTransfer)) {
            e.preventDefault();
          }
        });
      }
      editor.on('drop', e => {
        if (e.isDefaultPrevented()) {
          return;
        }
        const rng = getCaretRangeFromEvent(editor, e);
        if (isNullable(rng)) {
          return;
        }
        const dropContent = getDataTransferItems(e.dataTransfer);
        const internal = hasContentType(dropContent, internalHtmlMime());
        if ((!hasHtmlOrText(dropContent) || isPlainTextFileUrl(dropContent)) && pasteImageData(editor, e, rng)) {
          return;
        }
        const internalContent = dropContent[internalHtmlMime()];
        const content = internalContent || dropContent['text/html'] || dropContent['text/plain'];
        const needsInternalDrop = needsCustomInternalDrop(editor.dom, editor.schema, rng.startContainer, dropContent);
        const isInternalDrop = draggingInternallyState.get();
        if (isInternalDrop && !needsInternalDrop) {
          return;
        }
        if (content) {
          e.preventDefault();
          Delay.setEditorTimeout(editor, () => {
            editor.undoManager.transact(() => {
              if (internalContent || isInternalDrop && needsInternalDrop) {
                editor.execCommand('Delete');
              }
              setFocusedRange(editor, rng);
              const trimmedContent = trimHtml(content);
              if (dropContent['text/html']) {
                pasteHtml(editor, trimmedContent, internal, true);
              } else {
                pasteText(editor, trimmedContent, true);
              }
            });
          });
        }
      });
      editor.on('dragstart', _e => {
        draggingInternallyState.set(true);
      });
      editor.on('dragover dragend', e => {
        if (shouldPasteDataImages(editor) && !draggingInternallyState.get()) {
          e.preventDefault();
          setFocusedRange(editor, getCaretRangeFromEvent(editor, e));
        }
        if (e.type === 'dragend') {
          draggingInternallyState.set(false);
        }
      });
      setupSummaryDeleteByDragFix(editor);
    };

    const setup$9 = editor => {
      const processEvent = f => e => {
        f(editor, e);
      };
      const preProcess = getPastePreProcess(editor);
      if (isFunction(preProcess)) {
        editor.on('PastePreProcess', processEvent(preProcess));
      }
      const postProcess = getPastePostProcess(editor);
      if (isFunction(postProcess)) {
        editor.on('PastePostProcess', processEvent(postProcess));
      }
    };

    const addPreProcessFilter = (editor, filterFunc) => {
      editor.on('PastePreProcess', e => {
        e.content = filterFunc(editor, e.content, e.internal);
      });
    };
    const rgbRegExp = /rgb\s*\(\s*([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\s*\)/gi;
    const rgbToHex = value => Tools.trim(value).replace(rgbRegExp, rgbaToHexString).toLowerCase();
    const removeWebKitStyles = (editor, content, internal) => {
      const webKitStylesOption = getPasteWebkitStyles(editor);
      if (internal || webKitStylesOption === 'all' || !shouldPasteRemoveWebKitStyles(editor)) {
        return content;
      }
      const webKitStyles = webKitStylesOption ? webKitStylesOption.split(/[, ]/) : [];
      if (webKitStyles && webKitStylesOption !== 'none') {
        const dom = editor.dom, node = editor.selection.getNode();
        content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, (all, before, value, after) => {
          const inputStyles = dom.parseStyle(dom.decode(value));
          const outputStyles = {};
          for (let i = 0; i < webKitStyles.length; i++) {
            const inputValue = inputStyles[webKitStyles[i]];
            let compareInput = inputValue;
            let currentValue = dom.getStyle(node, webKitStyles[i], true);
            if (/color/.test(webKitStyles[i])) {
              compareInput = rgbToHex(compareInput);
              currentValue = rgbToHex(currentValue);
            }
            if (currentValue !== compareInput) {
              outputStyles[webKitStyles[i]] = inputValue;
            }
          }
          const outputStyle = dom.serializeStyle(outputStyles, 'span');
          if (outputStyle) {
            return before + ' style="' + outputStyle + '"' + after;
          }
          return before + after;
        });
      } else {
        content = content.replace(/(<[^>]+) style="([^"]*)"([^>]*>)/gi, '$1$3');
      }
      content = content.replace(/(<[^>]+) data-mce-style="([^"]+)"([^>]*>)/gi, (all, before, value, after) => {
        return before + ' style="' + value + '"' + after;
      });
      return content;
    };
    const setup$8 = editor => {
      if (Env.browser.isChromium() || Env.browser.isSafari()) {
        addPreProcessFilter(editor, removeWebKitStyles);
      }
    };

    const setup$7 = editor => {
      const draggingInternallyState = Cell(false);
      const pasteFormat = Cell(isPasteAsTextEnabled(editor) ? 'text' : 'html');
      const pasteBin = PasteBin(editor);
      setup$8(editor);
      register$1(editor, pasteFormat);
      setup$9(editor);
      editor.on('PreInit', () => {
        register(editor);
        setup$a(editor, draggingInternallyState);
        registerEventsAndFilters(editor, pasteBin, pasteFormat);
      });
    };

    const preventSummaryToggle = editor => {
      editor.on('click', e => {
        if (editor.dom.getParent(e.target, 'details')) {
          e.preventDefault();
        }
      });
    };
    const filterDetails = editor => {
      editor.parser.addNodeFilter('details', elms => {
        const initialStateOption = getDetailsInitialState(editor);
        each$e(elms, details => {
          if (initialStateOption === 'expanded') {
            details.attr('open', 'open');
          } else if (initialStateOption === 'collapsed') {
            details.attr('open', null);
          }
        });
      });
      editor.serializer.addNodeFilter('details', elms => {
        const serializedStateOption = getDetailsSerializedState(editor);
        each$e(elms, details => {
          if (serializedStateOption === 'expanded') {
            details.attr('open', 'open');
          } else if (serializedStateOption === 'collapsed') {
            details.attr('open', null);
          }
        });
      });
    };
    const setup$6 = editor => {
      preventSummaryToggle(editor);
      filterDetails(editor);
    };

    const isBr = isBr$6;
    const isText = isText$a;
    const isContentEditableFalse$2 = elm => isContentEditableFalse$b(elm.dom);
    const isContentEditableTrue = elm => isContentEditableTrue$3(elm.dom);
    const isRoot = rootNode => elm => eq(SugarElement.fromDom(rootNode), elm);
    const getClosestScope = (node, rootNode, schema) => closest$4(SugarElement.fromDom(node), elm => isContentEditableTrue(elm) || schema.isBlock(name(elm)), isRoot(rootNode)).getOr(SugarElement.fromDom(rootNode)).dom;
    const getClosestCef = (node, rootNode) => closest$4(SugarElement.fromDom(node), isContentEditableFalse$2, isRoot(rootNode));
    const findEdgeCaretCandidate = (startNode, scope, forward) => {
      const walker = new DomTreeWalker(startNode, scope);
      const next = forward ? walker.next.bind(walker) : walker.prev.bind(walker);
      let result = startNode;
      for (let current = forward ? startNode : next(); current && !isBr(current); current = next()) {
        if (isCaretCandidate$3(current)) {
          result = current;
        }
      }
      return result;
    };
    const findClosestBlockRange = (startRng, rootNode, schema) => {
      const startPos = CaretPosition.fromRangeStart(startRng);
      const clickNode = startPos.getNode();
      const scope = getClosestScope(clickNode, rootNode, schema);
      const startNode = findEdgeCaretCandidate(clickNode, scope, false);
      const endNode = findEdgeCaretCandidate(clickNode, scope, true);
      const rng = document.createRange();
      getClosestCef(startNode, scope).fold(() => {
        if (isText(startNode)) {
          rng.setStart(startNode, 0);
        } else {
          rng.setStartBefore(startNode);
        }
      }, cef => rng.setStartBefore(cef.dom));
      getClosestCef(endNode, scope).fold(() => {
        if (isText(endNode)) {
          rng.setEnd(endNode, endNode.data.length);
        } else {
          rng.setEndAfter(endNode);
        }
      }, cef => rng.setEndAfter(cef.dom));
      return rng;
    };
    const onTripleClickSelect = editor => {
      const rng = findClosestBlockRange(editor.selection.getRng(), editor.getBody(), editor.schema);
      editor.selection.setRng(normalize(rng));
    };
    const setup$5 = editor => {
      editor.on('mousedown', e => {
        if (e.detail >= 3) {
          e.preventDefault();
          onTripleClickSelect(editor);
        }
      });
    };

    var FakeCaretPosition;
    (function (FakeCaretPosition) {
      FakeCaretPosition['Before'] = 'before';
      FakeCaretPosition['After'] = 'after';
    }(FakeCaretPosition || (FakeCaretPosition = {})));
    const distanceToRectLeft = (clientRect, clientX) => Math.abs(clientRect.left - clientX);
    const distanceToRectRight = (clientRect, clientX) => Math.abs(clientRect.right - clientX);
    const isInsideY = (clientY, clientRect) => clientY >= clientRect.top && clientY <= clientRect.bottom;
    const collidesY = (r1, r2) => r1.top < r2.bottom && r1.bottom > r2.top;
    const isOverlapping = (r1, r2) => {
      const overlap = overlapY(r1, r2) / Math.min(r1.height, r2.height);
      return collidesY(r1, r2) && overlap > 0.5;
    };
    const splitRectsPerAxis = (rects, y) => {
      const intersectingRects = filter$5(rects, rect => isInsideY(y, rect));
      return boundingClientRectFromRects(intersectingRects).fold(() => [
        [],
        rects
      ], boundingRect => {
        const {
          pass: horizontal,
          fail: vertical
        } = partition$2(rects, rect => isOverlapping(rect, boundingRect));
        return [
          horizontal,
          vertical
        ];
      });
    };
    const clientInfo = (rect, clientX) => {
      return {
        node: rect.node,
        position: distanceToRectLeft(rect, clientX) < distanceToRectRight(rect, clientX) ? FakeCaretPosition.Before : FakeCaretPosition.After
      };
    };
    const horizontalDistance = (rect, x, _y) => x > rect.left && x < rect.right ? 0 : Math.min(Math.abs(rect.left - x), Math.abs(rect.right - x));
    const closestChildCaretCandidateNodeRect = (children, clientX, clientY, findCloserTextNode) => {
      const caretCandidateRect = rect => {
        if (isCaretCandidate$3(rect.node)) {
          return Optional.some(rect);
        } else if (isElement$6(rect.node)) {
          return closestChildCaretCandidateNodeRect(from(rect.node.childNodes), clientX, clientY, false);
        } else {
          return Optional.none();
        }
      };
      const tryFindSecondBestTextNode = (closest, sndClosest, distance) => {
        return caretCandidateRect(sndClosest).filter(rect => {
          const deltaDistance = Math.abs(distance(closest, clientX, clientY) - distance(rect, clientX, clientY));
          return deltaDistance < 2 && isText$a(rect.node);
        });
      };
      const findClosestCaretCandidateNodeRect = (rects, distance) => {
        const sortedRects = sort(rects, (r1, r2) => distance(r1, clientX, clientY) - distance(r2, clientX, clientY));
        return findMap(sortedRects, caretCandidateRect).map(closest => {
          if (findCloserTextNode && !isText$a(closest.node) && sortedRects.length > 1) {
            return tryFindSecondBestTextNode(closest, sortedRects[1], distance).getOr(closest);
          } else {
            return closest;
          }
        });
      };
      const [horizontalRects, verticalRects] = splitRectsPerAxis(getClientRects(children), clientY);
      const {
        pass: above,
        fail: below
      } = partition$2(verticalRects, rect => rect.top < clientY);
      return findClosestCaretCandidateNodeRect(horizontalRects, horizontalDistance).orThunk(() => findClosestCaretCandidateNodeRect(below, distanceToRectEdgeFromXY)).orThunk(() => findClosestCaretCandidateNodeRect(above, distanceToRectEdgeFromXY));
    };
    const traverseUp = (rootElm, scope, clientX, clientY) => {
      const helper = (scope, prevScope) => {
        const isDragGhostContainer = node => isElement$6(node) && node.classList.contains('mce-drag-container');
        const childNodesWithoutGhost = filter$5(scope.dom.childNodes, not(isDragGhostContainer));
        return prevScope.fold(() => closestChildCaretCandidateNodeRect(childNodesWithoutGhost, clientX, clientY, true), prevScope => {
          const uncheckedChildren = filter$5(childNodesWithoutGhost, node => node !== prevScope.dom);
          return closestChildCaretCandidateNodeRect(uncheckedChildren, clientX, clientY, true);
        }).orThunk(() => {
          const parent = eq(scope, rootElm) ? Optional.none() : parentElement(scope);
          return parent.bind(newScope => helper(newScope, Optional.some(scope)));
        });
      };
      return helper(scope, Optional.none());
    };
    const closestCaretCandidateNodeRect = (root, clientX, clientY) => {
      const rootElm = SugarElement.fromDom(root);
      const ownerDoc = documentOrOwner(rootElm);
      const elementAtPoint = SugarElement.fromPoint(ownerDoc, clientX, clientY).filter(elm => contains(rootElm, elm));
      const element = elementAtPoint.getOr(rootElm);
      return traverseUp(rootElm, element, clientX, clientY);
    };
    const closestFakeCaretCandidate = (root, clientX, clientY) => closestCaretCandidateNodeRect(root, clientX, clientY).filter(rect => isFakeCaretTarget(rect.node)).map(rect => clientInfo(rect, clientX));

    const getAbsolutePosition = elm => {
      var _a, _b;
      const clientRect = elm.getBoundingClientRect();
      const doc = elm.ownerDocument;
      const docElem = doc.documentElement;
      const win = doc.defaultView;
      return {
        top: clientRect.top + ((_a = win === null || win === void 0 ? void 0 : win.scrollY) !== null && _a !== void 0 ? _a : 0) - docElem.clientTop,
        left: clientRect.left + ((_b = win === null || win === void 0 ? void 0 : win.scrollX) !== null && _b !== void 0 ? _b : 0) - docElem.clientLeft
      };
    };
    const getBodyPosition = editor => editor.inline ? getAbsolutePosition(editor.getBody()) : {
      left: 0,
      top: 0
    };
    const getScrollPosition = editor => {
      const body = editor.getBody();
      return editor.inline ? {
        left: body.scrollLeft,
        top: body.scrollTop
      } : {
        left: 0,
        top: 0
      };
    };
    const getBodyScroll = editor => {
      const body = editor.getBody(), docElm = editor.getDoc().documentElement;
      const inlineScroll = {
        left: body.scrollLeft,
        top: body.scrollTop
      };
      const iframeScroll = {
        left: body.scrollLeft || docElm.scrollLeft,
        top: body.scrollTop || docElm.scrollTop
      };
      return editor.inline ? inlineScroll : iframeScroll;
    };
    const getMousePosition = (editor, event) => {
      if (event.target.ownerDocument !== editor.getDoc()) {
        const iframePosition = getAbsolutePosition(editor.getContentAreaContainer());
        const scrollPosition = getBodyScroll(editor);
        return {
          left: event.pageX - iframePosition.left + scrollPosition.left,
          top: event.pageY - iframePosition.top + scrollPosition.top
        };
      }
      return {
        left: event.pageX,
        top: event.pageY
      };
    };
    const calculatePosition = (bodyPosition, scrollPosition, mousePosition) => ({
      pageX: mousePosition.left - bodyPosition.left + scrollPosition.left,
      pageY: mousePosition.top - bodyPosition.top + scrollPosition.top
    });
    const calc = (editor, event) => calculatePosition(getBodyPosition(editor), getScrollPosition(editor), getMousePosition(editor, event));

    const getTargetProps = target => ({
      target,
      srcElement: target
    });
    const makeDndEventFromMouseEvent = (type, mouseEvent, target, dataTransfer) => ({
      ...mouseEvent,
      dataTransfer,
      type,
      ...getTargetProps(target)
    });
    const makeDndEvent = (type, target, dataTransfer) => {
      const fail = die('Function not supported on simulated event.');
      const event = {
        bubbles: true,
        cancelBubble: false,
        cancelable: true,
        composed: false,
        currentTarget: null,
        defaultPrevented: false,
        eventPhase: 0,
        isTrusted: true,
        returnValue: false,
        timeStamp: 0,
        type,
        composedPath: fail,
        initEvent: fail,
        preventDefault: noop,
        stopImmediatePropagation: noop,
        stopPropagation: noop,
        AT_TARGET: window.Event.AT_TARGET,
        BUBBLING_PHASE: window.Event.BUBBLING_PHASE,
        CAPTURING_PHASE: window.Event.CAPTURING_PHASE,
        NONE: window.Event.NONE,
        altKey: false,
        button: 0,
        buttons: 0,
        clientX: 0,
        clientY: 0,
        ctrlKey: false,
        metaKey: false,
        movementX: 0,
        movementY: 0,
        offsetX: 0,
        offsetY: 0,
        pageX: 0,
        pageY: 0,
        relatedTarget: null,
        screenX: 0,
        screenY: 0,
        shiftKey: false,
        x: 0,
        y: 0,
        detail: 0,
        view: null,
        which: 0,
        initUIEvent: fail,
        initMouseEvent: fail,
        getModifierState: fail,
        dataTransfer,
        ...getTargetProps(target)
      };
      return event;
    };
    const makeDataTransferCopyForDragEvent = (dataTransfer, eventType) => {
      const copy = cloneDataTransfer(dataTransfer);
      if (eventType === 'dragstart') {
        setDragstartEvent(copy);
        setReadWriteMode(copy);
      } else if (eventType === 'drop') {
        setDropEvent(copy);
        setReadOnlyMode(copy);
      } else {
        setDragendEvent(copy);
        setProtectedMode(copy);
      }
      return copy;
    };
    const makeDragEvent = (type, target, dataTransfer, mouseEvent) => {
      const dataTransferForDispatch = makeDataTransferCopyForDragEvent(dataTransfer, type);
      return isUndefined(mouseEvent) ? makeDndEvent(type, target, dataTransferForDispatch) : makeDndEventFromMouseEvent(type, mouseEvent, target, dataTransferForDispatch);
    };

    const scrollPixelsPerInterval = 32;
    const scrollIntervalValue = 100;
    const mouseRangeToTriggerScrollInsideEditor = 8;
    const mouseRangeToTriggerScrollOutsideEditor = 16;
    const isContentEditableFalse$1 = isContentEditableFalse$b;
    const isContentEditable = or(isContentEditableFalse$1, isContentEditableTrue$3);
    const isDraggable = (dom, rootElm, elm) => isContentEditableFalse$1(elm) && elm !== rootElm && dom.isEditable(elm.parentElement);
    const isValidDropTarget = (editor, targetElement, dragElement) => {
      if (isNullable(targetElement)) {
        return false;
      } else if (targetElement === dragElement || editor.dom.isChildOf(targetElement, dragElement)) {
        return false;
      } else {
        return editor.dom.isEditable(targetElement);
      }
    };
    const createGhost = (editor, elm, width, height) => {
      const dom = editor.dom;
      const clonedElm = elm.cloneNode(true);
      dom.setStyles(clonedElm, {
        width,
        height
      });
      dom.setAttrib(clonedElm, 'data-mce-selected', null);
      const ghostElm = dom.create('div', {
        'class': 'mce-drag-container',
        'data-mce-bogus': 'all',
        'unselectable': 'on',
        'contenteditable': 'false'
      });
      dom.setStyles(ghostElm, {
        position: 'absolute',
        opacity: 0.5,
        overflow: 'hidden',
        border: 0,
        padding: 0,
        margin: 0,
        width,
        height
      });
      dom.setStyles(clonedElm, {
        margin: 0,
        boxSizing: 'border-box'
      });
      ghostElm.appendChild(clonedElm);
      return ghostElm;
    };
    const appendGhostToBody = (ghostElm, bodyElm) => {
      if (ghostElm.parentNode !== bodyElm) {
        bodyElm.appendChild(ghostElm);
      }
    };
    const scrollEditor = (direction, amount) => win => () => {
      const current = direction === 'left' ? win.scrollX : win.scrollY;
      win.scroll({
        [direction]: current + amount,
        behavior: 'smooth'
      });
    };
    const scrollLeft = scrollEditor('left', -scrollPixelsPerInterval);
    const scrollRight = scrollEditor('left', scrollPixelsPerInterval);
    const scrollUp = scrollEditor('top', -scrollPixelsPerInterval);
    const scrollDown = scrollEditor('top', scrollPixelsPerInterval);
    const moveGhost = (ghostElm, position, width, height, maxX, maxY, mouseY, mouseX, contentAreaContainer, win, state, mouseEventOriginatedFromWithinTheEditor) => {
      let overflowX = 0, overflowY = 0;
      ghostElm.style.left = position.pageX + 'px';
      ghostElm.style.top = position.pageY + 'px';
      if (position.pageX + width > maxX) {
        overflowX = position.pageX + width - maxX;
      }
      if (position.pageY + height > maxY) {
        overflowY = position.pageY + height - maxY;
      }
      ghostElm.style.width = width - overflowX + 'px';
      ghostElm.style.height = height - overflowY + 'px';
      const clientHeight = contentAreaContainer.clientHeight;
      const clientWidth = contentAreaContainer.clientWidth;
      const outerMouseY = mouseY + contentAreaContainer.getBoundingClientRect().top;
      const outerMouseX = mouseX + contentAreaContainer.getBoundingClientRect().left;
      state.on(state => {
        state.intervalId.clear();
        if (state.dragging && mouseEventOriginatedFromWithinTheEditor) {
          if (mouseY + mouseRangeToTriggerScrollInsideEditor >= clientHeight) {
            state.intervalId.set(scrollDown(win));
          } else if (mouseY - mouseRangeToTriggerScrollInsideEditor <= 0) {
            state.intervalId.set(scrollUp(win));
          } else if (mouseX + mouseRangeToTriggerScrollInsideEditor >= clientWidth) {
            state.intervalId.set(scrollRight(win));
          } else if (mouseX - mouseRangeToTriggerScrollInsideEditor <= 0) {
            state.intervalId.set(scrollLeft(win));
          } else if (outerMouseY + mouseRangeToTriggerScrollOutsideEditor >= window.innerHeight) {
            state.intervalId.set(scrollDown(window));
          } else if (outerMouseY - mouseRangeToTriggerScrollOutsideEditor <= 0) {
            state.intervalId.set(scrollUp(window));
          } else if (outerMouseX + mouseRangeToTriggerScrollOutsideEditor >= window.innerWidth) {
            state.intervalId.set(scrollRight(window));
          } else if (outerMouseX - mouseRangeToTriggerScrollOutsideEditor <= 0) {
            state.intervalId.set(scrollLeft(window));
          }
        }
      });
    };
    const removeElement = elm => {
      if (elm && elm.parentNode) {
        elm.parentNode.removeChild(elm);
      }
    };
    const removeElementWithPadding = (dom, elm) => {
      const parentBlock = dom.getParent(elm.parentNode, dom.isBlock);
      removeElement(elm);
      if (parentBlock && parentBlock !== dom.getRoot() && dom.isEmpty(parentBlock)) {
        fillWithPaddingBr(SugarElement.fromDom(parentBlock));
      }
    };
    const isLeftMouseButtonPressed = e => e.button === 0;
    const applyRelPos = (state, position) => ({
      pageX: position.pageX - state.relX,
      pageY: position.pageY + 5
    });
    const start = (state, editor) => e => {
      if (isLeftMouseButtonPressed(e)) {
        const ceElm = find$2(editor.dom.getParents(e.target), isContentEditable).getOr(null);
        if (isNonNullable(ceElm) && isDraggable(editor.dom, editor.getBody(), ceElm)) {
          const elmPos = editor.dom.getPos(ceElm);
          const bodyElm = editor.getBody();
          const docElm = editor.getDoc().documentElement;
          state.set({
            element: ceElm,
            dataTransfer: createDataTransfer(),
            dragging: false,
            screenX: e.screenX,
            screenY: e.screenY,
            maxX: (editor.inline ? bodyElm.scrollWidth : docElm.offsetWidth) - 2,
            maxY: (editor.inline ? bodyElm.scrollHeight : docElm.offsetHeight) - 2,
            relX: e.pageX - elmPos.x,
            relY: e.pageY - elmPos.y,
            width: ceElm.offsetWidth,
            height: ceElm.offsetHeight,
            ghost: createGhost(editor, ceElm, ceElm.offsetWidth, ceElm.offsetHeight),
            intervalId: repeatable(scrollIntervalValue)
          });
        }
      }
    };
    const placeCaretAt = (editor, clientX, clientY) => {
      editor._selectionOverrides.hideFakeCaret();
      closestFakeCaretCandidate(editor.getBody(), clientX, clientY).fold(() => editor.selection.placeCaretAt(clientX, clientY), caretInfo => {
        const range = editor._selectionOverrides.showCaret(1, caretInfo.node, caretInfo.position === FakeCaretPosition.Before, false);
        if (range) {
          editor.selection.setRng(range);
        } else {
          editor.selection.placeCaretAt(clientX, clientY);
        }
      });
    };
    const dispatchDragEvent = (editor, type, target, dataTransfer, mouseEvent) => {
      if (type === 'dragstart') {
        setHtmlData(dataTransfer, editor.dom.getOuterHTML(target));
      }
      const event = makeDragEvent(type, target, dataTransfer, mouseEvent);
      const args = editor.dispatch(type, event);
      return args;
    };
    const move = (state, editor) => {
      const throttledPlaceCaretAt = first$1((clientX, clientY) => placeCaretAt(editor, clientX, clientY), 0);
      editor.on('remove', throttledPlaceCaretAt.cancel);
      const state_ = state;
      return e => state.on(state => {
        const movement = Math.max(Math.abs(e.screenX - state.screenX), Math.abs(e.screenY - state.screenY));
        if (!state.dragging && movement > 10) {
          const args = dispatchDragEvent(editor, 'dragstart', state.element, state.dataTransfer, e);
          if (isNonNullable(args.dataTransfer)) {
            state.dataTransfer = args.dataTransfer;
          }
          if (args.isDefaultPrevented()) {
            return;
          }
          state.dragging = true;
          editor.focus();
        }
        if (state.dragging) {
          const mouseEventOriginatedFromWithinTheEditor = e.currentTarget === editor.getDoc().documentElement;
          const targetPos = applyRelPos(state, calc(editor, e));
          appendGhostToBody(state.ghost, editor.getBody());
          moveGhost(state.ghost, targetPos, state.width, state.height, state.maxX, state.maxY, e.clientY, e.clientX, editor.getContentAreaContainer(), editor.getWin(), state_, mouseEventOriginatedFromWithinTheEditor);
          throttledPlaceCaretAt.throttle(e.clientX, e.clientY);
        }
      });
    };
    const getRawTarget = selection => {
      const sel = selection.getSel();
      if (isNonNullable(sel)) {
        const rng = sel.getRangeAt(0);
        const startContainer = rng.startContainer;
        return isText$a(startContainer) ? startContainer.parentNode : startContainer;
      } else {
        return null;
      }
    };
    const drop = (state, editor) => e => {
      state.on(state => {
        var _a;
        state.intervalId.clear();
        if (state.dragging) {
          if (isValidDropTarget(editor, getRawTarget(editor.selection), state.element)) {
            const dropTarget = (_a = editor.getDoc().elementFromPoint(e.clientX, e.clientY)) !== null && _a !== void 0 ? _a : editor.getBody();
            const args = dispatchDragEvent(editor, 'drop', dropTarget, state.dataTransfer, e);
            if (!args.isDefaultPrevented()) {
              editor.undoManager.transact(() => {
                removeElementWithPadding(editor.dom, state.element);
                getHtmlData(state.dataTransfer).each(content => editor.insertContent(content));
                editor._selectionOverrides.hideFakeCaret();
              });
            }
          }
          dispatchDragEvent(editor, 'dragend', editor.getBody(), state.dataTransfer, e);
        }
      });
      removeDragState(state);
    };
    const stopDragging = (state, editor, e) => {
      state.on(state => {
        state.intervalId.clear();
        if (state.dragging) {
          e.fold(() => dispatchDragEvent(editor, 'dragend', state.element, state.dataTransfer), mouseEvent => dispatchDragEvent(editor, 'dragend', state.element, state.dataTransfer, mouseEvent));
        }
      });
      removeDragState(state);
    };
    const stop = (state, editor) => e => stopDragging(state, editor, Optional.some(e));
    const removeDragState = state => {
      state.on(state => {
        state.intervalId.clear();
        removeElement(state.ghost);
      });
      state.clear();
    };
    const bindFakeDragEvents = editor => {
      const state = value$2();
      const pageDom = DOMUtils.DOM;
      const rootDocument = document;
      const dragStartHandler = start(state, editor);
      const dragHandler = move(state, editor);
      const dropHandler = drop(state, editor);
      const dragEndHandler = stop(state, editor);
      editor.on('mousedown', dragStartHandler);
      editor.on('mousemove', dragHandler);
      editor.on('mouseup', dropHandler);
      pageDom.bind(rootDocument, 'mousemove', dragHandler);
      pageDom.bind(rootDocument, 'mouseup', dragEndHandler);
      editor.on('remove', () => {
        pageDom.unbind(rootDocument, 'mousemove', dragHandler);
        pageDom.unbind(rootDocument, 'mouseup', dragEndHandler);
      });
      editor.on('keydown', e => {
        if (e.keyCode === VK.ESC) {
          stopDragging(state, editor, Optional.none());
        }
      });
    };
    const blockUnsupportedFileDrop = editor => {
      const preventFileDrop = e => {
        if (!e.isDefaultPrevented()) {
          const dataTransfer = e.dataTransfer;
          if (dataTransfer && (contains$2(dataTransfer.types, 'Files') || dataTransfer.files.length > 0)) {
            e.preventDefault();
            if (e.type === 'drop') {
              displayError(editor, 'Dropped file type is not supported');
            }
          }
        }
      };
      const preventFileDropIfUIElement = e => {
        if (isUIElement(editor, e.target)) {
          preventFileDrop(e);
        }
      };
      const setup = () => {
        const pageDom = DOMUtils.DOM;
        const dom = editor.dom;
        const doc = document;
        const editorRoot = editor.inline ? editor.getBody() : editor.getDoc();
        const eventNames = [
          'drop',
          'dragover'
        ];
        each$e(eventNames, name => {
          pageDom.bind(doc, name, preventFileDropIfUIElement);
          dom.bind(editorRoot, name, preventFileDrop);
        });
        editor.on('remove', () => {
          each$e(eventNames, name => {
            pageDom.unbind(doc, name, preventFileDropIfUIElement);
            dom.unbind(editorRoot, name, preventFileDrop);
          });
        });
      };
      editor.on('init', () => {
        Delay.setEditorTimeout(editor, setup, 0);
      });
    };
    const init$2 = editor => {
      bindFakeDragEvents(editor);
      if (shouldBlockUnsupportedDrop(editor)) {
        blockUnsupportedFileDrop(editor);
      }
    };

    const setup$4 = editor => {
      const renderFocusCaret = first$1(() => {
        if (!editor.removed && editor.getBody().contains(document.activeElement)) {
          const rng = editor.selection.getRng();
          if (rng.collapsed) {
            const caretRange = renderRangeCaret(editor, rng, false);
            editor.selection.setRng(caretRange);
          }
        }
      }, 0);
      editor.on('focus', () => {
        renderFocusCaret.throttle();
      });
      editor.on('blur', () => {
        renderFocusCaret.cancel();
      });
    };

    const setup$3 = editor => {
      editor.on('init', () => {
        editor.on('focusin', e => {
          const target = e.target;
          if (isMedia$2(target)) {
            const ceRoot = getContentEditableRoot$1(editor.getBody(), target);
            const node = isContentEditableFalse$b(ceRoot) ? ceRoot : target;
            if (editor.selection.getNode() !== node) {
              selectNode(editor, node).each(rng => editor.selection.setRng(rng));
            }
          }
        });
      });
    };

    const isContentEditableFalse = isContentEditableFalse$b;
    const getContentEditableRoot = (editor, node) => getContentEditableRoot$1(editor.getBody(), node);
    const SelectionOverrides = editor => {
      const selection = editor.selection, dom = editor.dom;
      const rootNode = editor.getBody();
      const fakeCaret = FakeCaret(editor, rootNode, dom.isBlock, () => hasFocus(editor));
      const realSelectionId = 'sel-' + dom.uniqueId();
      const elementSelectionAttr = 'data-mce-selected';
      let selectedElement;
      const isFakeSelectionElement = node => isNonNullable(node) && dom.hasClass(node, 'mce-offscreen-selection');
      const isFakeSelectionTargetElement = node => node !== rootNode && (isContentEditableFalse(node) || isMedia$2(node)) && dom.isChildOf(node, rootNode) && dom.isEditable(node.parentNode);
      const setRange = range => {
        if (range) {
          selection.setRng(range);
        }
      };
      const showCaret = (direction, node, before, scrollIntoView = true) => {
        const e = editor.dispatch('ShowCaret', {
          target: node,
          direction,
          before
        });
        if (e.isDefaultPrevented()) {
          return null;
        }
        if (scrollIntoView) {
          selection.scrollIntoView(node, direction === -1);
        }
        return fakeCaret.show(before, node);
      };
      const showBlockCaretContainer = blockCaretContainer => {
        if (blockCaretContainer.hasAttribute('data-mce-caret')) {
          showCaretContainerBlock(blockCaretContainer);
          selection.scrollIntoView(blockCaretContainer);
        }
      };
      const registerEvents = () => {
        editor.on('click', e => {
          if (!dom.isEditable(e.target)) {
            e.preventDefault();
            editor.focus();
          }
        });
        editor.on('blur NewBlock', removeElementSelection);
        editor.on('ResizeWindow FullscreenStateChanged', fakeCaret.reposition);
        editor.on('tap', e => {
          const targetElm = e.target;
          const contentEditableRoot = getContentEditableRoot(editor, targetElm);
          if (isContentEditableFalse(contentEditableRoot)) {
            e.preventDefault();
            selectNode(editor, contentEditableRoot).each(setElementSelection);
          } else if (isFakeSelectionTargetElement(targetElm)) {
            selectNode(editor, targetElm).each(setElementSelection);
          }
        }, true);
        editor.on('mousedown', e => {
          const targetElm = e.target;
          if (targetElm !== rootNode && targetElm.nodeName !== 'HTML' && !dom.isChildOf(targetElm, rootNode)) {
            return;
          }
          if (!isXYInContentArea(editor, e.clientX, e.clientY)) {
            return;
          }
          removeElementSelection();
          hideFakeCaret();
          const closestContentEditable = getContentEditableRoot(editor, targetElm);
          if (isContentEditableFalse(closestContentEditable)) {
            e.preventDefault();
            selectNode(editor, closestContentEditable).each(setElementSelection);
          } else {
            closestFakeCaretCandidate(rootNode, e.clientX, e.clientY).each(caretInfo => {
              e.preventDefault();
              const range = showCaret(1, caretInfo.node, caretInfo.position === FakeCaretPosition.Before, false);
              setRange(range);
              if (isHTMLElement(closestContentEditable)) {
                closestContentEditable.focus();
              } else {
                editor.getBody().focus();
              }
            });
          }
        });
        editor.on('keypress', e => {
          if (VK.modifierPressed(e)) {
            return;
          }
          if (isContentEditableFalse(selection.getNode())) {
            e.preventDefault();
          }
        });
        editor.on('GetSelectionRange', e => {
          let rng = e.range;
          if (selectedElement) {
            if (!selectedElement.parentNode) {
              selectedElement = null;
              return;
            }
            rng = rng.cloneRange();
            rng.selectNode(selectedElement);
            e.range = rng;
          }
        });
        editor.on('SetSelectionRange', e => {
          e.range = normalizeVoidElementSelection(e.range);
          const rng = setElementSelection(e.range, e.forward);
          if (rng) {
            e.range = rng;
          }
        });
        const isPasteBin = node => isElement$6(node) && node.id === 'mcepastebin';
        editor.on('AfterSetSelectionRange', e => {
          const rng = e.range;
          const parent = rng.startContainer.parentElement;
          if (!isRangeInCaretContainer(rng) && !isPasteBin(parent)) {
            hideFakeCaret();
          }
          if (!isFakeSelectionElement(parent)) {
            removeElementSelection();
          }
        });
        init$2(editor);
        setup$4(editor);
        setup$3(editor);
      };
      const isWithinCaretContainer = node => isCaretContainer$2(node) || startsWithCaretContainer$1(node) || endsWithCaretContainer$1(node);
      const isRangeInCaretContainer = rng => isWithinCaretContainer(rng.startContainer) || isWithinCaretContainer(rng.endContainer);
      const normalizeVoidElementSelection = rng => {
        const voidElements = editor.schema.getVoidElements();
        const newRng = dom.createRng();
        const startContainer = rng.startContainer;
        const startOffset = rng.startOffset;
        const endContainer = rng.endContainer;
        const endOffset = rng.endOffset;
        if (has$2(voidElements, startContainer.nodeName.toLowerCase())) {
          if (startOffset === 0) {
            newRng.setStartBefore(startContainer);
          } else {
            newRng.setStartAfter(startContainer);
          }
        } else {
          newRng.setStart(startContainer, startOffset);
        }
        if (has$2(voidElements, endContainer.nodeName.toLowerCase())) {
          if (endOffset === 0) {
            newRng.setEndBefore(endContainer);
          } else {
            newRng.setEndAfter(endContainer);
          }
        } else {
          newRng.setEnd(endContainer, endOffset);
        }
        return newRng;
      };
      const setupOffscreenSelection = (node, targetClone) => {
        const body = SugarElement.fromDom(editor.getBody());
        const doc = editor.getDoc();
        const realSelectionContainer = descendant$1(body, '#' + realSelectionId).getOrThunk(() => {
          const newContainer = SugarElement.fromHtml('<div data-mce-bogus="all" class="mce-offscreen-selection"></div>', doc);
          set$3(newContainer, 'id', realSelectionId);
          append$1(body, newContainer);
          return newContainer;
        });
        const newRange = dom.createRng();
        empty(realSelectionContainer);
        append(realSelectionContainer, [
          SugarElement.fromText(nbsp, doc),
          SugarElement.fromDom(targetClone),
          SugarElement.fromText(nbsp, doc)
        ]);
        newRange.setStart(realSelectionContainer.dom.firstChild, 1);
        newRange.setEnd(realSelectionContainer.dom.lastChild, 0);
        setAll(realSelectionContainer, { top: dom.getPos(node, editor.getBody()).y + 'px' });
        focus$1(realSelectionContainer);
        const sel = selection.getSel();
        if (sel) {
          sel.removeAllRanges();
          sel.addRange(newRange);
        }
        return newRange;
      };
      const selectElement = elm => {
        const targetClone = elm.cloneNode(true);
        const e = editor.dispatch('ObjectSelected', {
          target: elm,
          targetClone
        });
        if (e.isDefaultPrevented()) {
          return null;
        }
        const range = setupOffscreenSelection(elm, e.targetClone);
        const nodeElm = SugarElement.fromDom(elm);
        each$e(descendants(SugarElement.fromDom(editor.getBody()), `*[${ elementSelectionAttr }]`), elm => {
          if (!eq(nodeElm, elm)) {
            remove$a(elm, elementSelectionAttr);
          }
        });
        if (!dom.getAttrib(elm, elementSelectionAttr)) {
          elm.setAttribute(elementSelectionAttr, '1');
        }
        selectedElement = elm;
        hideFakeCaret();
        return range;
      };
      const setElementSelection = (range, forward) => {
        if (!range) {
          return null;
        }
        if (range.collapsed) {
          if (!isRangeInCaretContainer(range)) {
            const dir = forward ? 1 : -1;
            const caretPosition = getNormalizedRangeEndPoint(dir, rootNode, range);
            const beforeNode = caretPosition.getNode(!forward);
            if (isNonNullable(beforeNode)) {
              if (isFakeCaretTarget(beforeNode)) {
                return showCaret(dir, beforeNode, forward ? !caretPosition.isAtEnd() : false, false);
              }
              if (isCaretContainerInline(beforeNode) && isContentEditableFalse$b(beforeNode.nextSibling)) {
                const rng = dom.createRng();
                rng.setStart(beforeNode, 0);
                rng.setEnd(beforeNode, 0);
                return rng;
              }
            }
            const afterNode = caretPosition.getNode(forward);
            if (isNonNullable(afterNode)) {
              if (isFakeCaretTarget(afterNode)) {
                return showCaret(dir, afterNode, forward ? false : !caretPosition.isAtEnd(), false);
              }
              if (isCaretContainerInline(afterNode) && isContentEditableFalse$b(afterNode.previousSibling)) {
                const rng = dom.createRng();
                rng.setStart(afterNode, 1);
                rng.setEnd(afterNode, 1);
                return rng;
              }
            }
          }
          return null;
        }
        let startContainer = range.startContainer;
        let startOffset = range.startOffset;
        const endOffset = range.endOffset;
        if (isText$a(startContainer) && startOffset === 0 && isContentEditableFalse(startContainer.parentNode)) {
          startContainer = startContainer.parentNode;
          startOffset = dom.nodeIndex(startContainer);
          startContainer = startContainer.parentNode;
        }
        if (!isElement$6(startContainer)) {
          return null;
        }
        if (endOffset === startOffset + 1 && startContainer === range.endContainer) {
          const node = startContainer.childNodes[startOffset];
          if (isFakeSelectionTargetElement(node)) {
            return selectElement(node);
          }
        }
        return null;
      };
      const removeElementSelection = () => {
        if (selectedElement) {
          selectedElement.removeAttribute(elementSelectionAttr);
        }
        descendant$1(SugarElement.fromDom(editor.getBody()), '#' + realSelectionId).each(remove$5);
        selectedElement = null;
      };
      const destroy = () => {
        fakeCaret.destroy();
        selectedElement = null;
      };
      const hideFakeCaret = () => {
        fakeCaret.hide();
      };
      if (!isRtc(editor)) {
        registerEvents();
      }
      return {
        showCaret,
        showBlockCaretContainer,
        hideFakeCaret,
        destroy
      };
    };

    const getNormalizedTextOffset = (container, offset) => {
      let normalizedOffset = offset;
      for (let node = container.previousSibling; isText$a(node); node = node.previousSibling) {
        normalizedOffset += node.data.length;
      }
      return normalizedOffset;
    };
    const generatePath = (dom, root, node, offset, normalized) => {
      if (isText$a(node) && (offset < 0 || offset > node.data.length)) {
        return [];
      }
      const p = normalized && isText$a(node) ? [getNormalizedTextOffset(node, offset)] : [offset];
      let current = node;
      while (current !== root && current.parentNode) {
        p.push(dom.nodeIndex(current, normalized));
        current = current.parentNode;
      }
      return current === root ? p.reverse() : [];
    };
    const generatePathRange = (dom, root, startNode, startOffset, endNode, endOffset, normalized = false) => {
      const start = generatePath(dom, root, startNode, startOffset, normalized);
      const end = generatePath(dom, root, endNode, endOffset, normalized);
      return {
        start,
        end
      };
    };
    const resolvePath = (root, path) => {
      const nodePath = path.slice();
      const offset = nodePath.pop();
      if (!isNumber(offset)) {
        return Optional.none();
      } else {
        const resolvedNode = foldl(nodePath, (optNode, index) => optNode.bind(node => Optional.from(node.childNodes[index])), Optional.some(root));
        return resolvedNode.bind(node => {
          if (isText$a(node) && (offset < 0 || offset > node.data.length)) {
            return Optional.none();
          } else {
            return Optional.some({
              node,
              offset
            });
          }
        });
      }
    };
    const resolvePathRange = (root, range) => resolvePath(root, range.start).bind(({
      node: startNode,
      offset: startOffset
    }) => resolvePath(root, range.end).map(({
      node: endNode,
      offset: endOffset
    }) => {
      const rng = document.createRange();
      rng.setStart(startNode, startOffset);
      rng.setEnd(endNode, endOffset);
      return rng;
    }));
    const generatePathRangeFromRange = (dom, root, range, normalized = false) => generatePathRange(dom, root, range.startContainer, range.startOffset, range.endContainer, range.endOffset, normalized);

    const cleanEmptyNodes = (dom, node, isRoot) => {
      if (node && dom.isEmpty(node) && !isRoot(node)) {
        const parent = node.parentNode;
        dom.remove(node, isText$a(node.firstChild) && isWhitespaceText(node.firstChild.data));
        cleanEmptyNodes(dom, parent, isRoot);
      }
    };
    const deleteRng = (dom, rng, isRoot, clean = true) => {
      const startParent = rng.startContainer.parentNode;
      const endParent = rng.endContainer.parentNode;
      rng.deleteContents();
      if (clean && !isRoot(rng.startContainer)) {
        if (isText$a(rng.startContainer) && rng.startContainer.data.length === 0) {
          dom.remove(rng.startContainer);
        }
        if (isText$a(rng.endContainer) && rng.endContainer.data.length === 0) {
          dom.remove(rng.endContainer);
        }
        cleanEmptyNodes(dom, startParent, isRoot);
        if (startParent !== endParent) {
          cleanEmptyNodes(dom, endParent, isRoot);
        }
      }
    };
    const getParentBlock = (editor, rng) => Optional.from(editor.dom.getParent(rng.startContainer, editor.dom.isBlock));
    const resolveFromDynamicPatterns = (patternSet, block, beforeText) => {
      const dynamicPatterns = patternSet.dynamicPatternsLookup({
        text: beforeText,
        block
      });
      return {
        ...patternSet,
        blockPatterns: getBlockPatterns(dynamicPatterns).concat(patternSet.blockPatterns),
        inlinePatterns: getInlinePatterns(dynamicPatterns).concat(patternSet.inlinePatterns)
      };
    };
    const getBeforeText = (dom, block, node, offset) => {
      const rng = dom.createRng();
      rng.setStart(block, 0);
      rng.setEnd(node, offset);
      return rng.toString();
    };

    const startsWithSingleSpace = s => /^\s[^\s]/.test(s);
    const stripPattern = (dom, block, pattern) => {
      const firstTextNode = textAfter(block, 0, block);
      firstTextNode.each(spot => {
        const node = spot.container;
        scanRight(node, pattern.start.length, block).each(end => {
          const rng = dom.createRng();
          rng.setStart(node, 0);
          rng.setEnd(end.container, end.offset);
          deleteRng(dom, rng, e => e === block);
        });
        const text = SugarElement.fromDom(node);
        const textContent = get$3(text);
        if (startsWithSingleSpace(textContent)) {
          set(text, textContent.slice(1));
        }
      });
    };
    const applyPattern$1 = (editor, match) => {
      const dom = editor.dom;
      const pattern = match.pattern;
      const rng = resolvePathRange(dom.getRoot(), match.range).getOrDie('Unable to resolve path range');
      const isBlockFormatName = (name, formatter) => {
        const formatSet = formatter.get(name);
        return isArray$1(formatSet) && head(formatSet).exists(format => has$2(format, 'block'));
      };
      getParentBlock(editor, rng).each(block => {
        if (pattern.type === 'block-format') {
          if (isBlockFormatName(pattern.format, editor.formatter)) {
            editor.undoManager.transact(() => {
              stripPattern(editor.dom, block, pattern);
              editor.formatter.apply(pattern.format);
            });
          }
        } else if (pattern.type === 'block-command') {
          editor.undoManager.transact(() => {
            stripPattern(editor.dom, block, pattern);
            editor.execCommand(pattern.cmd, false, pattern.value);
          });
        }
      });
      return true;
    };
    const sortPatterns$1 = patterns => sort(patterns, (a, b) => b.start.length - a.start.length);
    const findPattern$1 = (patterns, text) => {
      const sortedPatterns = sortPatterns$1(patterns);
      const nuText = text.replace(nbsp, ' ');
      return find$2(sortedPatterns, pattern => text.indexOf(pattern.start) === 0 || nuText.indexOf(pattern.start) === 0);
    };
    const findPatterns$1 = (editor, block, patternSet, normalizedMatches) => {
      var _a;
      const dom = editor.dom;
      const forcedRootBlock = getForcedRootBlock(editor);
      if (!dom.is(block, forcedRootBlock)) {
        return [];
      }
      const blockText = (_a = block.textContent) !== null && _a !== void 0 ? _a : '';
      return findPattern$1(patternSet.blockPatterns, blockText).map(pattern => {
        if (Tools.trim(blockText).length === pattern.start.length) {
          return [];
        }
        return [{
            pattern,
            range: generatePathRange(dom, dom.getRoot(), block, 0, block, 0, normalizedMatches)
          }];
      }).getOr([]);
    };
    const applyMatches$1 = (editor, matches) => {
      if (matches.length === 0) {
        return;
      }
      const bookmark = editor.selection.getBookmark();
      each$e(matches, match => applyPattern$1(editor, match));
      editor.selection.moveToBookmark(bookmark);
    };

    const newMarker = (dom, id) => dom.create('span', {
      'data-mce-type': 'bookmark',
      id
    });
    const rangeFromMarker = (dom, marker) => {
      const rng = dom.createRng();
      rng.setStartAfter(marker.start);
      rng.setEndBefore(marker.end);
      return rng;
    };
    const createMarker = (dom, markerPrefix, pathRange) => {
      const rng = resolvePathRange(dom.getRoot(), pathRange).getOrDie('Unable to resolve path range');
      const startNode = rng.startContainer;
      const endNode = rng.endContainer;
      const textEnd = rng.endOffset === 0 ? endNode : endNode.splitText(rng.endOffset);
      const textStart = rng.startOffset === 0 ? startNode : startNode.splitText(rng.startOffset);
      const startParentNode = textStart.parentNode;
      const endParentNode = textEnd.parentNode;
      return {
        prefix: markerPrefix,
        end: endParentNode.insertBefore(newMarker(dom, markerPrefix + '-end'), textEnd),
        start: startParentNode.insertBefore(newMarker(dom, markerPrefix + '-start'), textStart)
      };
    };
    const removeMarker = (dom, marker, isRoot) => {
      cleanEmptyNodes(dom, dom.get(marker.prefix + '-end'), isRoot);
      cleanEmptyNodes(dom, dom.get(marker.prefix + '-start'), isRoot);
    };

    const isReplacementPattern = pattern => pattern.start.length === 0;
    const matchesPattern = patternContent => (element, offset) => {
      const text = element.data;
      const searchText = text.substring(0, offset);
      const startEndIndex = searchText.lastIndexOf(patternContent.charAt(patternContent.length - 1));
      const startIndex = searchText.lastIndexOf(patternContent);
      if (startIndex !== -1) {
        return startIndex + patternContent.length;
      } else if (startEndIndex !== -1) {
        return startEndIndex + 1;
      } else {
        return -1;
      }
    };
    const findPatternStartFromSpot = (dom, pattern, block, spot) => {
      const startPattern = pattern.start;
      const startSpot = repeatLeft(dom, spot.container, spot.offset, matchesPattern(startPattern), block);
      return startSpot.bind(spot => {
        var _a, _b;
        const startPatternIndex = (_b = (_a = block.textContent) === null || _a === void 0 ? void 0 : _a.indexOf(startPattern)) !== null && _b !== void 0 ? _b : -1;
        const isCompleteMatch = startPatternIndex !== -1 && spot.offset >= startPatternIndex + startPattern.length;
        if (isCompleteMatch) {
          const rng = dom.createRng();
          rng.setStart(spot.container, spot.offset - startPattern.length);
          rng.setEnd(spot.container, spot.offset);
          return Optional.some(rng);
        } else {
          const offset = spot.offset - startPattern.length;
          return scanLeft(spot.container, offset, block).map(nextSpot => {
            const rng = dom.createRng();
            rng.setStart(nextSpot.container, nextSpot.offset);
            rng.setEnd(spot.container, spot.offset);
            return rng;
          }).filter(rng => rng.toString() === startPattern).orThunk(() => findPatternStartFromSpot(dom, pattern, block, point(spot.container, 0)));
        }
      });
    };
    const findPatternStart = (dom, pattern, node, offset, block, requireGap = false) => {
      if (pattern.start.length === 0 && !requireGap) {
        const rng = dom.createRng();
        rng.setStart(node, offset);
        rng.setEnd(node, offset);
        return Optional.some(rng);
      }
      return textBefore(node, offset, block).bind(spot => {
        const start = findPatternStartFromSpot(dom, pattern, block, spot);
        return start.bind(startRange => {
          var _a;
          if (requireGap) {
            if (startRange.endContainer === spot.container && startRange.endOffset === spot.offset) {
              return Optional.none();
            } else if (spot.offset === 0 && ((_a = startRange.endContainer.textContent) === null || _a === void 0 ? void 0 : _a.length) === startRange.endOffset) {
              return Optional.none();
            }
          }
          return Optional.some(startRange);
        });
      });
    };
    const findPattern = (editor, block, details, normalizedMatches) => {
      const dom = editor.dom;
      const root = dom.getRoot();
      const pattern = details.pattern;
      const endNode = details.position.container;
      const endOffset = details.position.offset;
      return scanLeft(endNode, endOffset - details.pattern.end.length, block).bind(spot => {
        const endPathRng = generatePathRange(dom, root, spot.container, spot.offset, endNode, endOffset, normalizedMatches);
        if (isReplacementPattern(pattern)) {
          return Optional.some({
            matches: [{
                pattern,
                startRng: endPathRng,
                endRng: endPathRng
              }],
            position: spot
          });
        } else {
          const resultsOpt = findPatternsRec(editor, details.remainingPatterns, spot.container, spot.offset, block, normalizedMatches);
          const results = resultsOpt.getOr({
            matches: [],
            position: spot
          });
          const pos = results.position;
          const start = findPatternStart(dom, pattern, pos.container, pos.offset, block, resultsOpt.isNone());
          return start.map(startRng => {
            const startPathRng = generatePathRangeFromRange(dom, root, startRng, normalizedMatches);
            return {
              matches: results.matches.concat([{
                  pattern,
                  startRng: startPathRng,
                  endRng: endPathRng
                }]),
              position: point(startRng.startContainer, startRng.startOffset)
            };
          });
        }
      });
    };
    const findPatternsRec = (editor, patterns, node, offset, block, normalizedMatches) => {
      const dom = editor.dom;
      return textBefore(node, offset, dom.getRoot()).bind(endSpot => {
        const text = getBeforeText(dom, block, node, offset);
        for (let i = 0; i < patterns.length; i++) {
          const pattern = patterns[i];
          if (!endsWith(text, pattern.end)) {
            continue;
          }
          const patternsWithoutCurrent = patterns.slice();
          patternsWithoutCurrent.splice(i, 1);
          const result = findPattern(editor, block, {
            pattern,
            remainingPatterns: patternsWithoutCurrent,
            position: endSpot
          }, normalizedMatches);
          if (result.isNone() && offset > 0) {
            return findPatternsRec(editor, patterns, node, offset - 1, block, normalizedMatches);
          }
          if (result.isSome()) {
            return result;
          }
        }
        return Optional.none();
      });
    };
    const applyPattern = (editor, pattern, patternRange) => {
      editor.selection.setRng(patternRange);
      if (pattern.type === 'inline-format') {
        each$e(pattern.format, format => {
          editor.formatter.apply(format);
        });
      } else {
        editor.execCommand(pattern.cmd, false, pattern.value);
      }
    };
    const applyReplacementPattern = (editor, pattern, marker, isRoot) => {
      const markerRange = rangeFromMarker(editor.dom, marker);
      deleteRng(editor.dom, markerRange, isRoot);
      applyPattern(editor, pattern, markerRange);
    };
    const applyPatternWithContent = (editor, pattern, startMarker, endMarker, isRoot) => {
      const dom = editor.dom;
      const markerEndRange = rangeFromMarker(dom, endMarker);
      const markerStartRange = rangeFromMarker(dom, startMarker);
      deleteRng(dom, markerStartRange, isRoot);
      deleteRng(dom, markerEndRange, isRoot);
      const patternMarker = {
        prefix: startMarker.prefix,
        start: startMarker.end,
        end: endMarker.start
      };
      const patternRange = rangeFromMarker(dom, patternMarker);
      applyPattern(editor, pattern, patternRange);
    };
    const addMarkers = (dom, matches) => {
      const markerPrefix = generate$1('mce_textpattern');
      const matchesWithEnds = foldr(matches, (acc, match) => {
        const endMarker = createMarker(dom, markerPrefix + `_end${ acc.length }`, match.endRng);
        return acc.concat([{
            ...match,
            endMarker
          }]);
      }, []);
      return foldr(matchesWithEnds, (acc, match) => {
        const idx = matchesWithEnds.length - acc.length - 1;
        const startMarker = isReplacementPattern(match.pattern) ? match.endMarker : createMarker(dom, markerPrefix + `_start${ idx }`, match.startRng);
        return acc.concat([{
            ...match,
            startMarker
          }]);
      }, []);
    };
    const sortPatterns = patterns => sort(patterns, (a, b) => b.end.length - a.end.length);
    const getBestMatches = (matches, matchesWithSortedPatterns) => {
      const hasSameMatches = forall(matches, match => exists(matchesWithSortedPatterns, sortedMatch => match.pattern.start === sortedMatch.pattern.start && match.pattern.end === sortedMatch.pattern.end));
      if (matches.length === matchesWithSortedPatterns.length) {
        if (hasSameMatches) {
          return matches;
        } else {
          return matchesWithSortedPatterns;
        }
      }
      return matches.length > matchesWithSortedPatterns.length ? matches : matchesWithSortedPatterns;
    };
    const findPatterns = (editor, block, node, offset, patternSet, normalizedMatches) => {
      const matches = findPatternsRec(editor, patternSet.inlinePatterns, node, offset, block, normalizedMatches).fold(() => [], result => result.matches);
      const matchesWithSortedPatterns = findPatternsRec(editor, sortPatterns(patternSet.inlinePatterns), node, offset, block, normalizedMatches).fold(() => [], result => result.matches);
      return getBestMatches(matches, matchesWithSortedPatterns);
    };
    const applyMatches = (editor, matches) => {
      if (matches.length === 0) {
        return;
      }
      const dom = editor.dom;
      const bookmark = editor.selection.getBookmark();
      const matchesWithMarkers = addMarkers(dom, matches);
      each$e(matchesWithMarkers, match => {
        const block = dom.getParent(match.startMarker.start, dom.isBlock);
        const isRoot = node => node === block;
        if (isReplacementPattern(match.pattern)) {
          applyReplacementPattern(editor, match.pattern, match.endMarker, isRoot);
        } else {
          applyPatternWithContent(editor, match.pattern, match.startMarker, match.endMarker, isRoot);
        }
        removeMarker(dom, match.endMarker, isRoot);
        removeMarker(dom, match.startMarker, isRoot);
      });
      editor.selection.moveToBookmark(bookmark);
    };

    const handleEnter = (editor, patternSet) => {
      const rng = editor.selection.getRng();
      return getParentBlock(editor, rng).map(block => {
        var _a;
        const offset = Math.max(0, rng.startOffset);
        const dynamicPatternSet = resolveFromDynamicPatterns(patternSet, block, (_a = block.textContent) !== null && _a !== void 0 ? _a : '');
        const inlineMatches = findPatterns(editor, block, rng.startContainer, offset, dynamicPatternSet, true);
        const blockMatches = findPatterns$1(editor, block, dynamicPatternSet, true);
        if (blockMatches.length > 0 || inlineMatches.length > 0) {
          editor.undoManager.add();
          editor.undoManager.extra(() => {
            editor.execCommand('mceInsertNewLine');
          }, () => {
            insert$5(editor);
            applyMatches(editor, inlineMatches);
            applyMatches$1(editor, blockMatches);
            const range = editor.selection.getRng();
            const spot = textBefore(range.startContainer, range.startOffset, editor.dom.getRoot());
            editor.execCommand('mceInsertNewLine');
            spot.each(s => {
              const node = s.container;
              if (node.data.charAt(s.offset - 1) === zeroWidth) {
                node.deleteData(s.offset - 1, 1);
                cleanEmptyNodes(editor.dom, node.parentNode, e => e === editor.dom.getRoot());
              }
            });
          });
          return true;
        }
        return false;
      }).getOr(false);
    };
    const handleInlineKey = (editor, patternSet) => {
      const rng = editor.selection.getRng();
      getParentBlock(editor, rng).map(block => {
        const offset = Math.max(0, rng.startOffset - 1);
        const beforeText = getBeforeText(editor.dom, block, rng.startContainer, offset);
        const dynamicPatternSet = resolveFromDynamicPatterns(patternSet, block, beforeText);
        const inlineMatches = findPatterns(editor, block, rng.startContainer, offset, dynamicPatternSet, false);
        if (inlineMatches.length > 0) {
          editor.undoManager.transact(() => {
            applyMatches(editor, inlineMatches);
          });
        }
      });
    };
    const checkKeyEvent = (codes, event, predicate) => {
      for (let i = 0; i < codes.length; i++) {
        if (predicate(codes[i], event)) {
          return true;
        }
      }
      return false;
    };
    const checkKeyCode = (codes, event) => checkKeyEvent(codes, event, (code, event) => {
      return code === event.keyCode && !VK.modifierPressed(event);
    });
    const checkCharCode = (chars, event) => checkKeyEvent(chars, event, (chr, event) => {
      return chr.charCodeAt(0) === event.charCode;
    });

    const setup$2 = editor => {
      const charCodes = [
        ',',
        '.',
        ';',
        ':',
        '!',
        '?'
      ];
      const keyCodes = [32];
      const getPatternSet = () => createPatternSet(getTextPatterns(editor), getTextPatternsLookup(editor));
      const hasDynamicPatterns = () => hasTextPatternsLookup(editor);
      editor.on('keydown', e => {
        if (e.keyCode === 13 && !VK.modifierPressed(e) && editor.selection.isCollapsed()) {
          const patternSet = getPatternSet();
          const hasPatterns = patternSet.inlinePatterns.length > 0 || patternSet.blockPatterns.length > 0 || hasDynamicPatterns();
          if (hasPatterns && handleEnter(editor, patternSet)) {
            e.preventDefault();
          }
        }
      }, true);
      const handleInlineTrigger = () => {
        if (editor.selection.isCollapsed()) {
          const patternSet = getPatternSet();
          const hasPatterns = patternSet.inlinePatterns.length > 0 || hasDynamicPatterns();
          if (hasPatterns) {
            handleInlineKey(editor, patternSet);
          }
        }
      };
      editor.on('keyup', e => {
        if (checkKeyCode(keyCodes, e)) {
          handleInlineTrigger();
        }
      });
      editor.on('keypress', e => {
        if (checkCharCode(charCodes, e)) {
          Delay.setEditorTimeout(editor, handleInlineTrigger);
        }
      });
    };

    const setup$1 = editor => {
      setup$2(editor);
    };

    const Quirks = editor => {
      const each = Tools.each;
      const BACKSPACE = VK.BACKSPACE, DELETE = VK.DELETE, dom = editor.dom, selection = editor.selection, parser = editor.parser;
      const browser = Env.browser;
      const isGecko = browser.isFirefox();
      const isWebKit = browser.isChromium() || browser.isSafari();
      const isiOS = Env.deviceType.isiPhone() || Env.deviceType.isiPad();
      const isMac = Env.os.isMacOS() || Env.os.isiOS();
      const setEditorCommandState = (cmd, state) => {
        try {
          editor.getDoc().execCommand(cmd, false, String(state));
        } catch (ex) {
        }
      };
      const isDefaultPrevented = e => {
        return e.isDefaultPrevented();
      };
      const emptyEditorWhenDeleting = () => {
        const serializeRng = rng => {
          const body = dom.create('body');
          const contents = rng.cloneContents();
          body.appendChild(contents);
          return selection.serializer.serialize(body, { format: 'html' });
        };
        const allContentsSelected = rng => {
          const selection = serializeRng(rng);
          const allRng = dom.createRng();
          allRng.selectNode(editor.getBody());
          const allSelection = serializeRng(allRng);
          return selection === allSelection;
        };
        editor.on('keydown', e => {
          const keyCode = e.keyCode;
          if (!isDefaultPrevented(e) && (keyCode === DELETE || keyCode === BACKSPACE) && editor.selection.isEditable()) {
            const isCollapsed = editor.selection.isCollapsed();
            const body = editor.getBody();
            if (isCollapsed && !isEmpty$2(SugarElement.fromDom(body))) {
              return;
            }
            if (!isCollapsed && !allContentsSelected(editor.selection.getRng())) {
              return;
            }
            e.preventDefault();
            editor.setContent('');
            if (body.firstChild && dom.isBlock(body.firstChild)) {
              editor.selection.setCursorLocation(body.firstChild, 0);
            } else {
              editor.selection.setCursorLocation(body, 0);
            }
            editor.nodeChanged();
          }
        });
      };
      const selectAll = () => {
        editor.shortcuts.add('meta+a', null, 'SelectAll');
      };
      const documentElementEditingFocus = () => {
        if (!editor.inline) {
          dom.bind(editor.getDoc(), 'mousedown mouseup', e => {
            let rng;
            if (e.target === editor.getDoc().documentElement) {
              rng = selection.getRng();
              editor.getBody().focus();
              if (e.type === 'mousedown') {
                if (isCaretContainer$2(rng.startContainer)) {
                  return;
                }
                selection.placeCaretAt(e.clientX, e.clientY);
              } else {
                selection.setRng(rng);
              }
            }
          });
        }
      };
      const removeHrOnBackspace = () => {
        editor.on('keydown', e => {
          if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
            if (!editor.getBody().getElementsByTagName('hr').length) {
              return;
            }
            if (selection.isCollapsed() && selection.getRng().startOffset === 0) {
              const node = selection.getNode();
              const previousSibling = node.previousSibling;
              if (node.nodeName === 'HR') {
                dom.remove(node);
                e.preventDefault();
                return;
              }
              if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === 'hr') {
                dom.remove(previousSibling);
                e.preventDefault();
              }
            }
          }
        });
      };
      const focusBody = () => {
        if (!Range.prototype.getClientRects) {
          editor.on('mousedown', e => {
            if (!isDefaultPrevented(e) && e.target.nodeName === 'HTML') {
              const body = editor.getBody();
              body.blur();
              Delay.setEditorTimeout(editor, () => {
                body.focus();
              });
            }
          });
        }
      };
      const selectControlElements = () => {
        const visualAidsAnchorClass = getVisualAidsAnchorClass(editor);
        editor.on('click', e => {
          const target = e.target;
          if (/^(IMG|HR)$/.test(target.nodeName) && dom.isEditable(target)) {
            e.preventDefault();
            editor.selection.select(target);
            editor.nodeChanged();
          }
          if (target.nodeName === 'A' && dom.hasClass(target, visualAidsAnchorClass) && target.childNodes.length === 0 && dom.isEditable(target.parentNode)) {
            e.preventDefault();
            selection.select(target);
          }
        });
      };
      const removeStylesWhenDeletingAcrossBlockElements = () => {
        const getAttributeApplyFunction = () => {
          const template = dom.getAttribs(selection.getStart().cloneNode(false));
          return () => {
            const target = selection.getStart();
            if (target !== editor.getBody()) {
              dom.setAttrib(target, 'style', null);
              each(template, attr => {
                target.setAttributeNode(attr.cloneNode(true));
              });
            }
          };
        };
        const isSelectionAcrossElements = () => {
          return !selection.isCollapsed() && dom.getParent(selection.getStart(), dom.isBlock) !== dom.getParent(selection.getEnd(), dom.isBlock);
        };
        editor.on('keypress', e => {
          let applyAttributes;
          if (!isDefaultPrevented(e) && (e.keyCode === 8 || e.keyCode === 46) && isSelectionAcrossElements()) {
            applyAttributes = getAttributeApplyFunction();
            editor.getDoc().execCommand('delete', false);
            applyAttributes();
            e.preventDefault();
            return false;
          } else {
            return true;
          }
        });
        dom.bind(editor.getDoc(), 'cut', e => {
          if (!isDefaultPrevented(e) && isSelectionAcrossElements()) {
            const applyAttributes = getAttributeApplyFunction();
            Delay.setEditorTimeout(editor, () => {
              applyAttributes();
            });
          }
        });
      };
      const disableBackspaceIntoATable = () => {
        editor.on('keydown', e => {
          if (!isDefaultPrevented(e) && e.keyCode === BACKSPACE) {
            if (selection.isCollapsed() && selection.getRng().startOffset === 0) {
              const previousSibling = selection.getNode().previousSibling;
              if (previousSibling && previousSibling.nodeName && previousSibling.nodeName.toLowerCase() === 'table') {
                e.preventDefault();
                return false;
              }
            }
          }
          return true;
        });
      };
      const removeBlockQuoteOnBackSpace = () => {
        editor.on('keydown', e => {
          if (isDefaultPrevented(e) || e.keyCode !== VK.BACKSPACE) {
            return;
          }
          let rng = selection.getRng();
          const container = rng.startContainer;
          const offset = rng.startOffset;
          const root = dom.getRoot();
          let parent = container;
          if (!rng.collapsed || offset !== 0) {
            return;
          }
          while (parent.parentNode && parent.parentNode.firstChild === parent && parent.parentNode !== root) {
            parent = parent.parentNode;
          }
          if (parent.nodeName === 'BLOCKQUOTE') {
            editor.formatter.toggle('blockquote', undefined, parent);
            rng = dom.createRng();
            rng.setStart(container, 0);
            rng.setEnd(container, 0);
            selection.setRng(rng);
          }
        });
      };
      const setGeckoEditingOptions = () => {
        const setOpts = () => {
          setEditorCommandState('StyleWithCSS', false);
          setEditorCommandState('enableInlineTableEditing', false);
          if (!getObjectResizing(editor)) {
            setEditorCommandState('enableObjectResizing', false);
          }
        };
        if (!isReadOnly$1(editor)) {
          editor.on('BeforeExecCommand mousedown', setOpts);
        }
      };
      const addBrAfterLastLinks = () => {
        const fixLinks = () => {
          each(dom.select('a:not([data-mce-block])'), node => {
            var _a;
            let parentNode = node.parentNode;
            const root = dom.getRoot();
            if ((parentNode === null || parentNode === void 0 ? void 0 : parentNode.lastChild) === node) {
              while (parentNode && !dom.isBlock(parentNode)) {
                if (((_a = parentNode.parentNode) === null || _a === void 0 ? void 0 : _a.lastChild) !== parentNode || parentNode === root) {
                  return;
                }
                parentNode = parentNode.parentNode;
              }
              dom.add(parentNode, 'br', { 'data-mce-bogus': 1 });
            }
          });
        };
        editor.on('SetContent ExecCommand', e => {
          if (e.type === 'setcontent' || e.command === 'mceInsertLink') {
            fixLinks();
          }
        });
      };
      const setDefaultBlockType = () => {
        editor.on('init', () => {
          setEditorCommandState('DefaultParagraphSeparator', getForcedRootBlock(editor));
        });
      };
      const isAllContentSelected = editor => {
        const body = editor.getBody();
        const rng = editor.selection.getRng();
        return rng.startContainer === rng.endContainer && rng.startContainer === body && rng.startOffset === 0 && rng.endOffset === body.childNodes.length;
      };
      const normalizeSelection = () => {
        editor.on('keyup focusin mouseup', e => {
          if (!VK.modifierPressed(e) && !isAllContentSelected(editor)) {
            selection.normalize();
          }
        }, true);
      };
      const showBrokenImageIcon = () => {
        editor.contentStyles.push('img:-moz-broken {' + '-moz-force-broken-image-icon:1;' + 'min-width:24px;' + 'min-height:24px' + '}');
      };
      const restoreFocusOnKeyDown = () => {
        if (!editor.inline) {
          editor.on('keydown', () => {
            if (document.activeElement === document.body) {
              editor.getWin().focus();
            }
          });
        }
      };
      const bodyHeight = () => {
        if (!editor.inline) {
          editor.contentStyles.push('body {min-height: 150px}');
          editor.on('click', e => {
            let rng;
            if (e.target.nodeName === 'HTML') {
              rng = editor.selection.getRng();
              editor.getBody().focus();
              editor.selection.setRng(rng);
              editor.selection.normalize();
              editor.nodeChanged();
            }
          });
        }
      };
      const blockCmdArrowNavigation = () => {
        if (isMac) {
          editor.on('keydown', e => {
            if (VK.metaKeyPressed(e) && !e.shiftKey && (e.keyCode === 37 || e.keyCode === 39)) {
              e.preventDefault();
              const selection = editor.selection.getSel();
              selection.modify('move', e.keyCode === 37 ? 'backward' : 'forward', 'lineboundary');
            }
          });
        }
      };
      const tapLinksAndImages = () => {
        editor.on('click', e => {
          let elm = e.target;
          do {
            if (elm.tagName === 'A') {
              e.preventDefault();
              return;
            }
          } while (elm = elm.parentNode);
        });
        editor.contentStyles.push('.mce-content-body {-webkit-touch-callout: none}');
      };
      const blockFormSubmitInsideEditor = () => {
        editor.on('init', () => {
          editor.dom.bind(editor.getBody(), 'submit', e => {
            e.preventDefault();
          });
        });
      };
      const removeAppleInterchangeBrs = () => {
        parser.addNodeFilter('br', nodes => {
          let i = nodes.length;
          while (i--) {
            if (nodes[i].attr('class') === 'Apple-interchange-newline') {
              nodes[i].remove();
            }
          }
        });
      };
      const refreshContentEditable = noop;
      const isHidden = () => {
        if (!isGecko || editor.removed) {
          return false;
        }
        const sel = editor.selection.getSel();
        return !sel || !sel.rangeCount || sel.rangeCount === 0;
      };
      const setupRtc = () => {
        if (isWebKit) {
          documentElementEditingFocus();
          selectControlElements();
          blockFormSubmitInsideEditor();
          selectAll();
          if (isiOS) {
            restoreFocusOnKeyDown();
            bodyHeight();
            tapLinksAndImages();
          }
        }
        if (isGecko) {
          focusBody();
          setGeckoEditingOptions();
          showBrokenImageIcon();
          blockCmdArrowNavigation();
        }
      };
      const dropDragEndEvent = () => {
        editor.on('drop', event => {
          var _a;
          const data = (_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.getData('text/html');
          if (isString(data) && /^<img[^>]*>$/.test(data)) {
            editor.dispatch('dragend', new window.DragEvent('dragend', event));
          }
        });
      };
      const setup = () => {
        removeBlockQuoteOnBackSpace();
        emptyEditorWhenDeleting();
        if (!Env.windowsPhone) {
          normalizeSelection();
        }
        if (isWebKit) {
          documentElementEditingFocus();
          selectControlElements();
          setDefaultBlockType();
          blockFormSubmitInsideEditor();
          disableBackspaceIntoATable();
          removeAppleInterchangeBrs();
          if (isiOS) {
            restoreFocusOnKeyDown();
            bodyHeight();
            tapLinksAndImages();
          } else {
            selectAll();
          }
        }
        if (isGecko) {
          removeHrOnBackspace();
          focusBody();
          removeStylesWhenDeletingAcrossBlockElements();
          setGeckoEditingOptions();
          addBrAfterLastLinks();
          showBrokenImageIcon();
          blockCmdArrowNavigation();
          disableBackspaceIntoATable();
          dropDragEndEvent();
        }
      };
      if (isRtc(editor)) {
        setupRtc();
      } else {
        setup();
      }
      return {
        refreshContentEditable,
        isHidden
      };
    };

    const DOM$6 = DOMUtils.DOM;
    const appendStyle = (editor, text) => {
      const body = SugarElement.fromDom(editor.getBody());
      const container = getStyleContainer(getRootNode(body));
      const style = SugarElement.fromTag('style');
      set$3(style, 'type', 'text/css');
      append$1(style, SugarElement.fromText(text));
      append$1(container, style);
      editor.on('remove', () => {
        remove$5(style);
      });
    };
    const getRootName = editor => editor.inline ? editor.getElement().nodeName.toLowerCase() : undefined;
    const removeUndefined = obj => filter$4(obj, v => isUndefined(v) === false);
    const mkParserSettings = editor => {
      const getOption = editor.options.get;
      const blobCache = editor.editorUpload.blobCache;
      return removeUndefined({
        allow_conditional_comments: getOption('allow_conditional_comments'),
        allow_html_data_urls: getOption('allow_html_data_urls'),
        allow_svg_data_urls: getOption('allow_svg_data_urls'),
        allow_html_in_named_anchor: getOption('allow_html_in_named_anchor'),
        allow_script_urls: getOption('allow_script_urls'),
        allow_unsafe_link_target: getOption('allow_unsafe_link_target'),
        convert_unsafe_embeds: getOption('convert_unsafe_embeds'),
        convert_fonts_to_spans: getOption('convert_fonts_to_spans'),
        fix_list_elements: getOption('fix_list_elements'),
        font_size_legacy_values: getOption('font_size_legacy_values'),
        forced_root_block: getOption('forced_root_block'),
        forced_root_block_attrs: getOption('forced_root_block_attrs'),
        preserve_cdata: getOption('preserve_cdata'),
        inline_styles: getOption('inline_styles'),
        root_name: getRootName(editor),
        sandbox_iframes: getOption('sandbox_iframes'),
        sanitize: getOption('xss_sanitization'),
        validate: true,
        blob_cache: blobCache,
        document: editor.getDoc()
      });
    };
    const mkSchemaSettings = editor => {
      const getOption = editor.options.get;
      return removeUndefined({
        custom_elements: getOption('custom_elements'),
        extended_valid_elements: getOption('extended_valid_elements'),
        invalid_elements: getOption('invalid_elements'),
        invalid_styles: getOption('invalid_styles'),
        schema: getOption('schema'),
        valid_children: getOption('valid_children'),
        valid_classes: getOption('valid_classes'),
        valid_elements: getOption('valid_elements'),
        valid_styles: getOption('valid_styles'),
        verify_html: getOption('verify_html'),
        padd_empty_block_inline_children: getOption('format_empty_lines')
      });
    };
    const mkSerializerSettings = editor => {
      const getOption = editor.options.get;
      return {
        ...mkParserSettings(editor),
        ...mkSchemaSettings(editor),
        ...removeUndefined({
          remove_trailing_brs: getOption('remove_trailing_brs'),
          pad_empty_with_br: getOption('pad_empty_with_br'),
          url_converter: getOption('url_converter'),
          url_converter_scope: getOption('url_converter_scope'),
          element_format: getOption('element_format'),
          entities: getOption('entities'),
          entity_encoding: getOption('entity_encoding'),
          indent: getOption('indent'),
          indent_after: getOption('indent_after'),
          indent_before: getOption('indent_before')
        })
      };
    };
    const createParser = editor => {
      const parser = DomParser(mkParserSettings(editor), editor.schema);
      parser.addAttributeFilter('src,href,style,tabindex', (nodes, name) => {
        const dom = editor.dom;
        const internalName = 'data-mce-' + name;
        let i = nodes.length;
        while (i--) {
          const node = nodes[i];
          let value = node.attr(name);
          if (value && !node.attr(internalName)) {
            if (value.indexOf('data:') === 0 || value.indexOf('blob:') === 0) {
              continue;
            }
            if (name === 'style') {
              value = dom.serializeStyle(dom.parseStyle(value), node.name);
              if (!value.length) {
                value = null;
              }
              node.attr(internalName, value);
              node.attr(name, value);
            } else if (name === 'tabindex') {
              node.attr(internalName, value);
              node.attr(name, null);
            } else {
              node.attr(internalName, editor.convertURL(value, name, node.name));
            }
          }
        }
      });
      parser.addNodeFilter('script', nodes => {
        let i = nodes.length;
        while (i--) {
          const node = nodes[i];
          const type = node.attr('type') || 'no/type';
          if (type.indexOf('mce-') !== 0) {
            node.attr('type', 'mce-' + type);
          }
        }
      });
      if (shouldPreserveCData(editor)) {
        parser.addNodeFilter('#cdata', nodes => {
          var _a;
          let i = nodes.length;
          while (i--) {
            const node = nodes[i];
            node.type = 8;
            node.name = '#comment';
            node.value = '[CDATA[' + editor.dom.encode((_a = node.value) !== null && _a !== void 0 ? _a : '') + ']]';
          }
        });
      }
      parser.addNodeFilter('p,h1,h2,h3,h4,h5,h6,div', nodes => {
        let i = nodes.length;
        const nonEmptyElements = editor.schema.getNonEmptyElements();
        while (i--) {
          const node = nodes[i];
          if (node.isEmpty(nonEmptyElements) && node.getAll('br').length === 0) {
            node.append(new AstNode('br', 1));
          }
        }
      });
      return parser;
    };
    const autoFocus = editor => {
      const autoFocus = getAutoFocus(editor);
      if (autoFocus) {
        Delay.setEditorTimeout(editor, () => {
          let focusEditor;
          if (autoFocus === true) {
            focusEditor = editor;
          } else {
            focusEditor = editor.editorManager.get(autoFocus);
          }
          if (focusEditor && !focusEditor.destroyed) {
            focusEditor.focus();
            focusEditor.selection.scrollIntoView();
          }
        }, 100);
      }
    };
    const moveSelectionToFirstCaretPosition = editor => {
      const root = editor.dom.getRoot();
      if (!editor.inline && (!hasAnyRanges(editor) || editor.selection.getStart(true) === root)) {
        firstPositionIn(root).each(pos => {
          const node = pos.getNode();
          const caretPos = isTable$2(node) ? firstPositionIn(node).getOr(pos) : pos;
          editor.selection.setRng(caretPos.toRange());
        });
      }
    };
    const initEditor = editor => {
      editor.bindPendingEventDelegates();
      editor.initialized = true;
      fireInit(editor);
      editor.focus(true);
      moveSelectionToFirstCaretPosition(editor);
      editor.nodeChanged({ initial: true });
      const initInstanceCallback = getInitInstanceCallback(editor);
      if (isFunction(initInstanceCallback)) {
        initInstanceCallback.call(editor, editor);
      }
      autoFocus(editor);
    };
    const getStyleSheetLoader$1 = editor => editor.inline ? editor.ui.styleSheetLoader : editor.dom.styleSheetLoader;
    const makeStylesheetLoadingPromises = (editor, css, framedFonts) => {
      const {
        pass: bundledCss,
        fail: normalCss
      } = partition$2(css, name => tinymce.Resource.has(toContentSkinResourceName(name)));
      const bundledPromises = bundledCss.map(url => {
        const css = tinymce.Resource.get(toContentSkinResourceName(url));
        if (isString(css)) {
          return Promise.resolve(getStyleSheetLoader$1(editor).loadRawCss(url, css));
        }
        return Promise.resolve();
      });
      const promises = [
        ...bundledPromises,
        getStyleSheetLoader$1(editor).loadAll(normalCss)
      ];
      if (editor.inline) {
        return promises;
      } else {
        return promises.concat([editor.ui.styleSheetLoader.loadAll(framedFonts)]);
      }
    };
    const loadContentCss = editor => {
      const styleSheetLoader = getStyleSheetLoader$1(editor);
      const fontCss = getFontCss(editor);
      const css = editor.contentCSS;
      const removeCss = () => {
        styleSheetLoader.unloadAll(css);
        if (!editor.inline) {
          editor.ui.styleSheetLoader.unloadAll(fontCss);
        }
      };
      const loaded = () => {
        if (editor.removed) {
          removeCss();
        } else {
          editor.on('remove', removeCss);
        }
      };
      if (editor.contentStyles.length > 0) {
        let contentCssText = '';
        Tools.each(editor.contentStyles, style => {
          contentCssText += style + '\r\n';
        });
        editor.dom.addStyle(contentCssText);
      }
      const allStylesheets = Promise.all(makeStylesheetLoadingPromises(editor, css, fontCss)).then(loaded).catch(loaded);
      const contentStyle = getContentStyle(editor);
      if (contentStyle) {
        appendStyle(editor, contentStyle);
      }
      return allStylesheets;
    };
    const preInit = editor => {
      const doc = editor.getDoc(), body = editor.getBody();
      firePreInit(editor);
      if (!shouldBrowserSpellcheck(editor)) {
        doc.body.spellcheck = false;
        DOM$6.setAttrib(body, 'spellcheck', 'false');
      }
      editor.quirks = Quirks(editor);
      firePostRender(editor);
      const directionality = getDirectionality(editor);
      if (directionality !== undefined) {
        body.dir = directionality;
      }
      const protect = getProtect(editor);
      if (protect) {
        editor.on('BeforeSetContent', e => {
          Tools.each(protect, pattern => {
            e.content = e.content.replace(pattern, str => {
              return '<!--mce:protected ' + escape(str) + '-->';
            });
          });
        });
      }
      editor.on('SetContent', () => {
        editor.addVisual(editor.getBody());
      });
      editor.on('compositionstart compositionend', e => {
        editor.composing = e.type === 'compositionstart';
      });
    };
    const loadInitialContent = editor => {
      if (!isRtc(editor)) {
        editor.load({
          initial: true,
          format: 'html'
        });
      }
      editor.startContent = editor.getContent({ format: 'raw' });
    };
    const initEditorWithInitialContent = editor => {
      if (editor.removed !== true) {
        loadInitialContent(editor);
        initEditor(editor);
      }
    };
    const startProgress = editor => {
      let canceled = false;
      const progressTimeout = setTimeout(() => {
        if (!canceled) {
          editor.setProgressState(true);
        }
      }, 500);
      return () => {
        clearTimeout(progressTimeout);
        canceled = true;
        editor.setProgressState(false);
      };
    };
    const contentBodyLoaded = editor => {
      const targetElm = editor.getElement();
      let doc = editor.getDoc();
      if (editor.inline) {
        DOM$6.addClass(targetElm, 'mce-content-body');
        editor.contentDocument = doc = document;
        editor.contentWindow = window;
        editor.bodyElement = targetElm;
        editor.contentAreaContainer = targetElm;
      }
      const body = editor.getBody();
      body.disabled = true;
      editor.readonly = isReadOnly$1(editor);
      editor._editableRoot = hasEditableRoot$1(editor);
      if (!editor.readonly && editor.hasEditableRoot()) {
        if (editor.inline && DOM$6.getStyle(body, 'position', true) === 'static') {
          body.style.position = 'relative';
        }
        body.contentEditable = 'true';
      }
      body.disabled = false;
      editor.editorUpload = EditorUpload(editor);
      editor.schema = Schema(mkSchemaSettings(editor));
      editor.dom = DOMUtils(doc, {
        keep_values: true,
        url_converter: editor.convertURL,
        url_converter_scope: editor,
        update_styles: true,
        root_element: editor.inline ? editor.getBody() : null,
        collect: editor.inline,
        schema: editor.schema,
        contentCssCors: shouldUseContentCssCors(editor),
        referrerPolicy: getReferrerPolicy(editor),
        onSetAttrib: e => {
          editor.dispatch('SetAttrib', e);
        },
        force_hex_color: shouldForceHexColor(editor)
      });
      editor.parser = createParser(editor);
      editor.serializer = DomSerializer(mkSerializerSettings(editor), editor);
      editor.selection = EditorSelection(editor.dom, editor.getWin(), editor.serializer, editor);
      editor.annotator = Annotator(editor);
      editor.formatter = Formatter(editor);
      editor.undoManager = UndoManager(editor);
      editor._nodeChangeDispatcher = new NodeChange(editor);
      editor._selectionOverrides = SelectionOverrides(editor);
      setup$p(editor);
      setup$6(editor);
      setup$n(editor);
      if (!isRtc(editor)) {
        setup$5(editor);
        setup$1(editor);
      }
      const caret = setup$b(editor);
      setup$q(editor, caret);
      setup$o(editor);
      setup$r(editor);
      setup$7(editor);
      const setupRtcThunk = setup$t(editor);
      preInit(editor);
      setupRtcThunk.fold(() => {
        const cancelProgress = startProgress(editor);
        loadContentCss(editor).then(() => {
          initEditorWithInitialContent(editor);
          cancelProgress();
        });
      }, setupRtc => {
        editor.setProgressState(true);
        loadContentCss(editor).then(() => {
          setupRtc().then(_rtcMode => {
            editor.setProgressState(false);
            initEditorWithInitialContent(editor);
            bindEvents(editor);
          }, err => {
            editor.notificationManager.open({
              type: 'error',
              text: String(err)
            });
            initEditorWithInitialContent(editor);
            bindEvents(editor);
          });
        });
      });
    };

    const filter = always;
    const bind = (element, event, handler) => bind$2(element, event, filter, handler);

    const DOM$5 = DOMUtils.DOM;
    const createIframeElement = (id, title, customAttrs, tabindex) => {
      const iframe = SugarElement.fromTag('iframe');
      tabindex.each(t => set$3(iframe, 'tabindex', t));
      setAll$1(iframe, customAttrs);
      setAll$1(iframe, {
        id: id + '_ifr',
        frameBorder: '0',
        allowTransparency: 'true',
        title
      });
      add$2(iframe, 'tox-edit-area__iframe');
      return iframe;
    };
    const getIframeHtml = editor => {
      let iframeHTML = getDocType(editor) + '<html><head>';
      if (getDocumentBaseUrl(editor) !== editor.documentBaseUrl) {
        iframeHTML += '<base href="' + editor.documentBaseURI.getURI() + '" />';
      }
      iframeHTML += '<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />';
      const bodyId = getBodyId(editor);
      const bodyClass = getBodyClass(editor);
      const translatedAriaText = editor.translate(getIframeAriaText(editor));
      if (getContentSecurityPolicy(editor)) {
        iframeHTML += '<meta http-equiv="Content-Security-Policy" content="' + getContentSecurityPolicy(editor) + '" />';
      }
      iframeHTML += '</head>' + `<body id="${ bodyId }" class="mce-content-body ${ bodyClass }" data-id="${ editor.id }" aria-label="${ translatedAriaText }">` + '<br>' + '</body></html>';
      return iframeHTML;
    };
    const createIframe = (editor, boxInfo) => {
      const iframeTitle = editor.translate('Rich Text Area');
      const tabindex = getOpt(SugarElement.fromDom(editor.getElement()), 'tabindex').bind(toInt);
      const ifr = createIframeElement(editor.id, iframeTitle, getIframeAttrs(editor), tabindex).dom;
      ifr.onload = () => {
        ifr.onload = null;
        editor.dispatch('load');
      };
      editor.contentAreaContainer = boxInfo.iframeContainer;
      editor.iframeElement = ifr;
      editor.iframeHTML = getIframeHtml(editor);
      DOM$5.add(boxInfo.iframeContainer, ifr);
    };
    const setupIframeBody = editor => {
      const iframe = editor.iframeElement;
      const ready = () => {
        editor.contentDocument = iframe.contentDocument;
        contentBodyLoaded(editor);
      };
      if (shouldUseDocumentWrite(editor) || Env.browser.isFirefox()) {
        const doc = editor.getDoc();
        doc.open();
        doc.write(editor.iframeHTML);
        doc.close();
        ready();
      } else {
        const binder = bind(SugarElement.fromDom(iframe), 'load', () => {
          binder.unbind();
          ready();
        });
        iframe.srcdoc = editor.iframeHTML;
      }
    };
    const init$1 = (editor, boxInfo) => {
      createIframe(editor, boxInfo);
      if (boxInfo.editorContainer) {
        boxInfo.editorContainer.style.display = editor.orgDisplay;
        editor.hidden = DOM$5.isHidden(boxInfo.editorContainer);
      }
      editor.getElement().style.display = 'none';
      DOM$5.setAttrib(editor.id, 'aria-hidden', 'true');
      editor.getElement().style.visibility = editor.orgVisibility;
      setupIframeBody(editor);
    };

    const DOM$4 = DOMUtils.DOM;
    const initPlugin = (editor, initializedPlugins, plugin) => {
      const Plugin = PluginManager.get(plugin);
      const pluginUrl = PluginManager.urls[plugin] || editor.documentBaseUrl.replace(/\/$/, '');
      plugin = Tools.trim(plugin);
      if (Plugin && Tools.inArray(initializedPlugins, plugin) === -1) {
        if (editor.plugins[plugin]) {
          return;
        }
        try {
          const pluginInstance = Plugin(editor, pluginUrl) || {};
          editor.plugins[plugin] = pluginInstance;
          if (isFunction(pluginInstance.init)) {
            pluginInstance.init(editor, pluginUrl);
            initializedPlugins.push(plugin);
          }
        } catch (e) {
          pluginInitError(editor, plugin, e);
        }
      }
    };
    const trimLegacyPrefix = name => {
      return name.replace(/^\-/, '');
    };
    const initPlugins = editor => {
      const initializedPlugins = [];
      each$e(getPlugins(editor), name => {
        initPlugin(editor, initializedPlugins, trimLegacyPrefix(name));
      });
    };
    const initIcons = editor => {
      const iconPackName = Tools.trim(getIconPackName(editor));
      const currentIcons = editor.ui.registry.getAll().icons;
      const loadIcons = {
        ...IconManager.get('default').icons,
        ...IconManager.get(iconPackName).icons
      };
      each$d(loadIcons, (svgData, icon) => {
        if (!has$2(currentIcons, icon)) {
          editor.ui.registry.addIcon(icon, svgData);
        }
      });
    };
    const initTheme = editor => {
      const theme = getTheme(editor);
      if (isString(theme)) {
        const Theme = ThemeManager.get(theme);
        editor.theme = Theme(editor, ThemeManager.urls[theme]) || {};
        if (isFunction(editor.theme.init)) {
          editor.theme.init(editor, ThemeManager.urls[theme] || editor.documentBaseUrl.replace(/\/$/, ''));
        }
      } else {
        editor.theme = {};
      }
    };
    const initModel = editor => {
      const model = getModel(editor);
      const Model = ModelManager.get(model);
      editor.model = Model(editor, ModelManager.urls[model]);
    };
    const renderFromLoadedTheme = editor => {
      const render = editor.theme.renderUI;
      return render ? render() : renderThemeFalse(editor);
    };
    const renderFromThemeFunc = editor => {
      const elm = editor.getElement();
      const theme = getTheme(editor);
      const info = theme(editor, elm);
      if (info.editorContainer.nodeType) {
        info.editorContainer.id = info.editorContainer.id || editor.id + '_parent';
      }
      if (info.iframeContainer && info.iframeContainer.nodeType) {
        info.iframeContainer.id = info.iframeContainer.id || editor.id + '_iframecontainer';
      }
      info.height = info.iframeHeight ? info.iframeHeight : elm.offsetHeight;
      return info;
    };
    const createThemeFalseResult = (element, iframe) => {
      return {
        editorContainer: element,
        iframeContainer: iframe,
        api: {}
      };
    };
    const renderThemeFalseIframe = targetElement => {
      const iframeContainer = DOM$4.create('div');
      DOM$4.insertAfter(iframeContainer, targetElement);
      return createThemeFalseResult(iframeContainer, iframeContainer);
    };
    const renderThemeFalse = editor => {
      const targetElement = editor.getElement();
      return editor.inline ? createThemeFalseResult(null) : renderThemeFalseIframe(targetElement);
    };
    const renderThemeUi = editor => {
      const elm = editor.getElement();
      editor.orgDisplay = elm.style.display;
      if (isString(getTheme(editor))) {
        return renderFromLoadedTheme(editor);
      } else if (isFunction(getTheme(editor))) {
        return renderFromThemeFunc(editor);
      } else {
        return renderThemeFalse(editor);
      }
    };
    const augmentEditorUiApi = (editor, api) => {
      const uiApiFacade = {
        show: Optional.from(api.show).getOr(noop),
        hide: Optional.from(api.hide).getOr(noop),
        isEnabled: Optional.from(api.isEnabled).getOr(always),
        setEnabled: state => {
          if (!editor.mode.isReadOnly()) {
            Optional.from(api.setEnabled).each(f => f(state));
          }
        }
      };
      editor.ui = {
        ...editor.ui,
        ...uiApiFacade
      };
    };
    const init = async editor => {
      editor.dispatch('ScriptsLoaded');
      initIcons(editor);
      initTheme(editor);
      initModel(editor);
      initPlugins(editor);
      const renderInfo = await renderThemeUi(editor);
      augmentEditorUiApi(editor, Optional.from(renderInfo.api).getOr({}));
      editor.editorContainer = renderInfo.editorContainer;
      appendContentCssFromSettings(editor);
      if (editor.inline) {
        contentBodyLoaded(editor);
      } else {
        init$1(editor, {
          editorContainer: renderInfo.editorContainer,
          iframeContainer: renderInfo.iframeContainer
        });
      }
    };

    const DOM$3 = DOMUtils.DOM;
    const hasSkipLoadPrefix = name => name.charAt(0) === '-';
    const loadLanguage = (scriptLoader, editor) => {
      const languageCode = getLanguageCode(editor);
      const languageUrl = getLanguageUrl(editor);
      if (!I18n.hasCode(languageCode) && languageCode !== 'en') {
        const url = isNotEmpty(languageUrl) ? languageUrl : `${ editor.editorManager.baseURL }/langs/${ languageCode }.js`;
        scriptLoader.add(url).catch(() => {
          languageLoadError(editor, url, languageCode);
        });
      }
    };
    const loadTheme = (editor, suffix) => {
      const theme = getTheme(editor);
      if (isString(theme) && !hasSkipLoadPrefix(theme) && !has$2(ThemeManager.urls, theme)) {
        const themeUrl = getThemeUrl(editor);
        const url = themeUrl ? editor.documentBaseURI.toAbsolute(themeUrl) : `themes/${ theme }/theme${ suffix }.js`;
        ThemeManager.load(theme, url).catch(() => {
          themeLoadError(editor, url, theme);
        });
      }
    };
    const loadModel = (editor, suffix) => {
      const model = getModel(editor);
      if (model !== 'plugin' && !has$2(ModelManager.urls, model)) {
        const modelUrl = getModelUrl(editor);
        const url = isString(modelUrl) ? editor.documentBaseURI.toAbsolute(modelUrl) : `models/${ model }/model${ suffix }.js`;
        ModelManager.load(model, url).catch(() => {
          modelLoadError(editor, url, model);
        });
      }
    };
    const getIconsUrlMetaFromUrl = editor => Optional.from(getIconsUrl(editor)).filter(isNotEmpty).map(url => ({
      url,
      name: Optional.none()
    }));
    const getIconsUrlMetaFromName = (editor, name, suffix) => Optional.from(name).filter(name => isNotEmpty(name) && !IconManager.has(name)).map(name => ({
      url: `${ editor.editorManager.baseURL }/icons/${ name }/icons${ suffix }.js`,
      name: Optional.some(name)
    }));
    const loadIcons = (scriptLoader, editor, suffix) => {
      const defaultIconsUrl = getIconsUrlMetaFromName(editor, 'default', suffix);
      const customIconsUrl = getIconsUrlMetaFromUrl(editor).orThunk(() => getIconsUrlMetaFromName(editor, getIconPackName(editor), ''));
      each$e(cat([
        defaultIconsUrl,
        customIconsUrl
      ]), urlMeta => {
        scriptLoader.add(urlMeta.url).catch(() => {
          iconsLoadError(editor, urlMeta.url, urlMeta.name.getOrUndefined());
        });
      });
    };
    const loadPlugins = (editor, suffix) => {
      const loadPlugin = (name, url) => {
        PluginManager.load(name, url).catch(() => {
          pluginLoadError(editor, url, name);
        });
      };
      each$d(getExternalPlugins$1(editor), (url, name) => {
        loadPlugin(name, url);
        editor.options.set('plugins', getPlugins(editor).concat(name));
      });
      each$e(getPlugins(editor), plugin => {
        plugin = Tools.trim(plugin);
        if (plugin && !PluginManager.urls[plugin] && !hasSkipLoadPrefix(plugin)) {
          loadPlugin(plugin, `plugins/${ plugin }/plugin${ suffix }.js`);
        }
      });
    };
    const isThemeLoaded = editor => {
      const theme = getTheme(editor);
      return !isString(theme) || isNonNullable(ThemeManager.get(theme));
    };
    const isModelLoaded = editor => {
      const model = getModel(editor);
      return isNonNullable(ModelManager.get(model));
    };
    const loadScripts = (editor, suffix) => {
      const scriptLoader = ScriptLoader.ScriptLoader;
      const initEditor = () => {
        if (!editor.removed && isThemeLoaded(editor) && isModelLoaded(editor)) {
          init(editor);
        }
      };
      loadTheme(editor, suffix);
      loadModel(editor, suffix);
      loadLanguage(scriptLoader, editor);
      loadIcons(scriptLoader, editor, suffix);
      loadPlugins(editor, suffix);
      scriptLoader.loadQueue().then(initEditor, initEditor);
    };
    const getStyleSheetLoader = (element, editor) => instance.forElement(element, {
      contentCssCors: hasContentCssCors(editor),
      referrerPolicy: getReferrerPolicy(editor)
    });
    const render = editor => {
      const id = editor.id;
      I18n.setCode(getLanguageCode(editor));
      const readyHandler = () => {
        DOM$3.unbind(window, 'ready', readyHandler);
        editor.render();
      };
      if (!EventUtils.Event.domLoaded) {
        DOM$3.bind(window, 'ready', readyHandler);
        return;
      }
      if (!editor.getElement()) {
        return;
      }
      const element = SugarElement.fromDom(editor.getElement());
      const snapshot = clone$4(element);
      editor.on('remove', () => {
        eachr(element.dom.attributes, attr => remove$a(element, attr.name));
        setAll$1(element, snapshot);
      });
      editor.ui.styleSheetLoader = getStyleSheetLoader(element, editor);
      if (!isInline$1(editor)) {
        editor.orgVisibility = editor.getElement().style.visibility;
        editor.getElement().style.visibility = 'hidden';
      } else {
        editor.inline = true;
      }
      const form = editor.getElement().form || DOM$3.getParent(id, 'form');
      if (form) {
        editor.formElement = form;
        if (hasHiddenInput(editor) && !isTextareaOrInput(editor.getElement())) {
          DOM$3.insertAfter(DOM$3.create('input', {
            type: 'hidden',
            name: id
          }), id);
          editor.hasHiddenInput = true;
        }
        editor.formEventDelegate = e => {
          editor.dispatch(e.type, e);
        };
        DOM$3.bind(form, 'submit reset', editor.formEventDelegate);
        editor.on('reset', () => {
          editor.resetContent();
        });
        if (shouldPatchSubmit(editor) && !form.submit.nodeType && !form.submit.length && !form._mceOldSubmit) {
          form._mceOldSubmit = form.submit;
          form.submit = () => {
            editor.editorManager.triggerSave();
            editor.setDirty(false);
            return form._mceOldSubmit(form);
          };
        }
      }
      editor.windowManager = WindowManager(editor);
      editor.notificationManager = NotificationManager(editor);
      if (isEncodingXml(editor)) {
        editor.on('GetContent', e => {
          if (e.save) {
            e.content = DOM$3.encode(e.content);
          }
        });
      }
      if (shouldAddFormSubmitTrigger(editor)) {
        editor.on('submit', () => {
          if (editor.initialized) {
            editor.save();
          }
        });
      }
      if (shouldAddUnloadTrigger(editor)) {
        editor._beforeUnload = () => {
          if (editor.initialized && !editor.destroyed && !editor.isHidden()) {
            editor.save({
              format: 'raw',
              no_events: true,
              set_dirty: false
            });
          }
        };
        editor.editorManager.on('BeforeUnload', editor._beforeUnload);
      }
      editor.editorManager.add(editor);
      loadScripts(editor, editor.suffix);
    };

    const setEditableRoot = (editor, state) => {
      if (editor._editableRoot !== state) {
        editor._editableRoot = state;
        if (!editor.readonly) {
          editor.getBody().contentEditable = String(editor.hasEditableRoot());
          editor.nodeChanged();
        }
        fireEditableRootStateChange(editor, state);
      }
    };
    const hasEditableRoot = editor => editor._editableRoot;

    const sectionResult = (sections, settings) => ({
      sections: constant(sections),
      options: constant(settings)
    });
    const deviceDetection = detect$2().deviceType;
    const isPhone = deviceDetection.isPhone();
    const isTablet = deviceDetection.isTablet();
    const normalizePlugins = plugins => {
      if (isNullable(plugins)) {
        return [];
      } else {
        const pluginNames = isArray$1(plugins) ? plugins : plugins.split(/[ ,]/);
        const trimmedPlugins = map$3(pluginNames, trim$4);
        return filter$5(trimmedPlugins, isNotEmpty);
      }
    };
    const extractSections = (keys, options) => {
      const result = bifilter(options, (value, key) => {
        return contains$2(keys, key);
      });
      return sectionResult(result.t, result.f);
    };
    const getSection = (sectionResult, name, defaults = {}) => {
      const sections = sectionResult.sections();
      const sectionOptions = get$a(sections, name).getOr({});
      return Tools.extend({}, defaults, sectionOptions);
    };
    const hasSection = (sectionResult, name) => {
      return has$2(sectionResult.sections(), name);
    };
    const getSectionConfig = (sectionResult, name) => {
      return hasSection(sectionResult, name) ? sectionResult.sections()[name] : {};
    };
    const getMobileOverrideOptions = (mobileOptions, isPhone) => {
      const defaultMobileOptions = {
        table_grid: false,
        object_resizing: false,
        resize: false,
        toolbar_mode: get$a(mobileOptions, 'toolbar_mode').getOr('scrolling'),
        toolbar_sticky: false
      };
      const defaultPhoneOptions = { menubar: false };
      return {
        ...defaultMobileOptions,
        ...isPhone ? defaultPhoneOptions : {}
      };
    };
    const getExternalPlugins = (overrideOptions, options) => {
      var _a;
      const userDefinedExternalPlugins = (_a = options.external_plugins) !== null && _a !== void 0 ? _a : {};
      if (overrideOptions && overrideOptions.external_plugins) {
        return Tools.extend({}, overrideOptions.external_plugins, userDefinedExternalPlugins);
      } else {
        return userDefinedExternalPlugins;
      }
    };
    const combinePlugins = (forcedPlugins, plugins) => [
      ...normalizePlugins(forcedPlugins),
      ...normalizePlugins(plugins)
    ];
    const getPlatformPlugins = (isMobileDevice, sectionResult, desktopPlugins, mobilePlugins) => {
      if (isMobileDevice && hasSection(sectionResult, 'mobile')) {
        return mobilePlugins;
      } else {
        return desktopPlugins;
      }
    };
    const processPlugins = (isMobileDevice, sectionResult, defaultOverrideOptions, options) => {
      const forcedPlugins = normalizePlugins(defaultOverrideOptions.forced_plugins);
      const desktopPlugins = normalizePlugins(options.plugins);
      const mobileConfig = getSectionConfig(sectionResult, 'mobile');
      const mobilePlugins = mobileConfig.plugins ? normalizePlugins(mobileConfig.plugins) : desktopPlugins;
      const platformPlugins = getPlatformPlugins(isMobileDevice, sectionResult, desktopPlugins, mobilePlugins);
      const combinedPlugins = combinePlugins(forcedPlugins, platformPlugins);
      return Tools.extend(options, {
        forced_plugins: forcedPlugins,
        plugins: combinedPlugins
      });
    };
    const isOnMobile = (isMobileDevice, sectionResult) => {
      return isMobileDevice && hasSection(sectionResult, 'mobile');
    };
    const combineOptions = (isMobileDevice, isPhone, defaultOptions, defaultOverrideOptions, options) => {
      var _a;
      const deviceOverrideOptions = isMobileDevice ? { mobile: getMobileOverrideOptions((_a = options.mobile) !== null && _a !== void 0 ? _a : {}, isPhone) } : {};
      const sectionResult = extractSections(['mobile'], deepMerge(deviceOverrideOptions, options));
      const extendedOptions = Tools.extend(defaultOptions, defaultOverrideOptions, sectionResult.options(), isOnMobile(isMobileDevice, sectionResult) ? getSection(sectionResult, 'mobile') : {}, { external_plugins: getExternalPlugins(defaultOverrideOptions, sectionResult.options()) });
      return processPlugins(isMobileDevice, sectionResult, defaultOverrideOptions, extendedOptions);
    };
    const normalizeOptions = (defaultOverrideOptions, options) => combineOptions(isPhone || isTablet, isPhone, options, defaultOverrideOptions, options);

    const addVisual = (editor, elm) => addVisual$1(editor, elm);

    const registerExecCommands$3 = editor => {
      const toggleFormat = (name, value) => {
        editor.formatter.toggle(name, value);
        editor.nodeChanged();
      };
      const toggleAlign = align => () => {
        each$e('left,center,right,justify'.split(','), name => {
          if (align !== name) {
            editor.formatter.remove('align' + name);
          }
        });
        if (align !== 'none') {
          toggleFormat('align' + align);
        }
      };
      editor.editorCommands.addCommands({
        JustifyLeft: toggleAlign('left'),
        JustifyCenter: toggleAlign('center'),
        JustifyRight: toggleAlign('right'),
        JustifyFull: toggleAlign('justify'),
        JustifyNone: toggleAlign('none')
      });
    };
    const registerQueryStateCommands$1 = editor => {
      const alignStates = name => () => {
        const selection = editor.selection;
        const nodes = selection.isCollapsed() ? [editor.dom.getParent(selection.getNode(), editor.dom.isBlock)] : selection.getSelectedBlocks();
        return exists(nodes, node => isNonNullable(editor.formatter.matchNode(node, name)));
      };
      editor.editorCommands.addCommands({
        JustifyLeft: alignStates('alignleft'),
        JustifyCenter: alignStates('aligncenter'),
        JustifyRight: alignStates('alignright'),
        JustifyFull: alignStates('alignjustify')
      }, 'state');
    };
    const registerCommands$b = editor => {
      registerExecCommands$3(editor);
      registerQueryStateCommands$1(editor);
    };

    const registerCommands$a = editor => {
      editor.editorCommands.addCommands({
        'Cut,Copy,Paste': command => {
          const doc = editor.getDoc();
          let failed;
          try {
            doc.execCommand(command);
          } catch (ex) {
            failed = true;
          }
          if (command === 'paste' && !doc.queryCommandEnabled(command)) {
            failed = true;
          }
          if (failed || !doc.queryCommandSupported(command)) {
            let msg = editor.translate(`Your browser doesn't support direct access to the clipboard. ` + 'Please use the Ctrl+X/C/V keyboard shortcuts instead.');
            if (Env.os.isMacOS() || Env.os.isiOS()) {
              msg = msg.replace(/Ctrl\+/g, '\u2318+');
            }
            editor.notificationManager.open({
              text: msg,
              type: 'error'
            });
          }
        }
      });
    };

    const trimOrPadLeftRight = (dom, rng, html, schema) => {
      const root = SugarElement.fromDom(dom.getRoot());
      if (needsToBeNbspLeft(root, CaretPosition.fromRangeStart(rng), schema)) {
        html = html.replace(/^ /, '&nbsp;');
      } else {
        html = html.replace(/^&nbsp;/, ' ');
      }
      if (needsToBeNbspRight(root, CaretPosition.fromRangeEnd(rng), schema)) {
        html = html.replace(/(&nbsp;| )(<br( \/)>)?$/, '&nbsp;');
      } else {
        html = html.replace(/&nbsp;(<br( \/)?>)?$/, ' ');
      }
      return html;
    };

    const processValue$1 = value => {
      if (typeof value !== 'string') {
        const details = Tools.extend({
          paste: value.paste,
          data: { paste: value.paste }
        }, value);
        return {
          content: value.content,
          details
        };
      }
      return {
        content: value,
        details: {}
      };
    };
    const trimOrPad = (editor, value) => {
      const selection = editor.selection;
      const dom = editor.dom;
      if (/^ | $/.test(value)) {
        return trimOrPadLeftRight(dom, selection.getRng(), value, editor.schema);
      } else {
        return value;
      }
    };
    const insertAtCaret = (editor, value) => {
      if (editor.selection.isEditable()) {
        const {content, details} = processValue$1(value);
        preProcessSetContent(editor, {
          ...details,
          content: trimOrPad(editor, content),
          format: 'html',
          set: false,
          selection: true
        }).each(args => {
          const insertedContent = insertContent$1(editor, args.content, details);
          postProcessSetContent(editor, insertedContent, args);
          editor.addVisual();
        });
      }
    };

    const registerCommands$9 = editor => {
      editor.editorCommands.addCommands({
        mceCleanup: () => {
          const bm = editor.selection.getBookmark();
          editor.setContent(editor.getContent());
          editor.selection.moveToBookmark(bm);
        },
        insertImage: (_command, _ui, value) => {
          insertAtCaret(editor, editor.dom.createHTML('img', { src: value }));
        },
        insertHorizontalRule: () => {
          editor.execCommand('mceInsertContent', false, '<hr>');
        },
        insertText: (_command, _ui, value) => {
          insertAtCaret(editor, editor.dom.encode(value));
        },
        insertHTML: (_command, _ui, value) => {
          insertAtCaret(editor, value);
        },
        mceInsertContent: (_command, _ui, value) => {
          insertAtCaret(editor, value);
        },
        mceSetContent: (_command, _ui, value) => {
          editor.setContent(value);
        },
        mceReplaceContent: (_command, _ui, value) => {
          editor.execCommand('mceInsertContent', false, value.replace(/\{\$selection\}/g, editor.selection.getContent({ format: 'text' })));
        },
        mceNewDocument: () => {
          editor.setContent(getNewDocumentContent(editor));
        }
      });
    };

    const legacyPropNames = {
      'font-size': 'size',
      'font-family': 'face'
    };
    const isFont = isTag('font');
    const getSpecifiedFontProp = (propName, rootElm, elm) => {
      const getProperty = elm => getRaw(elm, propName).orThunk(() => {
        if (isFont(elm)) {
          return get$a(legacyPropNames, propName).bind(legacyPropName => getOpt(elm, legacyPropName));
        } else {
          return Optional.none();
        }
      });
      const isRoot = elm => eq(SugarElement.fromDom(rootElm), elm);
      return closest$1(SugarElement.fromDom(elm), elm => getProperty(elm), isRoot);
    };
    const normalizeFontFamily = fontFamily => fontFamily.replace(/[\'\"\\]/g, '').replace(/,\s+/g, ',');
    const getComputedFontProp = (propName, elm) => Optional.from(DOMUtils.DOM.getStyle(elm, propName, true));
    const getFontProp = propName => (rootElm, elm) => Optional.from(elm).map(SugarElement.fromDom).filter(isElement$7).bind(element => getSpecifiedFontProp(propName, rootElm, element.dom).or(getComputedFontProp(propName, element.dom))).getOr('');
    const getFontSize = getFontProp('font-size');
    const getFontFamily = compose(normalizeFontFamily, getFontProp('font-family'));

    const findFirstCaretElement = editor => firstPositionIn(editor.getBody()).bind(caret => {
      const container = caret.container();
      return Optional.from(isText$a(container) ? container.parentNode : container);
    });
    const getCaretElement = editor => Optional.from(editor.selection.getRng()).bind(rng => {
      const root = editor.getBody();
      const atStartOfNode = rng.startContainer === root && rng.startOffset === 0;
      return atStartOfNode ? Optional.none() : Optional.from(editor.selection.getStart(true));
    });
    const bindRange = (editor, binder) => getCaretElement(editor).orThunk(curry(findFirstCaretElement, editor)).map(SugarElement.fromDom).filter(isElement$7).bind(binder);
    const mapRange = (editor, mapper) => bindRange(editor, compose1(Optional.some, mapper));

    const fromFontSizeNumber = (editor, value) => {
      if (/^[0-9.]+$/.test(value)) {
        const fontSizeNumber = parseInt(value, 10);
        if (fontSizeNumber >= 1 && fontSizeNumber <= 7) {
          const fontSizes = getFontStyleValues(editor);
          const fontClasses = getFontSizeClasses(editor);
          if (fontClasses.length > 0) {
            return fontClasses[fontSizeNumber - 1] || value;
          } else {
            return fontSizes[fontSizeNumber - 1] || value;
          }
        } else {
          return value;
        }
      } else {
        return value;
      }
    };
    const normalizeFontNames = font => {
      const fonts = font.split(/\s*,\s*/);
      return map$3(fonts, font => {
        if (font.indexOf(' ') !== -1 && !(startsWith(font, '"') || startsWith(font, `'`))) {
          return `'${ font }'`;
        } else {
          return font;
        }
      }).join(',');
    };
    const fontNameAction = (editor, value) => {
      const font = fromFontSizeNumber(editor, value);
      editor.formatter.toggle('fontname', { value: normalizeFontNames(font) });
      editor.nodeChanged();
    };
    const fontNameQuery = editor => mapRange(editor, elm => getFontFamily(editor.getBody(), elm.dom)).getOr('');
    const fontSizeAction = (editor, value) => {
      editor.formatter.toggle('fontsize', { value: fromFontSizeNumber(editor, value) });
      editor.nodeChanged();
    };
    const fontSizeQuery = editor => mapRange(editor, elm => getFontSize(editor.getBody(), elm.dom)).getOr('');

    const lineHeightQuery = editor => mapRange(editor, elm => {
      const root = SugarElement.fromDom(editor.getBody());
      const specifiedStyle = closest$1(elm, elm => getRaw(elm, 'line-height'), curry(eq, root));
      const computedStyle = () => {
        const lineHeight = parseFloat(get$7(elm, 'line-height'));
        const fontSize = parseFloat(get$7(elm, 'font-size'));
        return String(lineHeight / fontSize);
      };
      return specifiedStyle.getOrThunk(computedStyle);
    }).getOr('');
    const lineHeightAction = (editor, lineHeight) => {
      editor.formatter.toggle('lineheight', { value: String(lineHeight) });
      editor.nodeChanged();
    };

    const registerExecCommands$2 = editor => {
      const toggleFormat = (name, value) => {
        editor.formatter.toggle(name, value);
        editor.nodeChanged();
      };
      editor.editorCommands.addCommands({
        'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': command => {
          toggleFormat(command);
        },
        'ForeColor,HiliteColor': (command, _ui, value) => {
          toggleFormat(command, { value });
        },
        'BackColor': (_command, _ui, value) => {
          toggleFormat('hilitecolor', { value });
        },
        'FontName': (_command, _ui, value) => {
          fontNameAction(editor, value);
        },
        'FontSize': (_command, _ui, value) => {
          fontSizeAction(editor, value);
        },
        'LineHeight': (_command, _ui, value) => {
          lineHeightAction(editor, value);
        },
        'Lang': (command, _ui, lang) => {
          var _a;
          toggleFormat(command, {
            value: lang.code,
            customValue: (_a = lang.customCode) !== null && _a !== void 0 ? _a : null
          });
        },
        'RemoveFormat': command => {
          editor.formatter.remove(command);
        },
        'mceBlockQuote': () => {
          toggleFormat('blockquote');
        },
        'FormatBlock': (_command, _ui, value) => {
          toggleFormat(isString(value) ? value : 'p');
        },
        'mceToggleFormat': (_command, _ui, value) => {
          toggleFormat(value);
        }
      });
    };
    const registerQueryValueCommands = editor => {
      const isFormatMatch = name => editor.formatter.match(name);
      editor.editorCommands.addCommands({
        'Bold,Italic,Underline,Strikethrough,Superscript,Subscript': command => isFormatMatch(command),
        'mceBlockQuote': () => isFormatMatch('blockquote')
      }, 'state');
      editor.editorCommands.addQueryValueHandler('FontName', () => fontNameQuery(editor));
      editor.editorCommands.addQueryValueHandler('FontSize', () => fontSizeQuery(editor));
      editor.editorCommands.addQueryValueHandler('LineHeight', () => lineHeightQuery(editor));
    };
    const registerCommands$8 = editor => {
      registerExecCommands$2(editor);
      registerQueryValueCommands(editor);
    };

    const registerCommands$7 = editor => {
      editor.editorCommands.addCommands({
        mceAddUndoLevel: () => {
          editor.undoManager.add();
        },
        mceEndUndoLevel: () => {
          editor.undoManager.add();
        },
        Undo: () => {
          editor.undoManager.undo();
        },
        Redo: () => {
          editor.undoManager.redo();
        }
      });
    };

    const registerCommands$6 = editor => {
      editor.editorCommands.addCommands({
        Indent: () => {
          indent(editor);
        },
        Outdent: () => {
          outdent(editor);
        }
      });
      editor.editorCommands.addCommands({ Outdent: () => canOutdent(editor) }, 'state');
    };

    const registerCommands$5 = editor => {
      const applyLinkToSelection = (_command, _ui, value) => {
        const linkDetails = isString(value) ? { href: value } : value;
        const anchor = editor.dom.getParent(editor.selection.getNode(), 'a');
        if (isObject(linkDetails) && isString(linkDetails.href)) {
          linkDetails.href = linkDetails.href.replace(/ /g, '%20');
          if (!anchor || !linkDetails.href) {
            editor.formatter.remove('link');
          }
          if (linkDetails.href) {
            editor.formatter.apply('link', linkDetails, anchor);
          }
        }
      };
      editor.editorCommands.addCommands({
        unlink: () => {
          if (editor.selection.isEditable()) {
            if (editor.selection.isCollapsed()) {
              const elm = editor.dom.getParent(editor.selection.getStart(), 'a');
              if (elm) {
                editor.dom.remove(elm, true);
              }
              return;
            }
            editor.formatter.remove('link');
          }
        },
        mceInsertLink: applyLinkToSelection,
        createLink: applyLinkToSelection
      });
    };

    const registerExecCommands$1 = editor => {
      editor.editorCommands.addCommands({
        'InsertUnorderedList,InsertOrderedList': command => {
          editor.getDoc().execCommand(command);
          const listElm = editor.dom.getParent(editor.selection.getNode(), 'ol,ul');
          if (listElm) {
            const listParent = listElm.parentNode;
            if (listParent && /^(H[1-6]|P|ADDRESS|PRE)$/.test(listParent.nodeName)) {
              const bm = editor.selection.getBookmark();
              editor.dom.split(listParent, listElm);
              editor.selection.moveToBookmark(bm);
            }
          }
        }
      });
    };
    const registerQueryStateCommands = editor => {
      editor.editorCommands.addCommands({
        'InsertUnorderedList,InsertOrderedList': command => {
          const list = editor.dom.getParent(editor.selection.getNode(), 'ul,ol');
          return list && (command === 'insertunorderedlist' && list.tagName === 'UL' || command === 'insertorderedlist' && list.tagName === 'OL');
        }
      }, 'state');
    };
    const registerCommands$4 = editor => {
      registerExecCommands$1(editor);
      registerQueryStateCommands(editor);
    };

    const getTopParentBlock = (editor, node, root, container) => {
      const dom = editor.dom;
      const selector = node => dom.isBlock(node) && node.parentElement === root;
      const topParentBlock = selector(node) ? node : dom.getParent(container, selector, root);
      return Optional.from(topParentBlock).map(SugarElement.fromDom);
    };
    const insert = (editor, before) => {
      const dom = editor.dom;
      const rng = editor.selection.getRng();
      const node = before ? editor.selection.getStart() : editor.selection.getEnd();
      const container = before ? rng.startContainer : rng.endContainer;
      const root = getEditableRoot(dom, container);
      if (!root || !root.isContentEditable) {
        return;
      }
      const insertFn = before ? before$3 : after$4;
      const newBlockName = getForcedRootBlock(editor);
      getTopParentBlock(editor, node, root, container).each(parentBlock => {
        const newBlock = createNewBlock(editor, container, parentBlock.dom, root, false, newBlockName);
        insertFn(parentBlock, SugarElement.fromDom(newBlock));
        editor.selection.setCursorLocation(newBlock, 0);
        editor.dispatch('NewBlock', { newBlock });
        fireInputEvent(editor, 'insertParagraph');
      });
    };
    const insertBefore = editor => insert(editor, true);
    const insertAfter = editor => insert(editor, false);

    const registerCommands$3 = editor => {
      editor.editorCommands.addCommands({
        InsertNewBlockBefore: () => {
          insertBefore(editor);
        },
        InsertNewBlockAfter: () => {
          insertAfter(editor);
        }
      });
    };

    const registerCommands$2 = editor => {
      editor.editorCommands.addCommands({
        insertParagraph: () => {
          insertBreak(blockbreak, editor);
        },
        mceInsertNewLine: (_command, _ui, value) => {
          insert$1(editor, value);
        },
        InsertLineBreak: (_command, _ui, _value) => {
          insertBreak(linebreak, editor);
        }
      });
    };

    const registerCommands$1 = editor => {
      editor.editorCommands.addCommands({
        mceSelectNodeDepth: (_command, _ui, value) => {
          let counter = 0;
          editor.dom.getParent(editor.selection.getNode(), node => {
            if (isElement$6(node) && counter++ === value) {
              editor.selection.select(node);
              return false;
            } else {
              return true;
            }
          }, editor.getBody());
        },
        mceSelectNode: (_command, _ui, value) => {
          editor.selection.select(value);
        },
        selectAll: () => {
          const editingHost = editor.dom.getParent(editor.selection.getStart(), isContentEditableTrue$3);
          if (editingHost) {
            const rng = editor.dom.createRng();
            rng.selectNodeContents(editingHost);
            editor.selection.setRng(rng);
          }
        }
      });
    };

    const registerExecCommands = editor => {
      editor.editorCommands.addCommands({
        mceRemoveNode: (_command, _ui, value) => {
          const node = value !== null && value !== void 0 ? value : editor.selection.getNode();
          if (node !== editor.getBody()) {
            const bm = editor.selection.getBookmark();
            editor.dom.remove(node, true);
            editor.selection.moveToBookmark(bm);
          }
        },
        mcePrint: () => {
          editor.getWin().print();
        },
        mceFocus: (_command, _ui, value) => {
          focus(editor, value === true);
        },
        mceToggleVisualAid: () => {
          editor.hasVisual = !editor.hasVisual;
          editor.addVisual();
        }
      });
    };
    const registerCommands = editor => {
      registerCommands$b(editor);
      registerCommands$a(editor);
      registerCommands$7(editor);
      registerCommands$1(editor);
      registerCommands$9(editor);
      registerCommands$5(editor);
      registerCommands$6(editor);
      registerCommands$3(editor);
      registerCommands$2(editor);
      registerCommands$4(editor);
      registerCommands$8(editor);
      registerExecCommands(editor);
    };

    const selectionSafeCommands = ['toggleview'];
    const isSelectionSafeCommand = command => contains$2(selectionSafeCommands, command.toLowerCase());
    class EditorCommands {
      constructor(editor) {
        this.commands = {
          state: {},
          exec: {},
          value: {}
        };
        this.editor = editor;
      }
      execCommand(command, ui = false, value, args) {
        const editor = this.editor;
        const lowerCaseCommand = command.toLowerCase();
        const skipFocus = args === null || args === void 0 ? void 0 : args.skip_focus;
        if (editor.removed) {
          return false;
        }
        if (lowerCaseCommand !== 'mcefocus') {
          if (!/^(mceAddUndoLevel|mceEndUndoLevel)$/i.test(lowerCaseCommand) && !skipFocus) {
            editor.focus();
          } else {
            restore(editor);
          }
        }
        const eventArgs = editor.dispatch('BeforeExecCommand', {
          command,
          ui,
          value
        });
        if (eventArgs.isDefaultPrevented()) {
          return false;
        }
        const func = this.commands.exec[lowerCaseCommand];
        if (isFunction(func)) {
          func(lowerCaseCommand, ui, value);
          editor.dispatch('ExecCommand', {
            command,
            ui,
            value
          });
          return true;
        }
        return false;
      }
      queryCommandState(command) {
        if (!isSelectionSafeCommand(command) && this.editor.quirks.isHidden() || this.editor.removed) {
          return false;
        }
        const lowerCaseCommand = command.toLowerCase();
        const func = this.commands.state[lowerCaseCommand];
        if (isFunction(func)) {
          return func(lowerCaseCommand);
        }
        return false;
      }
      queryCommandValue(command) {
        if (!isSelectionSafeCommand(command) && this.editor.quirks.isHidden() || this.editor.removed) {
          return '';
        }
        const lowerCaseCommand = command.toLowerCase();
        const func = this.commands.value[lowerCaseCommand];
        if (isFunction(func)) {
          return func(lowerCaseCommand);
        }
        return '';
      }
      addCommands(commandList, type = 'exec') {
        const commands = this.commands;
        each$d(commandList, (callback, command) => {
          each$e(command.toLowerCase().split(','), command => {
            commands[type][command] = callback;
          });
        });
      }
      addCommand(command, callback, scope) {
        const lowerCaseCommand = command.toLowerCase();
        this.commands.exec[lowerCaseCommand] = (_command, ui, value) => callback.call(scope !== null && scope !== void 0 ? scope : this.editor, ui, value);
      }
      queryCommandSupported(command) {
        const lowerCaseCommand = command.toLowerCase();
        if (this.commands.exec[lowerCaseCommand]) {
          return true;
        } else {
          return false;
        }
      }
      addQueryStateHandler(command, callback, scope) {
        this.commands.state[command.toLowerCase()] = () => callback.call(scope !== null && scope !== void 0 ? scope : this.editor);
      }
      addQueryValueHandler(command, callback, scope) {
        this.commands.value[command.toLowerCase()] = () => callback.call(scope !== null && scope !== void 0 ? scope : this.editor);
      }
    }

    const internalContentEditableAttr = 'data-mce-contenteditable';
    const toggleClass = (elm, cls, state) => {
      if (has(elm, cls) && !state) {
        remove$7(elm, cls);
      } else if (state) {
        add$2(elm, cls);
      }
    };
    const setEditorCommandState = (editor, cmd, state) => {
      try {
        editor.getDoc().execCommand(cmd, false, String(state));
      } catch (ex) {
      }
    };
    const setContentEditable = (elm, state) => {
      elm.dom.contentEditable = state ? 'true' : 'false';
    };
    const switchOffContentEditableTrue = elm => {
      each$e(descendants(elm, '*[contenteditable="true"]'), elm => {
        set$3(elm, internalContentEditableAttr, 'true');
        setContentEditable(elm, false);
      });
    };
    const switchOnContentEditableTrue = elm => {
      each$e(descendants(elm, `*[${ internalContentEditableAttr }="true"]`), elm => {
        remove$a(elm, internalContentEditableAttr);
        setContentEditable(elm, true);
      });
    };
    const removeFakeSelection = editor => {
      Optional.from(editor.selection.getNode()).each(elm => {
        elm.removeAttribute('data-mce-selected');
      });
    };
    const restoreFakeSelection = editor => {
      editor.selection.setRng(editor.selection.getRng());
    };
    const toggleReadOnly = (editor, state) => {
      const body = SugarElement.fromDom(editor.getBody());
      toggleClass(body, 'mce-content-readonly', state);
      if (state) {
        editor.selection.controlSelection.hideResizeRect();
        editor._selectionOverrides.hideFakeCaret();
        removeFakeSelection(editor);
        editor.readonly = true;
        setContentEditable(body, false);
        switchOffContentEditableTrue(body);
      } else {
        editor.readonly = false;
        if (editor.hasEditableRoot()) {
          setContentEditable(body, true);
        }
        switchOnContentEditableTrue(body);
        setEditorCommandState(editor, 'StyleWithCSS', false);
        setEditorCommandState(editor, 'enableInlineTableEditing', false);
        setEditorCommandState(editor, 'enableObjectResizing', false);
        if (hasEditorOrUiFocus(editor)) {
          editor.focus();
        }
        restoreFakeSelection(editor);
        editor.nodeChanged();
      }
    };
    const isReadOnly = editor => editor.readonly;
    const registerFilters = editor => {
      editor.parser.addAttributeFilter('contenteditable', nodes => {
        if (isReadOnly(editor)) {
          each$e(nodes, node => {
            node.attr(internalContentEditableAttr, node.attr('contenteditable'));
            node.attr('contenteditable', 'false');
          });
        }
      });
      editor.serializer.addAttributeFilter(internalContentEditableAttr, nodes => {
        if (isReadOnly(editor)) {
          each$e(nodes, node => {
            node.attr('contenteditable', node.attr(internalContentEditableAttr));
          });
        }
      });
      editor.serializer.addTempAttr(internalContentEditableAttr);
    };
    const registerReadOnlyContentFilters = editor => {
      if (editor.serializer) {
        registerFilters(editor);
      } else {
        editor.on('PreInit', () => {
          registerFilters(editor);
        });
      }
    };
    const isClickEvent = e => e.type === 'click';
    const allowedEvents = ['copy'];
    const isReadOnlyAllowedEvent = e => contains$2(allowedEvents, e.type);
    const getAnchorHrefOpt = (editor, elm) => {
      const isRoot = elm => eq(elm, SugarElement.fromDom(editor.getBody()));
      return closest$3(elm, 'a', isRoot).bind(a => getOpt(a, 'href'));
    };
    const processReadonlyEvents = (editor, e) => {
      if (isClickEvent(e) && !VK.metaKeyPressed(e)) {
        const elm = SugarElement.fromDom(e.target);
        getAnchorHrefOpt(editor, elm).each(href => {
          e.preventDefault();
          if (/^#/.test(href)) {
            const targetEl = editor.dom.select(`${ href },[name="${ removeLeading(href, '#') }"]`);
            if (targetEl.length) {
              editor.selection.scrollIntoView(targetEl[0], true);
            }
          } else {
            window.open(href, '_blank', 'rel=noopener noreferrer,menubar=yes,toolbar=yes,location=yes,status=yes,resizable=yes,scrollbars=yes');
          }
        });
      } else if (isReadOnlyAllowedEvent(e)) {
        editor.dispatch(e.type, e);
      }
    };
    const registerReadOnlySelectionBlockers = editor => {
      editor.on('ShowCaret', e => {
        if (isReadOnly(editor)) {
          e.preventDefault();
        }
      });
      editor.on('ObjectSelected', e => {
        if (isReadOnly(editor)) {
          e.preventDefault();
        }
      });
    };

    const nativeEvents = Tools.makeMap('focus blur focusin focusout click dblclick mousedown mouseup mousemove mouseover beforepaste paste cut copy selectionchange ' + 'mouseout mouseenter mouseleave wheel keydown keypress keyup input beforeinput contextmenu dragstart dragend dragover ' + 'draggesture dragdrop drop drag submit ' + 'compositionstart compositionend compositionupdate touchstart touchmove touchend touchcancel', ' ');
    class EventDispatcher {
      static isNative(name) {
        return !!nativeEvents[name.toLowerCase()];
      }
      constructor(settings) {
        this.bindings = {};
        this.settings = settings || {};
        this.scope = this.settings.scope || this;
        this.toggleEvent = this.settings.toggleEvent || never;
      }
      fire(name, args) {
        return this.dispatch(name, args);
      }
      dispatch(name, args) {
        const lcName = name.toLowerCase();
        const event = normalize$3(lcName, args !== null && args !== void 0 ? args : {}, this.scope);
        if (this.settings.beforeFire) {
          this.settings.beforeFire(event);
        }
        const handlers = this.bindings[lcName];
        if (handlers) {
          for (let i = 0, l = handlers.length; i < l; i++) {
            const callback = handlers[i];
            if (callback.removed) {
              continue;
            }
            if (callback.once) {
              this.off(lcName, callback.func);
            }
            if (event.isImmediatePropagationStopped()) {
              return event;
            }
            if (callback.func.call(this.scope, event) === false) {
              event.preventDefault();
              return event;
            }
          }
        }
        return event;
      }
      on(name, callback, prepend, extra) {
        if (callback === false) {
          callback = never;
        }
        if (callback) {
          const wrappedCallback = {
            func: callback,
            removed: false
          };
          if (extra) {
            Tools.extend(wrappedCallback, extra);
          }
          const names = name.toLowerCase().split(' ');
          let i = names.length;
          while (i--) {
            const currentName = names[i];
            let handlers = this.bindings[currentName];
            if (!handlers) {
              handlers = [];
              this.toggleEvent(currentName, true);
            }
            if (prepend) {
              handlers = [
                wrappedCallback,
                ...handlers
              ];
            } else {
              handlers = [
                ...handlers,
                wrappedCallback
              ];
            }
            this.bindings[currentName] = handlers;
          }
        }
        return this;
      }
      off(name, callback) {
        if (name) {
          const names = name.toLowerCase().split(' ');
          let i = names.length;
          while (i--) {
            const currentName = names[i];
            let handlers = this.bindings[currentName];
            if (!currentName) {
              each$d(this.bindings, (_value, bindingName) => {
                this.toggleEvent(bindingName, false);
                delete this.bindings[bindingName];
              });
              return this;
            }
            if (handlers) {
              if (!callback) {
                handlers.length = 0;
              } else {
                const filteredHandlers = partition$2(handlers, handler => handler.func === callback);
                handlers = filteredHandlers.fail;
                this.bindings[currentName] = handlers;
                each$e(filteredHandlers.pass, handler => {
                  handler.removed = true;
                });
              }
              if (!handlers.length) {
                this.toggleEvent(name, false);
                delete this.bindings[currentName];
              }
            }
          }
        } else {
          each$d(this.bindings, (_value, name) => {
            this.toggleEvent(name, false);
          });
          this.bindings = {};
        }
        return this;
      }
      once(name, callback, prepend) {
        return this.on(name, callback, prepend, { once: true });
      }
      has(name) {
        name = name.toLowerCase();
        const binding = this.bindings[name];
        return !(!binding || binding.length === 0);
      }
    }

    const getEventDispatcher = obj => {
      if (!obj._eventDispatcher) {
        obj._eventDispatcher = new EventDispatcher({
          scope: obj,
          toggleEvent: (name, state) => {
            if (EventDispatcher.isNative(name) && obj.toggleNativeEvent) {
              obj.toggleNativeEvent(name, state);
            }
          }
        });
      }
      return obj._eventDispatcher;
    };
    const Observable = {
      fire(name, args, bubble) {
        return this.dispatch(name, args, bubble);
      },
      dispatch(name, args, bubble) {
        const self = this;
        if (self.removed && name !== 'remove' && name !== 'detach') {
          return normalize$3(name.toLowerCase(), args !== null && args !== void 0 ? args : {}, self);
        }
        const dispatcherArgs = getEventDispatcher(self).dispatch(name, args);
        if (bubble !== false && self.parent) {
          let parent = self.parent();
          while (parent && !dispatcherArgs.isPropagationStopped()) {
            parent.dispatch(name, dispatcherArgs, false);
            parent = parent.parent ? parent.parent() : undefined;
          }
        }
        return dispatcherArgs;
      },
      on(name, callback, prepend) {
        return getEventDispatcher(this).on(name, callback, prepend);
      },
      off(name, callback) {
        return getEventDispatcher(this).off(name, callback);
      },
      once(name, callback) {
        return getEventDispatcher(this).once(name, callback);
      },
      hasEventListeners(name) {
        return getEventDispatcher(this).has(name);
      }
    };

    const DOM$2 = DOMUtils.DOM;
    let customEventRootDelegates;
    const getEventTarget = (editor, eventName) => {
      if (eventName === 'selectionchange') {
        return editor.getDoc();
      }
      if (!editor.inline && /^(?:mouse|touch|click|contextmenu|drop|dragover|dragend)/.test(eventName)) {
        return editor.getDoc().documentElement;
      }
      const eventRoot = getEventRoot(editor);
      if (eventRoot) {
        if (!editor.eventRoot) {
          editor.eventRoot = DOM$2.select(eventRoot)[0];
        }
        return editor.eventRoot;
      }
      return editor.getBody();
    };
    const isListening = editor => !editor.hidden && !isReadOnly(editor);
    const fireEvent = (editor, eventName, e) => {
      if (isListening(editor)) {
        editor.dispatch(eventName, e);
      } else if (isReadOnly(editor)) {
        processReadonlyEvents(editor, e);
      }
    };
    const bindEventDelegate = (editor, eventName) => {
      if (!editor.delegates) {
        editor.delegates = {};
      }
      if (editor.delegates[eventName] || editor.removed) {
        return;
      }
      const eventRootElm = getEventTarget(editor, eventName);
      if (getEventRoot(editor)) {
        if (!customEventRootDelegates) {
          customEventRootDelegates = {};
          editor.editorManager.on('removeEditor', () => {
            if (!editor.editorManager.activeEditor) {
              if (customEventRootDelegates) {
                each$d(customEventRootDelegates, (_value, name) => {
                  editor.dom.unbind(getEventTarget(editor, name));
                });
                customEventRootDelegates = null;
              }
            }
          });
        }
        if (customEventRootDelegates[eventName]) {
          return;
        }
        const delegate = e => {
          const target = e.target;
          const editors = editor.editorManager.get();
          let i = editors.length;
          while (i--) {
            const body = editors[i].getBody();
            if (body === target || DOM$2.isChildOf(target, body)) {
              fireEvent(editors[i], eventName, e);
            }
          }
        };
        customEventRootDelegates[eventName] = delegate;
        DOM$2.bind(eventRootElm, eventName, delegate);
      } else {
        const delegate = e => {
          fireEvent(editor, eventName, e);
        };
        DOM$2.bind(eventRootElm, eventName, delegate);
        editor.delegates[eventName] = delegate;
      }
    };
    const EditorObservable = {
      ...Observable,
      bindPendingEventDelegates() {
        const self = this;
        Tools.each(self._pendingNativeEvents, name => {
          bindEventDelegate(self, name);
        });
      },
      toggleNativeEvent(name, state) {
        const self = this;
        if (name === 'focus' || name === 'blur') {
          return;
        }
        if (self.removed) {
          return;
        }
        if (state) {
          if (self.initialized) {
            bindEventDelegate(self, name);
          } else {
            if (!self._pendingNativeEvents) {
              self._pendingNativeEvents = [name];
            } else {
              self._pendingNativeEvents.push(name);
            }
          }
        } else if (self.initialized && self.delegates) {
          self.dom.unbind(getEventTarget(self, name), name, self.delegates[name]);
          delete self.delegates[name];
        }
      },
      unbindAllNativeEvents() {
        const self = this;
        const body = self.getBody();
        const dom = self.dom;
        if (self.delegates) {
          each$d(self.delegates, (value, name) => {
            self.dom.unbind(getEventTarget(self, name), name, value);
          });
          delete self.delegates;
        }
        if (!self.inline && body && dom) {
          body.onload = null;
          dom.unbind(self.getWin());
          dom.unbind(self.getDoc());
        }
        if (dom) {
          dom.unbind(body);
          dom.unbind(self.getContainer());
        }
      }
    };

    const stringListProcessor = value => {
      if (isString(value)) {
        return {
          value: value.split(/[ ,]/),
          valid: true
        };
      } else if (isArrayOf(value, isString)) {
        return {
          value,
          valid: true
        };
      } else {
        return {
          valid: false,
          message: `The value must be a string[] or a comma/space separated string.`
        };
      }
    };
    const getBuiltInProcessor = type => {
      const validator = (() => {
        switch (type) {
        case 'array':
          return isArray$1;
        case 'boolean':
          return isBoolean;
        case 'function':
          return isFunction;
        case 'number':
          return isNumber;
        case 'object':
          return isObject;
        case 'string':
          return isString;
        case 'string[]':
          return stringListProcessor;
        case 'object[]':
          return val => isArrayOf(val, isObject);
        case 'regexp':
          return val => is$4(val, RegExp);
        default:
          return always;
        }
      })();
      return value => processValue(value, validator, `The value must be a ${ type }.`);
    };
    const isBuiltInSpec = spec => isString(spec.processor);
    const getErrorMessage = (message, result) => {
      const additionalText = isEmpty$3(result.message) ? '' : `. ${ result.message }`;
      return message + additionalText;
    };
    const isValidResult = result => result.valid;
    const processValue = (value, processor, message = '') => {
      const result = processor(value);
      if (isBoolean(result)) {
        return result ? {
          value: value,
          valid: true
        } : {
          valid: false,
          message
        };
      } else {
        return result;
      }
    };
    const processDefaultValue = (name, defaultValue, processor) => {
      if (!isUndefined(defaultValue)) {
        const result = processValue(defaultValue, processor);
        if (isValidResult(result)) {
          return result.value;
        } else {
          console.error(getErrorMessage(`Invalid default value passed for the "${ name }" option`, result));
        }
      }
      return undefined;
    };
    const create$5 = (editor, initialOptions) => {
      const registry = {};
      const values = {};
      const setValue = (name, value, processor) => {
        const result = processValue(value, processor);
        if (isValidResult(result)) {
          values[name] = result.value;
          return true;
        } else {
          console.warn(getErrorMessage(`Invalid value passed for the ${ name } option`, result));
          return false;
        }
      };
      const register = (name, spec) => {
        const processor = isBuiltInSpec(spec) ? getBuiltInProcessor(spec.processor) : spec.processor;
        const defaultValue = processDefaultValue(name, spec.default, processor);
        registry[name] = {
          ...spec,
          default: defaultValue,
          processor
        };
        const initValue = get$a(values, name).orThunk(() => get$a(initialOptions, name));
        initValue.each(value => setValue(name, value, processor));
      };
      const isRegistered = name => has$2(registry, name);
      const get = name => get$a(values, name).orThunk(() => get$a(registry, name).map(spec => spec.default)).getOrUndefined();
      const set = (name, value) => {
        if (!isRegistered(name)) {
          console.warn(`"${ name }" is not a registered option. Ensure the option has been registered before setting a value.`);
          return false;
        } else {
          const spec = registry[name];
          if (spec.immutable) {
            console.error(`"${ name }" is an immutable option and cannot be updated`);
            return false;
          } else {
            return setValue(name, value, spec.processor);
          }
        }
      };
      const unset = name => {
        const registered = isRegistered(name);
        if (registered) {
          delete values[name];
        }
        return registered;
      };
      const isSet = name => has$2(values, name);
      return {
        register,
        isRegistered,
        get,
        set,
        unset,
        isSet
      };
    };

    const defaultModes = [
      'design',
      'readonly'
    ];
    const switchToMode = (editor, activeMode, availableModes, mode) => {
      const oldMode = availableModes[activeMode.get()];
      const newMode = availableModes[mode];
      try {
        newMode.activate();
      } catch (e) {
        console.error(`problem while activating editor mode ${ mode }:`, e);
        return;
      }
      oldMode.deactivate();
      if (oldMode.editorReadOnly !== newMode.editorReadOnly) {
        toggleReadOnly(editor, newMode.editorReadOnly);
      }
      activeMode.set(mode);
      fireSwitchMode(editor, mode);
    };
    const setMode = (editor, availableModes, activeMode, mode) => {
      if (mode === activeMode.get()) {
        return;
      } else if (!has$2(availableModes, mode)) {
        throw new Error(`Editor mode '${ mode }' is invalid`);
      }
      if (editor.initialized) {
        switchToMode(editor, activeMode, availableModes, mode);
      } else {
        editor.on('init', () => switchToMode(editor, activeMode, availableModes, mode));
      }
    };
    const registerMode = (availableModes, mode, api) => {
      if (contains$2(defaultModes, mode)) {
        throw new Error(`Cannot override default mode ${ mode }`);
      }
      return {
        ...availableModes,
        [mode]: {
          ...api,
          deactivate: () => {
            try {
              api.deactivate();
            } catch (e) {
              console.error(`problem while deactivating editor mode ${ mode }:`, e);
            }
          }
        }
      };
    };

    const create$4 = editor => {
      const activeMode = Cell('design');
      const availableModes = Cell({
        design: {
          activate: noop,
          deactivate: noop,
          editorReadOnly: false
        },
        readonly: {
          activate: noop,
          deactivate: noop,
          editorReadOnly: true
        }
      });
      registerReadOnlyContentFilters(editor);
      registerReadOnlySelectionBlockers(editor);
      return {
        isReadOnly: () => isReadOnly(editor),
        set: mode => setMode(editor, availableModes.get(), activeMode, mode),
        get: () => activeMode.get(),
        register: (mode, api) => {
          availableModes.set(registerMode(availableModes.get(), mode, api));
        }
      };
    };

    const each$2 = Tools.each, explode = Tools.explode;
    const keyCodeLookup = {
      f1: 112,
      f2: 113,
      f3: 114,
      f4: 115,
      f5: 116,
      f6: 117,
      f7: 118,
      f8: 119,
      f9: 120,
      f10: 121,
      f11: 122,
      f12: 123
    };
    const modifierNames = Tools.makeMap('alt,ctrl,shift,meta,access');
    const isModifier = key => key in modifierNames;
    const parseShortcut = pattern => {
      const shortcut = {};
      const isMac = Env.os.isMacOS() || Env.os.isiOS();
      each$2(explode(pattern.toLowerCase(), '+'), value => {
        if (isModifier(value)) {
          shortcut[value] = true;
        } else {
          if (/^[0-9]{2,}$/.test(value)) {
            shortcut.keyCode = parseInt(value, 10);
          } else {
            shortcut.charCode = value.charCodeAt(0);
            shortcut.keyCode = keyCodeLookup[value] || value.toUpperCase().charCodeAt(0);
          }
        }
      });
      const id = [shortcut.keyCode];
      let key;
      for (key in modifierNames) {
        if (shortcut[key]) {
          id.push(key);
        } else {
          shortcut[key] = false;
        }
      }
      shortcut.id = id.join(',');
      if (shortcut.access) {
        shortcut.alt = true;
        if (isMac) {
          shortcut.ctrl = true;
        } else {
          shortcut.shift = true;
        }
      }
      if (shortcut.meta) {
        if (isMac) {
          shortcut.meta = true;
        } else {
          shortcut.ctrl = true;
          shortcut.meta = false;
        }
      }
      return shortcut;
    };
    class Shortcuts {
      constructor(editor) {
        this.shortcuts = {};
        this.pendingPatterns = [];
        this.editor = editor;
        const self = this;
        editor.on('keyup keypress keydown', e => {
          if ((self.hasModifier(e) || self.isFunctionKey(e)) && !e.isDefaultPrevented()) {
            each$2(self.shortcuts, shortcut => {
              if (self.matchShortcut(e, shortcut)) {
                self.pendingPatterns = shortcut.subpatterns.slice(0);
                if (e.type === 'keydown') {
                  self.executeShortcutAction(shortcut);
                }
              }
            });
            if (self.matchShortcut(e, self.pendingPatterns[0])) {
              if (self.pendingPatterns.length === 1) {
                if (e.type === 'keydown') {
                  self.executeShortcutAction(self.pendingPatterns[0]);
                }
              }
              self.pendingPatterns.shift();
            }
          }
        });
      }
      add(pattern, desc, cmdFunc, scope) {
        const self = this;
        const func = self.normalizeCommandFunc(cmdFunc);
        each$2(explode(Tools.trim(pattern)), pattern => {
          const shortcut = self.createShortcut(pattern, desc, func, scope);
          self.shortcuts[shortcut.id] = shortcut;
        });
        return true;
      }
      remove(pattern) {
        const shortcut = this.createShortcut(pattern);
        if (this.shortcuts[shortcut.id]) {
          delete this.shortcuts[shortcut.id];
          return true;
        }
        return false;
      }
      normalizeCommandFunc(cmdFunc) {
        const self = this;
        const cmd = cmdFunc;
        if (typeof cmd === 'string') {
          return () => {
            self.editor.execCommand(cmd, false, null);
          };
        } else if (Tools.isArray(cmd)) {
          return () => {
            self.editor.execCommand(cmd[0], cmd[1], cmd[2]);
          };
        } else {
          return cmd;
        }
      }
      createShortcut(pattern, desc, cmdFunc, scope) {
        const shortcuts = Tools.map(explode(pattern, '>'), parseShortcut);
        shortcuts[shortcuts.length - 1] = Tools.extend(shortcuts[shortcuts.length - 1], {
          func: cmdFunc,
          scope: scope || this.editor
        });
        return Tools.extend(shortcuts[0], {
          desc: this.editor.translate(desc),
          subpatterns: shortcuts.slice(1)
        });
      }
      hasModifier(e) {
        return e.altKey || e.ctrlKey || e.metaKey;
      }
      isFunctionKey(e) {
        return e.type === 'keydown' && e.keyCode >= 112 && e.keyCode <= 123;
      }
      matchShortcut(e, shortcut) {
        if (!shortcut) {
          return false;
        }
        if (shortcut.ctrl !== e.ctrlKey || shortcut.meta !== e.metaKey) {
          return false;
        }
        if (shortcut.alt !== e.altKey || shortcut.shift !== e.shiftKey) {
          return false;
        }
        if (e.keyCode === shortcut.keyCode || e.charCode && e.charCode === shortcut.charCode) {
          e.preventDefault();
          return true;
        }
        return false;
      }
      executeShortcutAction(shortcut) {
        return shortcut.func ? shortcut.func.call(shortcut.scope) : null;
      }
    }

    const create$3 = () => {
      const buttons = {};
      const menuItems = {};
      const popups = {};
      const icons = {};
      const contextMenus = {};
      const contextToolbars = {};
      const sidebars = {};
      const views = {};
      const add = (collection, type) => (name, spec) => {
        collection[name.toLowerCase()] = {
          ...spec,
          type
        };
      };
      const addIcon = (name, svgData) => icons[name.toLowerCase()] = svgData;
      return {
        addButton: add(buttons, 'button'),
        addGroupToolbarButton: add(buttons, 'grouptoolbarbutton'),
        addToggleButton: add(buttons, 'togglebutton'),
        addMenuButton: add(buttons, 'menubutton'),
        addSplitButton: add(buttons, 'splitbutton'),
        addMenuItem: add(menuItems, 'menuitem'),
        addNestedMenuItem: add(menuItems, 'nestedmenuitem'),
        addToggleMenuItem: add(menuItems, 'togglemenuitem'),
        addAutocompleter: add(popups, 'autocompleter'),
        addContextMenu: add(contextMenus, 'contextmenu'),
        addContextToolbar: add(contextToolbars, 'contexttoolbar'),
        addContextForm: add(contextToolbars, 'contextform'),
        addSidebar: add(sidebars, 'sidebar'),
        addView: add(views, 'views'),
        addIcon,
        getAll: () => ({
          buttons,
          menuItems,
          icons,
          popups,
          contextMenus,
          contextToolbars,
          sidebars,
          views
        })
      };
    };

    const registry = () => {
      const bridge = create$3();
      return {
        addAutocompleter: bridge.addAutocompleter,
        addButton: bridge.addButton,
        addContextForm: bridge.addContextForm,
        addContextMenu: bridge.addContextMenu,
        addContextToolbar: bridge.addContextToolbar,
        addIcon: bridge.addIcon,
        addMenuButton: bridge.addMenuButton,
        addMenuItem: bridge.addMenuItem,
        addNestedMenuItem: bridge.addNestedMenuItem,
        addSidebar: bridge.addSidebar,
        addSplitButton: bridge.addSplitButton,
        addToggleButton: bridge.addToggleButton,
        addGroupToolbarButton: bridge.addGroupToolbarButton,
        addToggleMenuItem: bridge.addToggleMenuItem,
        addView: bridge.addView,
        getAll: bridge.getAll
      };
    };

    const DOM$1 = DOMUtils.DOM;
    const extend = Tools.extend, each$1 = Tools.each;
    class Editor {
      constructor(id, options, editorManager) {
        this.plugins = {};
        this.contentCSS = [];
        this.contentStyles = [];
        this.loadedCSS = {};
        this.isNotDirty = false;
        this.composing = false;
        this.destroyed = false;
        this.hasHiddenInput = false;
        this.iframeElement = null;
        this.initialized = false;
        this.readonly = false;
        this.removed = false;
        this.startContent = '';
        this._pendingNativeEvents = [];
        this._skinLoaded = false;
        this._editableRoot = true;
        this.editorManager = editorManager;
        this.documentBaseUrl = editorManager.documentBaseURL;
        extend(this, EditorObservable);
        const self = this;
        this.id = id;
        this.hidden = false;
        const normalizedOptions = normalizeOptions(editorManager.defaultOptions, options);
        this.options = create$5(self, normalizedOptions);
        register$7(self);
        const getOption = this.options.get;
        if (getOption('deprecation_warnings')) {
          logWarnings(options, normalizedOptions);
        }
        const suffix = getOption('suffix');
        if (suffix) {
          editorManager.suffix = suffix;
        }
        this.suffix = editorManager.suffix;
        const baseUrl = getOption('base_url');
        if (baseUrl) {
          editorManager._setBaseUrl(baseUrl);
        }
        this.baseUri = editorManager.baseURI;
        const referrerPolicy = getReferrerPolicy(self);
        if (referrerPolicy) {
          ScriptLoader.ScriptLoader._setReferrerPolicy(referrerPolicy);
          DOMUtils.DOM.styleSheetLoader._setReferrerPolicy(referrerPolicy);
        }
        const contentCssCors = hasContentCssCors(self);
        if (isNonNullable(contentCssCors)) {
          DOMUtils.DOM.styleSheetLoader._setContentCssCors(contentCssCors);
        }
        AddOnManager.languageLoad = getOption('language_load');
        AddOnManager.baseURL = editorManager.baseURL;
        this.setDirty(false);
        this.documentBaseURI = new URI(getDocumentBaseUrl(self), { base_uri: this.baseUri });
        this.baseURI = this.baseUri;
        this.inline = isInline$1(self);
        this.hasVisual = isVisualAidsEnabled(self);
        this.shortcuts = new Shortcuts(this);
        this.editorCommands = new EditorCommands(this);
        registerCommands(this);
        const cacheSuffix = getOption('cache_suffix');
        if (cacheSuffix) {
          Env.cacheSuffix = cacheSuffix.replace(/^[\?\&]+/, '');
        }
        this.ui = {
          registry: registry(),
          styleSheetLoader: undefined,
          show: noop,
          hide: noop,
          setEnabled: noop,
          isEnabled: always
        };
        this.mode = create$4(self);
        editorManager.dispatch('SetupEditor', { editor: this });
        const setupCallback = getSetupCallback(self);
        if (isFunction(setupCallback)) {
          setupCallback.call(self, self);
        }
      }
      render() {
        render(this);
      }
      focus(skipFocus) {
        this.execCommand('mceFocus', false, skipFocus);
      }
      hasFocus() {
        return hasFocus(this);
      }
      translate(text) {
        return I18n.translate(text);
      }
      getParam(name, defaultVal, type) {
        const options = this.options;
        if (!options.isRegistered(name)) {
          if (isNonNullable(type)) {
            options.register(name, {
              processor: type,
              default: defaultVal
            });
          } else {
            options.register(name, {
              processor: always,
              default: defaultVal
            });
          }
        }
        return !options.isSet(name) && !isUndefined(defaultVal) ? defaultVal : options.get(name);
      }
      hasPlugin(name, loaded) {
        const hasPlugin = contains$2(getPlugins(this), name);
        if (hasPlugin) {
          return loaded ? PluginManager.get(name) !== undefined : true;
        } else {
          return false;
        }
      }
      nodeChanged(args) {
        this._nodeChangeDispatcher.nodeChanged(args);
      }
      addCommand(name, callback, scope) {
        this.editorCommands.addCommand(name, callback, scope);
      }
      addQueryStateHandler(name, callback, scope) {
        this.editorCommands.addQueryStateHandler(name, callback, scope);
      }
      addQueryValueHandler(name, callback, scope) {
        this.editorCommands.addQueryValueHandler(name, callback, scope);
      }
      addShortcut(pattern, desc, cmdFunc, scope) {
        this.shortcuts.add(pattern, desc, cmdFunc, scope);
      }
      execCommand(cmd, ui, value, args) {
        return this.editorCommands.execCommand(cmd, ui, value, args);
      }
      queryCommandState(cmd) {
        return this.editorCommands.queryCommandState(cmd);
      }
      queryCommandValue(cmd) {
        return this.editorCommands.queryCommandValue(cmd);
      }
      queryCommandSupported(cmd) {
        return this.editorCommands.queryCommandSupported(cmd);
      }
      show() {
        const self = this;
        if (self.hidden) {
          self.hidden = false;
          if (self.inline) {
            self.getBody().contentEditable = 'true';
          } else {
            DOM$1.show(self.getContainer());
            DOM$1.hide(self.id);
          }
          self.load();
          self.dispatch('show');
        }
      }
      hide() {
        const self = this;
        if (!self.hidden) {
          self.save();
          if (self.inline) {
            self.getBody().contentEditable = 'false';
            if (self === self.editorManager.focusedEditor) {
              self.editorManager.focusedEditor = null;
            }
          } else {
            DOM$1.hide(self.getContainer());
            DOM$1.setStyle(self.id, 'display', self.orgDisplay);
          }
          self.hidden = true;
          self.dispatch('hide');
        }
      }
      isHidden() {
        return this.hidden;
      }
      setProgressState(state, time) {
        this.dispatch('ProgressState', {
          state,
          time
        });
      }
      load(args = {}) {
        const self = this;
        const elm = self.getElement();
        if (self.removed) {
          return '';
        }
        if (elm) {
          const loadArgs = {
            ...args,
            load: true
          };
          const value = isTextareaOrInput(elm) ? elm.value : elm.innerHTML;
          const html = self.setContent(value, loadArgs);
          if (!loadArgs.no_events) {
            self.dispatch('LoadContent', {
              ...loadArgs,
              element: elm
            });
          }
          return html;
        } else {
          return '';
        }
      }
      save(args = {}) {
        const self = this;
        let elm = self.getElement();
        if (!elm || !self.initialized || self.removed) {
          return '';
        }
        const getArgs = {
          ...args,
          save: true,
          element: elm
        };
        let html = self.getContent(getArgs);
        const saveArgs = {
          ...getArgs,
          content: html
        };
        if (!saveArgs.no_events) {
          self.dispatch('SaveContent', saveArgs);
        }
        if (saveArgs.format === 'raw') {
          self.dispatch('RawSaveContent', saveArgs);
        }
        html = saveArgs.content;
        if (!isTextareaOrInput(elm)) {
          if (args.is_removing || !self.inline) {
            elm.innerHTML = html;
          }
          const form = DOM$1.getParent(self.id, 'form');
          if (form) {
            each$1(form.elements, elm => {
              if (elm.name === self.id) {
                elm.value = html;
                return false;
              } else {
                return true;
              }
            });
          }
        } else {
          elm.value = html;
        }
        saveArgs.element = getArgs.element = elm = null;
        if (saveArgs.set_dirty !== false) {
          self.setDirty(false);
        }
        return html;
      }
      setContent(content, args) {
        return setContent(this, content, args);
      }
      getContent(args) {
        return getContent(this, args);
      }
      insertContent(content, args) {
        if (args) {
          content = extend({ content }, args);
        }
        this.execCommand('mceInsertContent', false, content);
      }
      resetContent(initialContent) {
        if (initialContent === undefined) {
          setContent(this, this.startContent, { format: 'raw' });
        } else {
          setContent(this, initialContent);
        }
        this.undoManager.reset();
        this.setDirty(false);
        this.nodeChanged();
      }
      isDirty() {
        return !this.isNotDirty;
      }
      setDirty(state) {
        const oldState = !this.isNotDirty;
        this.isNotDirty = !state;
        if (state && state !== oldState) {
          this.dispatch('dirty');
        }
      }
      getContainer() {
        const self = this;
        if (!self.container) {
          self.container = self.editorContainer || DOM$1.get(self.id + '_parent');
        }
        return self.container;
      }
      getContentAreaContainer() {
        return this.contentAreaContainer;
      }
      getElement() {
        if (!this.targetElm) {
          this.targetElm = DOM$1.get(this.id);
        }
        return this.targetElm;
      }
      getWin() {
        const self = this;
        if (!self.contentWindow) {
          const elm = self.iframeElement;
          if (elm) {
            self.contentWindow = elm.contentWindow;
          }
        }
        return self.contentWindow;
      }
      getDoc() {
        const self = this;
        if (!self.contentDocument) {
          const win = self.getWin();
          if (win) {
            self.contentDocument = win.document;
          }
        }
        return self.contentDocument;
      }
      getBody() {
        var _a, _b;
        const doc = this.getDoc();
        return (_b = (_a = this.bodyElement) !== null && _a !== void 0 ? _a : doc === null || doc === void 0 ? void 0 : doc.body) !== null && _b !== void 0 ? _b : null;
      }
      convertURL(url, name, elm) {
        const self = this, getOption = self.options.get;
        const urlConverterCallback = getUrlConverterCallback(self);
        if (isFunction(urlConverterCallback)) {
          return urlConverterCallback.call(self, url, elm, true, name);
        }
        if (!getOption('convert_urls') || elm === 'link' || isObject(elm) && elm.nodeName === 'LINK' || url.indexOf('file:') === 0 || url.length === 0) {
          return url;
        }
        const urlObject = new URI(url);
        if (urlObject.protocol !== 'http' && urlObject.protocol !== 'https' && urlObject.protocol !== '') {
          return url;
        }
        if (getOption('relative_urls')) {
          return self.documentBaseURI.toRelative(url);
        }
        url = self.documentBaseURI.toAbsolute(url, getOption('remove_script_host'));
        return url;
      }
      addVisual(elm) {
        addVisual(this, elm);
      }
      setEditableRoot(state) {
        setEditableRoot(this, state);
      }
      hasEditableRoot() {
        return hasEditableRoot(this);
      }
      remove() {
        remove$1(this);
      }
      destroy(automatic) {
        destroy(this, automatic);
      }
      uploadImages() {
        return this.editorUpload.uploadImages();
      }
      _scanForImages() {
        return this.editorUpload.scanForImages();
      }
    }

    const DOM = DOMUtils.DOM;
    const each = Tools.each;
    let boundGlobalEvents = false;
    let beforeUnloadDelegate;
    let editors = [];
    const globalEventDelegate = e => {
      const type = e.type;
      each(EditorManager.get(), editor => {
        switch (type) {
        case 'scroll':
          editor.dispatch('ScrollWindow', e);
          break;
        case 'resize':
          editor.dispatch('ResizeWindow', e);
          break;
        }
      });
    };
    const toggleGlobalEvents = state => {
      if (state !== boundGlobalEvents) {
        const DOM = DOMUtils.DOM;
        if (state) {
          DOM.bind(window, 'resize', globalEventDelegate);
          DOM.bind(window, 'scroll', globalEventDelegate);
        } else {
          DOM.unbind(window, 'resize', globalEventDelegate);
          DOM.unbind(window, 'scroll', globalEventDelegate);
        }
        boundGlobalEvents = state;
      }
    };
    const removeEditorFromList = targetEditor => {
      const oldEditors = editors;
      editors = filter$5(editors, editor => {
        return targetEditor !== editor;
      });
      if (EditorManager.activeEditor === targetEditor) {
        EditorManager.activeEditor = editors.length > 0 ? editors[0] : null;
      }
      if (EditorManager.focusedEditor === targetEditor) {
        EditorManager.focusedEditor = null;
      }
      return oldEditors.length !== editors.length;
    };
    const purgeDestroyedEditor = editor => {
      if (editor && editor.initialized && !(editor.getContainer() || editor.getBody()).parentNode) {
        removeEditorFromList(editor);
        editor.unbindAllNativeEvents();
        editor.destroy(true);
        editor.removed = true;
      }
    };
    const isQuirksMode = document.compatMode !== 'CSS1Compat';
    const EditorManager = {
      ...Observable,
      baseURI: null,
      baseURL: null,
      defaultOptions: {},
      documentBaseURL: null,
      suffix: null,
      majorVersion: '6',
      minorVersion: '8.5',
      releaseDate: 'TBD',
      i18n: I18n,
      activeEditor: null,
      focusedEditor: null,
      setup() {
        const self = this;
        let baseURL = '';
        let suffix = '';
        let documentBaseURL = URI.getDocumentBaseUrl(document.location);
        if (/^[^:]+:\/\/\/?[^\/]+\//.test(documentBaseURL)) {
          documentBaseURL = documentBaseURL.replace(/[\?#].*$/, '').replace(/[\/\\][^\/]+$/, '');
          if (!/[\/\\]$/.test(documentBaseURL)) {
            documentBaseURL += '/';
          }
        }
        const preInit = window.tinymce || window.tinyMCEPreInit;
        if (preInit) {
          baseURL = preInit.base || preInit.baseURL;
          suffix = preInit.suffix;
        } else {
          const scripts = document.getElementsByTagName('script');
          for (let i = 0; i < scripts.length; i++) {
            const src = scripts[i].src || '';
            if (src === '') {
              continue;
            }
            const srcScript = src.substring(src.lastIndexOf('/'));
            if (/tinymce(\.full|\.jquery|)(\.min|\.dev|)\.js/.test(src)) {
              if (srcScript.indexOf('.min') !== -1) {
                suffix = '.min';
              }
              baseURL = src.substring(0, src.lastIndexOf('/'));
              break;
            }
          }
          if (!baseURL && document.currentScript) {
            const src = document.currentScript.src;
            if (src.indexOf('.min') !== -1) {
              suffix = '.min';
            }
            baseURL = src.substring(0, src.lastIndexOf('/'));
          }
        }
        self.baseURL = new URI(documentBaseURL).toAbsolute(baseURL);
        self.documentBaseURL = documentBaseURL;
        self.baseURI = new URI(self.baseURL);
        self.suffix = suffix;
        setup$w(self);
      },
      overrideDefaults(defaultOptions) {
        const baseUrl = defaultOptions.base_url;
        if (baseUrl) {
          this._setBaseUrl(baseUrl);
        }
        const suffix = defaultOptions.suffix;
        if (suffix) {
          this.suffix = suffix;
        }
        this.defaultOptions = defaultOptions;
        const pluginBaseUrls = defaultOptions.plugin_base_urls;
        if (pluginBaseUrls !== undefined) {
          each$d(pluginBaseUrls, (pluginBaseUrl, pluginName) => {
            AddOnManager.PluginManager.urls[pluginName] = pluginBaseUrl;
          });
        }
      },
      init(options) {
        const self = this;
        let result;
        const invalidInlineTargets = Tools.makeMap('area base basefont br col frame hr img input isindex link meta param embed source wbr track ' + 'colgroup option table tbody tfoot thead tr th td script noscript style textarea video audio iframe object menu', ' ');
        const isInvalidInlineTarget = (options, elm) => options.inline && elm.tagName.toLowerCase() in invalidInlineTargets;
        const createId = elm => {
          let id = elm.id;
          if (!id) {
            id = get$a(elm, 'name').filter(name => !DOM.get(name)).getOrThunk(DOM.uniqueId);
            elm.setAttribute('id', id);
          }
          return id;
        };
        const execCallback = name => {
          const callback = options[name];
          if (!callback) {
            return;
          }
          return callback.apply(self, []);
        };
        const findTargets = options => {
          if (Env.browser.isIE() || Env.browser.isEdge()) {
            initError('TinyMCE does not support the browser you are using. For a list of supported' + ' browsers please see: https://www.tiny.cloud/docs/tinymce/6/support/#supportedwebbrowsers');
            return [];
          } else if (isQuirksMode) {
            initError('Failed to initialize the editor as the document is not in standards mode. ' + 'TinyMCE requires standards mode.');
            return [];
          } else if (isString(options.selector)) {
            return DOM.select(options.selector);
          } else if (isNonNullable(options.target)) {
            return [options.target];
          } else {
            return [];
          }
        };
        let provideResults = editors => {
          result = editors;
        };
        const initEditors = () => {
          let initCount = 0;
          const editors = [];
          let targets;
          const createEditor = (id, options, targetElm) => {
            const editor = new Editor(id, options, self);
            editors.push(editor);
            editor.on('init', () => {
              if (++initCount === targets.length) {
                provideResults(editors);
              }
            });
            editor.targetElm = editor.targetElm || targetElm;
            editor.render();
          };
          DOM.unbind(window, 'ready', initEditors);
          execCallback('onpageload');
          targets = unique$1(findTargets(options));
          Tools.each(targets, elm => {
            purgeDestroyedEditor(self.get(elm.id));
          });
          targets = Tools.grep(targets, elm => {
            return !self.get(elm.id);
          });
          if (targets.length === 0) {
            provideResults([]);
          } else {
            each(targets, elm => {
              if (isInvalidInlineTarget(options, elm)) {
                initError('Could not initialize inline editor on invalid inline target element', elm);
              } else {
                createEditor(createId(elm), options, elm);
              }
            });
          }
        };
        DOM.bind(window, 'ready', initEditors);
        return new Promise(resolve => {
          if (result) {
            resolve(result);
          } else {
            provideResults = editors => {
              resolve(editors);
            };
          }
        });
      },
      get(id) {
        if (arguments.length === 0) {
          return editors.slice(0);
        } else if (isString(id)) {
          return find$2(editors, editor => {
            return editor.id === id;
          }).getOr(null);
        } else if (isNumber(id)) {
          return editors[id] ? editors[id] : null;
        } else {
          return null;
        }
      },
      add(editor) {
        const self = this;
        const existingEditor = self.get(editor.id);
        if (existingEditor === editor) {
          return editor;
        }
        if (existingEditor === null) {
          editors.push(editor);
        }
        toggleGlobalEvents(true);
        self.activeEditor = editor;
        self.dispatch('AddEditor', { editor });
        if (!beforeUnloadDelegate) {
          beforeUnloadDelegate = e => {
            const event = self.dispatch('BeforeUnload');
            if (event.returnValue) {
              e.preventDefault();
              e.returnValue = event.returnValue;
              return event.returnValue;
            }
          };
          window.addEventListener('beforeunload', beforeUnloadDelegate);
        }
        return editor;
      },
      createEditor(id, options) {
        return this.add(new Editor(id, options, this));
      },
      remove(selector) {
        const self = this;
        let editor;
        if (!selector) {
          for (let i = editors.length - 1; i >= 0; i--) {
            self.remove(editors[i]);
          }
          return;
        }
        if (isString(selector)) {
          each(DOM.select(selector), elm => {
            editor = self.get(elm.id);
            if (editor) {
              self.remove(editor);
            }
          });
          return;
        }
        editor = selector;
        if (isNull(self.get(editor.id))) {
          return null;
        }
        if (removeEditorFromList(editor)) {
          self.dispatch('RemoveEditor', { editor });
        }
        if (editors.length === 0) {
          window.removeEventListener('beforeunload', beforeUnloadDelegate);
        }
        editor.remove();
        toggleGlobalEvents(editors.length > 0);
        return editor;
      },
      execCommand(cmd, ui, value) {
        var _a;
        const self = this;
        const editorId = isObject(value) ? (_a = value.id) !== null && _a !== void 0 ? _a : value.index : value;
        switch (cmd) {
        case 'mceAddEditor': {
            if (!self.get(editorId)) {
              const editorOptions = value.options;
              new Editor(editorId, editorOptions, self).render();
            }
            return true;
          }
        case 'mceRemoveEditor': {
            const editor = self.get(editorId);
            if (editor) {
              editor.remove();
            }
            return true;
          }
        case 'mceToggleEditor': {
            const editor = self.get(editorId);
            if (!editor) {
              self.execCommand('mceAddEditor', false, value);
              return true;
            }
            if (editor.isHidden()) {
              editor.show();
            } else {
              editor.hide();
            }
            return true;
          }
        }
        if (self.activeEditor) {
          return self.activeEditor.execCommand(cmd, ui, value);
        }
        return false;
      },
      triggerSave: () => {
        each(editors, editor => {
          editor.save();
        });
      },
      addI18n: (code, items) => {
        I18n.add(code, items);
      },
      translate: text => {
        return I18n.translate(text);
      },
      setActive(editor) {
        const activeEditor = this.activeEditor;
        if (this.activeEditor !== editor) {
          if (activeEditor) {
            activeEditor.dispatch('deactivate', { relatedTarget: editor });
          }
          editor.dispatch('activate', { relatedTarget: activeEditor });
        }
        this.activeEditor = editor;
      },
      _setBaseUrl(baseUrl) {
        this.baseURL = new URI(this.documentBaseURL).toAbsolute(baseUrl.replace(/\/+$/, ''));
        this.baseURI = new URI(this.baseURL);
      }
    };
    EditorManager.setup();

    const setup = () => {
      const dataValue = value$2();
      const FakeClipboardItem = items => ({
        items,
        types: keys(items),
        getType: type => get$a(items, type).getOrUndefined()
      });
      const write = data => {
        dataValue.set(data);
      };
      const read = () => dataValue.get().getOrUndefined();
      const clear = dataValue.clear;
      return {
        FakeClipboardItem,
        write,
        read,
        clear
      };
    };
    const FakeClipboard = setup();

    const min = Math.min, max = Math.max, round = Math.round;
    const relativePosition = (rect, targetRect, rel) => {
      let x = targetRect.x;
      let y = targetRect.y;
      const w = rect.w;
      const h = rect.h;
      const targetW = targetRect.w;
      const targetH = targetRect.h;
      const relChars = (rel || '').split('');
      if (relChars[0] === 'b') {
        y += targetH;
      }
      if (relChars[1] === 'r') {
        x += targetW;
      }
      if (relChars[0] === 'c') {
        y += round(targetH / 2);
      }
      if (relChars[1] === 'c') {
        x += round(targetW / 2);
      }
      if (relChars[3] === 'b') {
        y -= h;
      }
      if (relChars[4] === 'r') {
        x -= w;
      }
      if (relChars[3] === 'c') {
        y -= round(h / 2);
      }
      if (relChars[4] === 'c') {
        x -= round(w / 2);
      }
      return create$2(x, y, w, h);
    };
    const findBestRelativePosition = (rect, targetRect, constrainRect, rels) => {
      for (let i = 0; i < rels.length; i++) {
        const pos = relativePosition(rect, targetRect, rels[i]);
        if (pos.x >= constrainRect.x && pos.x + pos.w <= constrainRect.w + constrainRect.x && pos.y >= constrainRect.y && pos.y + pos.h <= constrainRect.h + constrainRect.y) {
          return rels[i];
        }
      }
      return null;
    };
    const inflate = (rect, w, h) => {
      return create$2(rect.x - w, rect.y - h, rect.w + w * 2, rect.h + h * 2);
    };
    const intersect = (rect, cropRect) => {
      const x1 = max(rect.x, cropRect.x);
      const y1 = max(rect.y, cropRect.y);
      const x2 = min(rect.x + rect.w, cropRect.x + cropRect.w);
      const y2 = min(rect.y + rect.h, cropRect.y + cropRect.h);
      if (x2 - x1 < 0 || y2 - y1 < 0) {
        return null;
      }
      return create$2(x1, y1, x2 - x1, y2 - y1);
    };
    const clamp = (rect, clampRect, fixedSize) => {
      let x1 = rect.x;
      let y1 = rect.y;
      let x2 = rect.x + rect.w;
      let y2 = rect.y + rect.h;
      const cx2 = clampRect.x + clampRect.w;
      const cy2 = clampRect.y + clampRect.h;
      const underflowX1 = max(0, clampRect.x - x1);
      const underflowY1 = max(0, clampRect.y - y1);
      const overflowX2 = max(0, x2 - cx2);
      const overflowY2 = max(0, y2 - cy2);
      x1 += underflowX1;
      y1 += underflowY1;
      if (fixedSize) {
        x2 += underflowX1;
        y2 += underflowY1;
        x1 -= overflowX2;
        y1 -= overflowY2;
      }
      x2 -= overflowX2;
      y2 -= overflowY2;
      return create$2(x1, y1, x2 - x1, y2 - y1);
    };
    const create$2 = (x, y, w, h) => {
      return {
        x,
        y,
        w,
        h
      };
    };
    const fromClientRect = clientRect => {
      return create$2(clientRect.left, clientRect.top, clientRect.width, clientRect.height);
    };
    const Rect = {
      inflate,
      relativePosition,
      findBestRelativePosition,
      intersect,
      clamp,
      create: create$2,
      fromClientRect
    };

    const awaiter = (resolveCb, rejectCb, timeout = 1000) => {
      let done = false;
      let timer = null;
      const complete = completer => (...args) => {
        if (!done) {
          done = true;
          if (timer !== null) {
            clearTimeout(timer);
            timer = null;
          }
          completer.apply(null, args);
        }
      };
      const resolve = complete(resolveCb);
      const reject = complete(rejectCb);
      const start = (...args) => {
        if (!done && timer === null) {
          timer = setTimeout(() => reject.apply(null, args), timeout);
        }
      };
      return {
        start,
        resolve,
        reject
      };
    };
    const create$1 = () => {
      const tasks = {};
      const resultFns = {};
      const resources = {};
      const load = (id, url) => {
        const loadErrMsg = `Script at URL "${ url }" failed to load`;
        const runErrMsg = `Script at URL "${ url }" did not call \`tinymce.Resource.add('${ id }', data)\` within 1 second`;
        if (tasks[id] !== undefined) {
          return tasks[id];
        } else {
          const task = new Promise((resolve, reject) => {
            const waiter = awaiter(resolve, reject);
            resultFns[id] = waiter.resolve;
            ScriptLoader.ScriptLoader.loadScript(url).then(() => waiter.start(runErrMsg), () => waiter.reject(loadErrMsg));
          });
          tasks[id] = task;
          return task;
        }
      };
      const add = (id, data) => {
        if (resultFns[id] !== undefined) {
          resultFns[id](data);
          delete resultFns[id];
        }
        tasks[id] = Promise.resolve(data);
        resources[id] = data;
      };
      const has = id => {
        return id in resources;
      };
      const unload = id => {
        delete tasks[id];
      };
      const get = id => resources[id];
      return {
        load,
        add,
        has,
        get,
        unload
      };
    };
    const Resource = create$1();

    const create = () => (() => {
      let data = {};
      let keys = [];
      const storage = {
        getItem: key => {
          const item = data[key];
          return item ? item : null;
        },
        setItem: (key, value) => {
          keys.push(key);
          data[key] = String(value);
        },
        key: index => {
          return keys[index];
        },
        removeItem: key => {
          keys = keys.filter(k => k === key);
          delete data[key];
        },
        clear: () => {
          keys = [];
          data = {};
        },
        length: 0
      };
      Object.defineProperty(storage, 'length', {
        get: () => keys.length,
        configurable: false,
        enumerable: false
      });
      return storage;
    })();

    let localStorage;
    try {
      const test = '__storage_test__';
      localStorage = window.localStorage;
      localStorage.setItem(test, test);
      localStorage.removeItem(test);
    } catch (e) {
      localStorage = create();
    }
    var LocalStorage = localStorage;

    const publicApi = {
      geom: { Rect },
      util: {
        Delay,
        Tools,
        VK,
        URI,
        EventDispatcher,
        Observable,
        I18n,
        LocalStorage,
        ImageUploader
      },
      dom: {
        EventUtils,
        TreeWalker: DomTreeWalker,
        TextSeeker,
        DOMUtils,
        ScriptLoader,
        RangeUtils,
        Serializer: DomSerializer,
        StyleSheetLoader,
        ControlSelection,
        BookmarkManager,
        Selection: EditorSelection,
        Event: EventUtils.Event
      },
      html: {
        Styles,
        Entities,
        Node: AstNode,
        Schema,
        DomParser,
        Writer,
        Serializer: HtmlSerializer
      },
      Env,
      AddOnManager,
      Annotator,
      Formatter,
      UndoManager,
      EditorCommands,
      WindowManager,
      NotificationManager,
      EditorObservable,
      Shortcuts,
      Editor,
      FocusManager,
      EditorManager,
      DOM: DOMUtils.DOM,
      ScriptLoader: ScriptLoader.ScriptLoader,
      PluginManager,
      ThemeManager,
      ModelManager,
      IconManager,
      Resource,
      FakeClipboard,
      trim: Tools.trim,
      isArray: Tools.isArray,
      is: Tools.is,
      toArray: Tools.toArray,
      makeMap: Tools.makeMap,
      each: Tools.each,
      map: Tools.map,
      grep: Tools.grep,
      inArray: Tools.inArray,
      extend: Tools.extend,
      walk: Tools.walk,
      resolve: Tools.resolve,
      explode: Tools.explode,
      _addCacheSuffix: Tools._addCacheSuffix
    };
    const tinymce$1 = Tools.extend(EditorManager, publicApi);

    const exportToModuleLoaders = tinymce => {
      if (typeof module === 'object') {
        try {
          module.exports = tinymce;
        } catch (_) {
        }
      }
    };
    const exportToWindowGlobal = tinymce => {
      window.tinymce = tinymce;
      window.tinyMCE = tinymce;
    };
    exportToWindowGlobal(tinymce$1);
    exportToModuleLoaders(tinymce$1);

})();



/* @preserve
 * Leaflet 1.9.4+v1.d15112c, a JS library for interactive maps. https://leafletjs.com
 * (c) 2010-2023 Vladimir Agafonkin, (c) 2010-2011 CloudMade
 */


(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
  typeof define === 'function' && define.amd ? define(['exports'], factory) :
  (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.leaflet = {}));
})(this, (function (exports) { 'use strict';

  var version = "1.9.4";

  /*
   * @namespace Util
   *
   * Various utility functions, used by Leaflet internally.
   */

  // @function extend(dest: Object, src?: Object): Object
  // Merges the properties of the `src` object (or multiple objects) into `dest` object and returns the latter. Has an `L.extend` shortcut.
  function extend(dest) {
  	var i, j, len, src;

  	for (j = 1, len = arguments.length; j < len; j++) {
  		src = arguments[j];
  		for (i in src) {
  			dest[i] = src[i];
  		}
  	}
  	return dest;
  }

  // @function create(proto: Object, properties?: Object): Object
  // Compatibility polyfill for [Object.create](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/create)
  var create$2 = Object.create || (function () {
  	function F() {}
  	return function (proto) {
  		F.prototype = proto;
  		return new F();
  	};
  })();

  // @function bind(fn: Function, …): Function
  // Returns a new function bound to the arguments passed, like [Function.prototype.bind](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Function/bind).
  // Has a `L.bind()` shortcut.
  function bind(fn, obj) {
  	var slice = Array.prototype.slice;

  	if (fn.bind) {
  		return fn.bind.apply(fn, slice.call(arguments, 1));
  	}

  	var args = slice.call(arguments, 2);

  	return function () {
  		return fn.apply(obj, args.length ? args.concat(slice.call(arguments)) : arguments);
  	};
  }

  // @property lastId: Number
  // Last unique ID used by [`stamp()`](#util-stamp)
  var lastId = 0;

  // @function stamp(obj: Object): Number
  // Returns the unique ID of an object, assigning it one if it doesn't have it.
  function stamp(obj) {
  	if (!('_leaflet_id' in obj)) {
  		obj['_leaflet_id'] = ++lastId;
  	}
  	return obj._leaflet_id;
  }

  // @function throttle(fn: Function, time: Number, context: Object): Function
  // Returns a function which executes function `fn` with the given scope `context`
  // (so that the `this` keyword refers to `context` inside `fn`'s code). The function
  // `fn` will be called no more than one time per given amount of `time`. The arguments
  // received by the bound function will be any arguments passed when binding the
  // function, followed by any arguments passed when invoking the bound function.
  // Has an `L.throttle` shortcut.
  function throttle(fn, time, context) {
  	var lock, args, wrapperFn, later;

  	later = function () {
  		// reset lock and call if queued
  		lock = false;
  		if (args) {
  			wrapperFn.apply(context, args);
  			args = false;
  		}
  	};

  	wrapperFn = function () {
  		if (lock) {
  			// called too soon, queue to call later
  			args = arguments;

  		} else {
  			// call and lock until later
  			fn.apply(context, arguments);
  			setTimeout(later, time);
  			lock = true;
  		}
  	};

  	return wrapperFn;
  }

  // @function wrapNum(num: Number, range: Number[], includeMax?: Boolean): Number
  // Returns the number `num` modulo `range` in such a way so it lies within
  // `range[0]` and `range[1]`. The returned value will be always smaller than
  // `range[1]` unless `includeMax` is set to `true`.
  function wrapNum(x, range, includeMax) {
  	var max = range[1],
  	    min = range[0],
  	    d = max - min;
  	return x === max && includeMax ? x : ((x - min) % d + d) % d + min;
  }

  // @function falseFn(): Function
  // Returns a function which always returns `false`.
  function falseFn() { return false; }

  // @function formatNum(num: Number, precision?: Number|false): Number
  // Returns the number `num` rounded with specified `precision`.
  // The default `precision` value is 6 decimal places.
  // `false` can be passed to skip any processing (can be useful to avoid round-off errors).
  function formatNum(num, precision) {
  	if (precision === false) { return num; }
  	var pow = Math.pow(10, precision === undefined ? 6 : precision);
  	return Math.round(num * pow) / pow;
  }

  // @function trim(str: String): String
  // Compatibility polyfill for [String.prototype.trim](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String/Trim)
  function trim(str) {
  	return str.trim ? str.trim() : str.replace(/^\s+|\s+$/g, '');
  }

  // @function splitWords(str: String): String[]
  // Trims and splits the string on whitespace and returns the array of parts.
  function splitWords(str) {
  	return trim(str).split(/\s+/);
  }

  // @function setOptions(obj: Object, options: Object): Object
  // Merges the given properties to the `options` of the `obj` object, returning the resulting options. See `Class options`. Has an `L.setOptions` shortcut.
  function setOptions(obj, options) {
  	if (!Object.prototype.hasOwnProperty.call(obj, 'options')) {
  		obj.options = obj.options ? create$2(obj.options) : {};
  	}
  	for (var i in options) {
  		obj.options[i] = options[i];
  	}
  	return obj.options;
  }

  // @function getParamString(obj: Object, existingUrl?: String, uppercase?: Boolean): String
  // Converts an object into a parameter URL string, e.g. `{a: "foo", b: "bar"}`
  // translates to `'?a=foo&b=bar'`. If `existingUrl` is set, the parameters will
  // be appended at the end. If `uppercase` is `true`, the parameter names will
  // be uppercased (e.g. `'?A=foo&B=bar'`)
  function getParamString(obj, existingUrl, uppercase) {
  	var params = [];
  	for (var i in obj) {
  		params.push(encodeURIComponent(uppercase ? i.toUpperCase() : i) + '=' + encodeURIComponent(obj[i]));
  	}
  	return ((!existingUrl || existingUrl.indexOf('?') === -1) ? '?' : '&') + params.join('&');
  }

  var templateRe = /\{ *([\w_ -]+) *\}/g;

  // @function template(str: String, data: Object): String
  // Simple templating facility, accepts a template string of the form `'Hello {a}, {b}'`
  // and a data object like `{a: 'foo', b: 'bar'}`, returns evaluated string
  // `('Hello foo, bar')`. You can also specify functions instead of strings for
  // data values — they will be evaluated passing `data` as an argument.
  function template(str, data) {
  	return str.replace(templateRe, function (str, key) {
  		var value = data[key];

  		if (value === undefined) {
  			throw new Error('No value provided for variable ' + str);

  		} else if (typeof value === 'function') {
  			value = value(data);
  		}
  		return value;
  	});
  }

  // @function isArray(obj): Boolean
  // Compatibility polyfill for [Array.isArray](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray)
  var isArray = Array.isArray || function (obj) {
  	return (Object.prototype.toString.call(obj) === '[object Array]');
  };

  // @function indexOf(array: Array, el: Object): Number
  // Compatibility polyfill for [Array.prototype.indexOf](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf)
  function indexOf(array, el) {
  	for (var i = 0; i < array.length; i++) {
  		if (array[i] === el) { return i; }
  	}
  	return -1;
  }

  // @property emptyImageUrl: String
  // Data URI string containing a base64-encoded empty GIF image.
  // Used as a hack to free memory from unused images on WebKit-powered
  // mobile devices (by setting image `src` to this string).
  var emptyImageUrl = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';

  // inspired by https://paulirish.com/2011/requestanimationframe-for-smart-animating/

  function getPrefixed(name) {
  	return window['webkit' + name] || window['moz' + name] || window['ms' + name];
  }

  var lastTime = 0;

  // fallback for IE 7-8
  function timeoutDefer(fn) {
  	var time = +new Date(),
  	    timeToCall = Math.max(0, 16 - (time - lastTime));

  	lastTime = time + timeToCall;
  	return window.setTimeout(fn, timeToCall);
  }

  var requestFn = window.requestAnimationFrame || getPrefixed('RequestAnimationFrame') || timeoutDefer;
  var cancelFn = window.cancelAnimationFrame || getPrefixed('CancelAnimationFrame') ||
  		getPrefixed('CancelRequestAnimationFrame') || function (id) { window.clearTimeout(id); };

  // @function requestAnimFrame(fn: Function, context?: Object, immediate?: Boolean): Number
  // Schedules `fn` to be executed when the browser repaints. `fn` is bound to
  // `context` if given. When `immediate` is set, `fn` is called immediately if
  // the browser doesn't have native support for
  // [`window.requestAnimationFrame`](https://developer.mozilla.org/docs/Web/API/window/requestAnimationFrame),
  // otherwise it's delayed. Returns a request ID that can be used to cancel the request.
  function requestAnimFrame(fn, context, immediate) {
  	if (immediate && requestFn === timeoutDefer) {
  		fn.call(context);
  	} else {
  		return requestFn.call(window, bind(fn, context));
  	}
  }

  // @function cancelAnimFrame(id: Number): undefined
  // Cancels a previous `requestAnimFrame`. See also [window.cancelAnimationFrame](https://developer.mozilla.org/docs/Web/API/window/cancelAnimationFrame).
  function cancelAnimFrame(id) {
  	if (id) {
  		cancelFn.call(window, id);
  	}
  }

  var Util = {
    __proto__: null,
    extend: extend,
    create: create$2,
    bind: bind,
    get lastId () { return lastId; },
    stamp: stamp,
    throttle: throttle,
    wrapNum: wrapNum,
    falseFn: falseFn,
    formatNum: formatNum,
    trim: trim,
    splitWords: splitWords,
    setOptions: setOptions,
    getParamString: getParamString,
    template: template,
    isArray: isArray,
    indexOf: indexOf,
    emptyImageUrl: emptyImageUrl,
    requestFn: requestFn,
    cancelFn: cancelFn,
    requestAnimFrame: requestAnimFrame,
    cancelAnimFrame: cancelAnimFrame
  };

  // @class Class
  // @aka L.Class

  // @section
  // @uninheritable

  // Thanks to John Resig and Dean Edwards for inspiration!

  function Class() {}

  Class.extend = function (props) {

  	// @function extend(props: Object): Function
  	// [Extends the current class](#class-inheritance) given the properties to be included.
  	// Returns a Javascript function that is a class constructor (to be called with `new`).
  	var NewClass = function () {

  		setOptions(this);

  		// call the constructor
  		if (this.initialize) {
  			this.initialize.apply(this, arguments);
  		}

  		// call all constructor hooks
  		this.callInitHooks();
  	};

  	var parentProto = NewClass.__super__ = this.prototype;

  	var proto = create$2(parentProto);
  	proto.constructor = NewClass;

  	NewClass.prototype = proto;

  	// inherit parent's statics
  	for (var i in this) {
  		if (Object.prototype.hasOwnProperty.call(this, i) && i !== 'prototype' && i !== '__super__') {
  			NewClass[i] = this[i];
  		}
  	}

  	// mix static properties into the class
  	if (props.statics) {
  		extend(NewClass, props.statics);
  	}

  	// mix includes into the prototype
  	if (props.includes) {
  		checkDeprecatedMixinEvents(props.includes);
  		extend.apply(null, [proto].concat(props.includes));
  	}

  	// mix given properties into the prototype
  	extend(proto, props);
  	delete proto.statics;
  	delete proto.includes;

  	// merge options
  	if (proto.options) {
  		proto.options = parentProto.options ? create$2(parentProto.options) : {};
  		extend(proto.options, props.options);
  	}

  	proto._initHooks = [];

  	// add method for calling all hooks
  	proto.callInitHooks = function () {

  		if (this._initHooksCalled) { return; }

  		if (parentProto.callInitHooks) {
  			parentProto.callInitHooks.call(this);
  		}

  		this._initHooksCalled = true;

  		for (var i = 0, len = proto._initHooks.length; i < len; i++) {
  			proto._initHooks[i].call(this);
  		}
  	};

  	return NewClass;
  };


  // @function include(properties: Object): this
  // [Includes a mixin](#class-includes) into the current class.
  Class.include = function (props) {
  	var parentOptions = this.prototype.options;
  	extend(this.prototype, props);
  	if (props.options) {
  		this.prototype.options = parentOptions;
  		this.mergeOptions(props.options);
  	}
  	return this;
  };

  // @function mergeOptions(options: Object): this
  // [Merges `options`](#class-options) into the defaults of the class.
  Class.mergeOptions = function (options) {
  	extend(this.prototype.options, options);
  	return this;
  };

  // @function addInitHook(fn: Function): this
  // Adds a [constructor hook](#class-constructor-hooks) to the class.
  Class.addInitHook = function (fn) { // (Function) || (String, args...)
  	var args = Array.prototype.slice.call(arguments, 1);

  	var init = typeof fn === 'function' ? fn : function () {
  		this[fn].apply(this, args);
  	};

  	this.prototype._initHooks = this.prototype._initHooks || [];
  	this.prototype._initHooks.push(init);
  	return this;
  };

  function checkDeprecatedMixinEvents(includes) {
  	/* global L: true */
  	if (typeof L === 'undefined' || !L || !L.Mixin) { return; }

  	includes = isArray(includes) ? includes : [includes];

  	for (var i = 0; i < includes.length; i++) {
  		if (includes[i] === L.Mixin.Events) {
  			console.warn('Deprecated include of L.Mixin.Events: ' +
  				'this property will be removed in future releases, ' +
  				'please inherit from L.Evented instead.', new Error().stack);
  		}
  	}
  }

  /*
   * @class Evented
   * @aka L.Evented
   * @inherits Class
   *
   * A set of methods shared between event-powered classes (like `Map` and `Marker`). Generally, events allow you to execute some function when something happens with an object (e.g. the user clicks on the map, causing the map to fire `'click'` event).
   *
   * @example
   *
   * ```js
   * map.on('click', function(e) {
   * 	alert(e.latlng);
   * } );
   * ```
   *
   * Leaflet deals with event listeners by reference, so if you want to add a listener and then remove it, define it as a function:
   *
   * ```js
   * function onClick(e) { ... }
   *
   * map.on('click', onClick);
   * map.off('click', onClick);
   * ```
   */

  var Events = {
  	/* @method on(type: String, fn: Function, context?: Object): this
  	 * Adds a listener function (`fn`) to a particular event type of the object. You can optionally specify the context of the listener (object the this keyword will point to). You can also pass several space-separated types (e.g. `'click dblclick'`).
  	 *
  	 * @alternative
  	 * @method on(eventMap: Object): this
  	 * Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
  	 */
  	on: function (types, fn, context) {

  		// types can be a map of types/handlers
  		if (typeof types === 'object') {
  			for (var type in types) {
  				// we don't process space-separated events here for performance;
  				// it's a hot path since Layer uses the on(obj) syntax
  				this._on(type, types[type], fn);
  			}

  		} else {
  			// types can be a string of space-separated words
  			types = splitWords(types);

  			for (var i = 0, len = types.length; i < len; i++) {
  				this._on(types[i], fn, context);
  			}
  		}

  		return this;
  	},

  	/* @method off(type: String, fn?: Function, context?: Object): this
  	 * Removes a previously added listener function. If no function is specified, it will remove all the listeners of that particular event from the object. Note that if you passed a custom context to `on`, you must pass the same context to `off` in order to remove the listener.
  	 *
  	 * @alternative
  	 * @method off(eventMap: Object): this
  	 * Removes a set of type/listener pairs.
  	 *
  	 * @alternative
  	 * @method off: this
  	 * Removes all listeners to all events on the object. This includes implicitly attached events.
  	 */
  	off: function (types, fn, context) {

  		if (!arguments.length) {
  			// clear all listeners if called without arguments
  			delete this._events;

  		} else if (typeof types === 'object') {
  			for (var type in types) {
  				this._off(type, types[type], fn);
  			}

  		} else {
  			types = splitWords(types);

  			var removeAll = arguments.length === 1;
  			for (var i = 0, len = types.length; i < len; i++) {
  				if (removeAll) {
  					this._off(types[i]);
  				} else {
  					this._off(types[i], fn, context);
  				}
  			}
  		}

  		return this;
  	},

  	// attach listener (without syntactic sugar now)
  	_on: function (type, fn, context, _once) {
  		if (typeof fn !== 'function') {
  			console.warn('wrong listener type: ' + typeof fn);
  			return;
  		}

  		// check if fn already there
  		if (this._listens(type, fn, context) !== false) {
  			return;
  		}

  		if (context === this) {
  			// Less memory footprint.
  			context = undefined;
  		}

  		var newListener = {fn: fn, ctx: context};
  		if (_once) {
  			newListener.once = true;
  		}

  		this._events = this._events || {};
  		this._events[type] = this._events[type] || [];
  		this._events[type].push(newListener);
  	},

  	_off: function (type, fn, context) {
  		var listeners,
  		    i,
  		    len;

  		if (!this._events) {
  			return;
  		}

  		listeners = this._events[type];
  		if (!listeners) {
  			return;
  		}

  		if (arguments.length === 1) { // remove all
  			if (this._firingCount) {
  				// Set all removed listeners to noop
  				// so they are not called if remove happens in fire
  				for (i = 0, len = listeners.length; i < len; i++) {
  					listeners[i].fn = falseFn;
  				}
  			}
  			// clear all listeners for a type if function isn't specified
  			delete this._events[type];
  			return;
  		}

  		if (typeof fn !== 'function') {
  			console.warn('wrong listener type: ' + typeof fn);
  			return;
  		}

  		// find fn and remove it
  		var index = this._listens(type, fn, context);
  		if (index !== false) {
  			var listener = listeners[index];
  			if (this._firingCount) {
  				// set the removed listener to noop so that's not called if remove happens in fire
  				listener.fn = falseFn;

  				/* copy array in case events are being fired */
  				this._events[type] = listeners = listeners.slice();
  			}
  			listeners.splice(index, 1);
  		}
  	},

  	// @method fire(type: String, data?: Object, propagate?: Boolean): this
  	// Fires an event of the specified type. You can optionally provide a data
  	// object — the first argument of the listener function will contain its
  	// properties. The event can optionally be propagated to event parents.
  	fire: function (type, data, propagate) {
  		if (!this.listens(type, propagate)) { return this; }

  		var event = extend({}, data, {
  			type: type,
  			target: this,
  			sourceTarget: data && data.sourceTarget || this
  		});

  		if (this._events) {
  			var listeners = this._events[type];
  			if (listeners) {
  				this._firingCount = (this._firingCount + 1) || 1;
  				for (var i = 0, len = listeners.length; i < len; i++) {
  					var l = listeners[i];
  					// off overwrites l.fn, so we need to copy fn to a var
  					var fn = l.fn;
  					if (l.once) {
  						this.off(type, fn, l.ctx);
  					}
  					fn.call(l.ctx || this, event);
  				}

  				this._firingCount--;
  			}
  		}

  		if (propagate) {
  			// propagate the event to parents (set with addEventParent)
  			this._propagateEvent(event);
  		}

  		return this;
  	},

  	// @method listens(type: String, propagate?: Boolean): Boolean
  	// @method listens(type: String, fn: Function, context?: Object, propagate?: Boolean): Boolean
  	// Returns `true` if a particular event type has any listeners attached to it.
  	// The verification can optionally be propagated, it will return `true` if parents have the listener attached to it.
  	listens: function (type, fn, context, propagate) {
  		if (typeof type !== 'string') {
  			console.warn('"string" type argument expected');
  		}

  		// we don't overwrite the input `fn` value, because we need to use it for propagation
  		var _fn = fn;
  		if (typeof fn !== 'function') {
  			propagate = !!fn;
  			_fn = undefined;
  			context = undefined;
  		}

  		var listeners = this._events && this._events[type];
  		if (listeners && listeners.length) {
  			if (this._listens(type, _fn, context) !== false) {
  				return true;
  			}
  		}

  		if (propagate) {
  			// also check parents for listeners if event propagates
  			for (var id in this._eventParents) {
  				if (this._eventParents[id].listens(type, fn, context, propagate)) { return true; }
  			}
  		}
  		return false;
  	},

  	// returns the index (number) or false
  	_listens: function (type, fn, context) {
  		if (!this._events) {
  			return false;
  		}

  		var listeners = this._events[type] || [];
  		if (!fn) {
  			return !!listeners.length;
  		}

  		if (context === this) {
  			// Less memory footprint.
  			context = undefined;
  		}

  		for (var i = 0, len = listeners.length; i < len; i++) {
  			if (listeners[i].fn === fn && listeners[i].ctx === context) {
  				return i;
  			}
  		}
  		return false;

  	},

  	// @method once(…): this
  	// Behaves as [`on(…)`](#evented-on), except the listener will only get fired once and then removed.
  	once: function (types, fn, context) {

  		// types can be a map of types/handlers
  		if (typeof types === 'object') {
  			for (var type in types) {
  				// we don't process space-separated events here for performance;
  				// it's a hot path since Layer uses the on(obj) syntax
  				this._on(type, types[type], fn, true);
  			}

  		} else {
  			// types can be a string of space-separated words
  			types = splitWords(types);

  			for (var i = 0, len = types.length; i < len; i++) {
  				this._on(types[i], fn, context, true);
  			}
  		}

  		return this;
  	},

  	// @method addEventParent(obj: Evented): this
  	// Adds an event parent - an `Evented` that will receive propagated events
  	addEventParent: function (obj) {
  		this._eventParents = this._eventParents || {};
  		this._eventParents[stamp(obj)] = obj;
  		return this;
  	},

  	// @method removeEventParent(obj: Evented): this
  	// Removes an event parent, so it will stop receiving propagated events
  	removeEventParent: function (obj) {
  		if (this._eventParents) {
  			delete this._eventParents[stamp(obj)];
  		}
  		return this;
  	},

  	_propagateEvent: function (e) {
  		for (var id in this._eventParents) {
  			this._eventParents[id].fire(e.type, extend({
  				layer: e.target,
  				propagatedFrom: e.target
  			}, e), true);
  		}
  	}
  };

  // aliases; we should ditch those eventually

  // @method addEventListener(…): this
  // Alias to [`on(…)`](#evented-on)
  Events.addEventListener = Events.on;

  // @method removeEventListener(…): this
  // Alias to [`off(…)`](#evented-off)

  // @method clearAllEventListeners(…): this
  // Alias to [`off()`](#evented-off)
  Events.removeEventListener = Events.clearAllEventListeners = Events.off;

  // @method addOneTimeEventListener(…): this
  // Alias to [`once(…)`](#evented-once)
  Events.addOneTimeEventListener = Events.once;

  // @method fireEvent(…): this
  // Alias to [`fire(…)`](#evented-fire)
  Events.fireEvent = Events.fire;

  // @method hasEventListeners(…): Boolean
  // Alias to [`listens(…)`](#evented-listens)
  Events.hasEventListeners = Events.listens;

  var Evented = Class.extend(Events);

  /*
   * @class Point
   * @aka L.Point
   *
   * Represents a point with `x` and `y` coordinates in pixels.
   *
   * @example
   *
   * ```js
   * var point = L.point(200, 300);
   * ```
   *
   * All Leaflet methods and options that accept `Point` objects also accept them in a simple Array form (unless noted otherwise), so these lines are equivalent:
   *
   * ```js
   * map.panBy([200, 300]);
   * map.panBy(L.point(200, 300));
   * ```
   *
   * Note that `Point` does not inherit from Leaflet's `Class` object,
   * which means new classes can't inherit from it, and new methods
   * can't be added to it with the `include` function.
   */

  function Point(x, y, round) {
  	// @property x: Number; The `x` coordinate of the point
  	this.x = (round ? Math.round(x) : x);
  	// @property y: Number; The `y` coordinate of the point
  	this.y = (round ? Math.round(y) : y);
  }

  var trunc = Math.trunc || function (v) {
  	return v > 0 ? Math.floor(v) : Math.ceil(v);
  };

  Point.prototype = {

  	// @method clone(): Point
  	// Returns a copy of the current point.
  	clone: function () {
  		return new Point(this.x, this.y);
  	},

  	// @method add(otherPoint: Point): Point
  	// Returns the result of addition of the current and the given points.
  	add: function (point) {
  		// non-destructive, returns a new point
  		return this.clone()._add(toPoint(point));
  	},

  	_add: function (point) {
  		// destructive, used directly for performance in situations where it's safe to modify existing point
  		this.x += point.x;
  		this.y += point.y;
  		return this;
  	},

  	// @method subtract(otherPoint: Point): Point
  	// Returns the result of subtraction of the given point from the current.
  	subtract: function (point) {
  		return this.clone()._subtract(toPoint(point));
  	},

  	_subtract: function (point) {
  		this.x -= point.x;
  		this.y -= point.y;
  		return this;
  	},

  	// @method divideBy(num: Number): Point
  	// Returns the result of division of the current point by the given number.
  	divideBy: function (num) {
  		return this.clone()._divideBy(num);
  	},

  	_divideBy: function (num) {
  		this.x /= num;
  		this.y /= num;
  		return this;
  	},

  	// @method multiplyBy(num: Number): Point
  	// Returns the result of multiplication of the current point by the given number.
  	multiplyBy: function (num) {
  		return this.clone()._multiplyBy(num);
  	},

  	_multiplyBy: function (num) {
  		this.x *= num;
  		this.y *= num;
  		return this;
  	},

  	// @method scaleBy(scale: Point): Point
  	// Multiply each coordinate of the current point by each coordinate of
  	// `scale`. In linear algebra terms, multiply the point by the
  	// [scaling matrix](https://en.wikipedia.org/wiki/Scaling_%28geometry%29#Matrix_representation)
  	// defined by `scale`.
  	scaleBy: function (point) {
  		return new Point(this.x * point.x, this.y * point.y);
  	},

  	// @method unscaleBy(scale: Point): Point
  	// Inverse of `scaleBy`. Divide each coordinate of the current point by
  	// each coordinate of `scale`.
  	unscaleBy: function (point) {
  		return new Point(this.x / point.x, this.y / point.y);
  	},

  	// @method round(): Point
  	// Returns a copy of the current point with rounded coordinates.
  	round: function () {
  		return this.clone()._round();
  	},

  	_round: function () {
  		this.x = Math.round(this.x);
  		this.y = Math.round(this.y);
  		return this;
  	},

  	// @method floor(): Point
  	// Returns a copy of the current point with floored coordinates (rounded down).
  	floor: function () {
  		return this.clone()._floor();
  	},

  	_floor: function () {
  		this.x = Math.floor(this.x);
  		this.y = Math.floor(this.y);
  		return this;
  	},

  	// @method ceil(): Point
  	// Returns a copy of the current point with ceiled coordinates (rounded up).
  	ceil: function () {
  		return this.clone()._ceil();
  	},

  	_ceil: function () {
  		this.x = Math.ceil(this.x);
  		this.y = Math.ceil(this.y);
  		return this;
  	},

  	// @method trunc(): Point
  	// Returns a copy of the current point with truncated coordinates (rounded towards zero).
  	trunc: function () {
  		return this.clone()._trunc();
  	},

  	_trunc: function () {
  		this.x = trunc(this.x);
  		this.y = trunc(this.y);
  		return this;
  	},

  	// @method distanceTo(otherPoint: Point): Number
  	// Returns the cartesian distance between the current and the given points.
  	distanceTo: function (point) {
  		point = toPoint(point);

  		var x = point.x - this.x,
  		    y = point.y - this.y;

  		return Math.sqrt(x * x + y * y);
  	},

  	// @method equals(otherPoint: Point): Boolean
  	// Returns `true` if the given point has the same coordinates.
  	equals: function (point) {
  		point = toPoint(point);

  		return point.x === this.x &&
  		       point.y === this.y;
  	},

  	// @method contains(otherPoint: Point): Boolean
  	// Returns `true` if both coordinates of the given point are less than the corresponding current point coordinates (in absolute values).
  	contains: function (point) {
  		point = toPoint(point);

  		return Math.abs(point.x) <= Math.abs(this.x) &&
  		       Math.abs(point.y) <= Math.abs(this.y);
  	},

  	// @method toString(): String
  	// Returns a string representation of the point for debugging purposes.
  	toString: function () {
  		return 'Point(' +
  		        formatNum(this.x) + ', ' +
  		        formatNum(this.y) + ')';
  	}
  };

  // @factory L.point(x: Number, y: Number, round?: Boolean)
  // Creates a Point object with the given `x` and `y` coordinates. If optional `round` is set to true, rounds the `x` and `y` values.

  // @alternative
  // @factory L.point(coords: Number[])
  // Expects an array of the form `[x, y]` instead.

  // @alternative
  // @factory L.point(coords: Object)
  // Expects a plain object of the form `{x: Number, y: Number}` instead.
  function toPoint(x, y, round) {
  	if (x instanceof Point) {
  		return x;
  	}
  	if (isArray(x)) {
  		return new Point(x[0], x[1]);
  	}
  	if (x === undefined || x === null) {
  		return x;
  	}
  	if (typeof x === 'object' && 'x' in x && 'y' in x) {
  		return new Point(x.x, x.y);
  	}
  	return new Point(x, y, round);
  }

  /*
   * @class Bounds
   * @aka L.Bounds
   *
   * Represents a rectangular area in pixel coordinates.
   *
   * @example
   *
   * ```js
   * var p1 = L.point(10, 10),
   * p2 = L.point(40, 60),
   * bounds = L.bounds(p1, p2);
   * ```
   *
   * All Leaflet methods that accept `Bounds` objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
   *
   * ```js
   * otherBounds.intersects([[10, 10], [40, 60]]);
   * ```
   *
   * Note that `Bounds` does not inherit from Leaflet's `Class` object,
   * which means new classes can't inherit from it, and new methods
   * can't be added to it with the `include` function.
   */

  function Bounds(a, b) {
  	if (!a) { return; }

  	var points = b ? [a, b] : a;

  	for (var i = 0, len = points.length; i < len; i++) {
  		this.extend(points[i]);
  	}
  }

  Bounds.prototype = {
  	// @method extend(point: Point): this
  	// Extends the bounds to contain the given point.

  	// @alternative
  	// @method extend(otherBounds: Bounds): this
  	// Extend the bounds to contain the given bounds
  	extend: function (obj) {
  		var min2, max2;
  		if (!obj) { return this; }

  		if (obj instanceof Point || typeof obj[0] === 'number' || 'x' in obj) {
  			min2 = max2 = toPoint(obj);
  		} else {
  			obj = toBounds(obj);
  			min2 = obj.min;
  			max2 = obj.max;

  			if (!min2 || !max2) { return this; }
  		}

  		// @property min: Point
  		// The top left corner of the rectangle.
  		// @property max: Point
  		// The bottom right corner of the rectangle.
  		if (!this.min && !this.max) {
  			this.min = min2.clone();
  			this.max = max2.clone();
  		} else {
  			this.min.x = Math.min(min2.x, this.min.x);
  			this.max.x = Math.max(max2.x, this.max.x);
  			this.min.y = Math.min(min2.y, this.min.y);
  			this.max.y = Math.max(max2.y, this.max.y);
  		}
  		return this;
  	},

  	// @method getCenter(round?: Boolean): Point
  	// Returns the center point of the bounds.
  	getCenter: function (round) {
  		return toPoint(
  		        (this.min.x + this.max.x) / 2,
  		        (this.min.y + this.max.y) / 2, round);
  	},

  	// @method getBottomLeft(): Point
  	// Returns the bottom-left point of the bounds.
  	getBottomLeft: function () {
  		return toPoint(this.min.x, this.max.y);
  	},

  	// @method getTopRight(): Point
  	// Returns the top-right point of the bounds.
  	getTopRight: function () { // -> Point
  		return toPoint(this.max.x, this.min.y);
  	},

  	// @method getTopLeft(): Point
  	// Returns the top-left point of the bounds (i.e. [`this.min`](#bounds-min)).
  	getTopLeft: function () {
  		return this.min; // left, top
  	},

  	// @method getBottomRight(): Point
  	// Returns the bottom-right point of the bounds (i.e. [`this.max`](#bounds-max)).
  	getBottomRight: function () {
  		return this.max; // right, bottom
  	},

  	// @method getSize(): Point
  	// Returns the size of the given bounds
  	getSize: function () {
  		return this.max.subtract(this.min);
  	},

  	// @method contains(otherBounds: Bounds): Boolean
  	// Returns `true` if the rectangle contains the given one.
  	// @alternative
  	// @method contains(point: Point): Boolean
  	// Returns `true` if the rectangle contains the given point.
  	contains: function (obj) {
  		var min, max;

  		if (typeof obj[0] === 'number' || obj instanceof Point) {
  			obj = toPoint(obj);
  		} else {
  			obj = toBounds(obj);
  		}

  		if (obj instanceof Bounds) {
  			min = obj.min;
  			max = obj.max;
  		} else {
  			min = max = obj;
  		}

  		return (min.x >= this.min.x) &&
  		       (max.x <= this.max.x) &&
  		       (min.y >= this.min.y) &&
  		       (max.y <= this.max.y);
  	},

  	// @method intersects(otherBounds: Bounds): Boolean
  	// Returns `true` if the rectangle intersects the given bounds. Two bounds
  	// intersect if they have at least one point in common.
  	intersects: function (bounds) { // (Bounds) -> Boolean
  		bounds = toBounds(bounds);

  		var min = this.min,
  		    max = this.max,
  		    min2 = bounds.min,
  		    max2 = bounds.max,
  		    xIntersects = (max2.x >= min.x) && (min2.x <= max.x),
  		    yIntersects = (max2.y >= min.y) && (min2.y <= max.y);

  		return xIntersects && yIntersects;
  	},

  	// @method overlaps(otherBounds: Bounds): Boolean
  	// Returns `true` if the rectangle overlaps the given bounds. Two bounds
  	// overlap if their intersection is an area.
  	overlaps: function (bounds) { // (Bounds) -> Boolean
  		bounds = toBounds(bounds);

  		var min = this.min,
  		    max = this.max,
  		    min2 = bounds.min,
  		    max2 = bounds.max,
  		    xOverlaps = (max2.x > min.x) && (min2.x < max.x),
  		    yOverlaps = (max2.y > min.y) && (min2.y < max.y);

  		return xOverlaps && yOverlaps;
  	},

  	// @method isValid(): Boolean
  	// Returns `true` if the bounds are properly initialized.
  	isValid: function () {
  		return !!(this.min && this.max);
  	},


  	// @method pad(bufferRatio: Number): Bounds
  	// Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
  	// For example, a ratio of 0.5 extends the bounds by 50% in each direction.
  	// Negative values will retract the bounds.
  	pad: function (bufferRatio) {
  		var min = this.min,
  		max = this.max,
  		heightBuffer = Math.abs(min.x - max.x) * bufferRatio,
  		widthBuffer = Math.abs(min.y - max.y) * bufferRatio;


  		return toBounds(
  			toPoint(min.x - heightBuffer, min.y - widthBuffer),
  			toPoint(max.x + heightBuffer, max.y + widthBuffer));
  	},


  	// @method equals(otherBounds: Bounds): Boolean
  	// Returns `true` if the rectangle is equivalent to the given bounds.
  	equals: function (bounds) {
  		if (!bounds) { return false; }

  		bounds = toBounds(bounds);

  		return this.min.equals(bounds.getTopLeft()) &&
  			this.max.equals(bounds.getBottomRight());
  	},
  };


  // @factory L.bounds(corner1: Point, corner2: Point)
  // Creates a Bounds object from two corners coordinate pairs.
  // @alternative
  // @factory L.bounds(points: Point[])
  // Creates a Bounds object from the given array of points.
  function toBounds(a, b) {
  	if (!a || a instanceof Bounds) {
  		return a;
  	}
  	return new Bounds(a, b);
  }

  /*
   * @class LatLngBounds
   * @aka L.LatLngBounds
   *
   * Represents a rectangular geographical area on a map.
   *
   * @example
   *
   * ```js
   * var corner1 = L.latLng(40.712, -74.227),
   * corner2 = L.latLng(40.774, -74.125),
   * bounds = L.latLngBounds(corner1, corner2);
   * ```
   *
   * All Leaflet methods that accept LatLngBounds objects also accept them in a simple Array form (unless noted otherwise), so the bounds example above can be passed like this:
   *
   * ```js
   * map.fitBounds([
   * 	[40.712, -74.227],
   * 	[40.774, -74.125]
   * ]);
   * ```
   *
   * Caution: if the area crosses the antimeridian (often confused with the International Date Line), you must specify corners _outside_ the [-180, 180] degrees longitude range.
   *
   * Note that `LatLngBounds` does not inherit from Leaflet's `Class` object,
   * which means new classes can't inherit from it, and new methods
   * can't be added to it with the `include` function.
   */

  function LatLngBounds(corner1, corner2) { // (LatLng, LatLng) or (LatLng[])
  	if (!corner1) { return; }

  	var latlngs = corner2 ? [corner1, corner2] : corner1;

  	for (var i = 0, len = latlngs.length; i < len; i++) {
  		this.extend(latlngs[i]);
  	}
  }

  LatLngBounds.prototype = {

  	// @method extend(latlng: LatLng): this
  	// Extend the bounds to contain the given point

  	// @alternative
  	// @method extend(otherBounds: LatLngBounds): this
  	// Extend the bounds to contain the given bounds
  	extend: function (obj) {
  		var sw = this._southWest,
  		    ne = this._northEast,
  		    sw2, ne2;

  		if (obj instanceof LatLng) {
  			sw2 = obj;
  			ne2 = obj;

  		} else if (obj instanceof LatLngBounds) {
  			sw2 = obj._southWest;
  			ne2 = obj._northEast;

  			if (!sw2 || !ne2) { return this; }

  		} else {
  			return obj ? this.extend(toLatLng(obj) || toLatLngBounds(obj)) : this;
  		}

  		if (!sw && !ne) {
  			this._southWest = new LatLng(sw2.lat, sw2.lng);
  			this._northEast = new LatLng(ne2.lat, ne2.lng);
  		} else {
  			sw.lat = Math.min(sw2.lat, sw.lat);
  			sw.lng = Math.min(sw2.lng, sw.lng);
  			ne.lat = Math.max(ne2.lat, ne.lat);
  			ne.lng = Math.max(ne2.lng, ne.lng);
  		}

  		return this;
  	},

  	// @method pad(bufferRatio: Number): LatLngBounds
  	// Returns bounds created by extending or retracting the current bounds by a given ratio in each direction.
  	// For example, a ratio of 0.5 extends the bounds by 50% in each direction.
  	// Negative values will retract the bounds.
  	pad: function (bufferRatio) {
  		var sw = this._southWest,
  		    ne = this._northEast,
  		    heightBuffer = Math.abs(sw.lat - ne.lat) * bufferRatio,
  		    widthBuffer = Math.abs(sw.lng - ne.lng) * bufferRatio;

  		return new LatLngBounds(
  		        new LatLng(sw.lat - heightBuffer, sw.lng - widthBuffer),
  		        new LatLng(ne.lat + heightBuffer, ne.lng + widthBuffer));
  	},

  	// @method getCenter(): LatLng
  	// Returns the center point of the bounds.
  	getCenter: function () {
  		return new LatLng(
  		        (this._southWest.lat + this._northEast.lat) / 2,
  		        (this._southWest.lng + this._northEast.lng) / 2);
  	},

  	// @method getSouthWest(): LatLng
  	// Returns the south-west point of the bounds.
  	getSouthWest: function () {
  		return this._southWest;
  	},

  	// @method getNorthEast(): LatLng
  	// Returns the north-east point of the bounds.
  	getNorthEast: function () {
  		return this._northEast;
  	},

  	// @method getNorthWest(): LatLng
  	// Returns the north-west point of the bounds.
  	getNorthWest: function () {
  		return new LatLng(this.getNorth(), this.getWest());
  	},

  	// @method getSouthEast(): LatLng
  	// Returns the south-east point of the bounds.
  	getSouthEast: function () {
  		return new LatLng(this.getSouth(), this.getEast());
  	},

  	// @method getWest(): Number
  	// Returns the west longitude of the bounds
  	getWest: function () {
  		return this._southWest.lng;
  	},

  	// @method getSouth(): Number
  	// Returns the south latitude of the bounds
  	getSouth: function () {
  		return this._southWest.lat;
  	},

  	// @method getEast(): Number
  	// Returns the east longitude of the bounds
  	getEast: function () {
  		return this._northEast.lng;
  	},

  	// @method getNorth(): Number
  	// Returns the north latitude of the bounds
  	getNorth: function () {
  		return this._northEast.lat;
  	},

  	// @method contains(otherBounds: LatLngBounds): Boolean
  	// Returns `true` if the rectangle contains the given one.

  	// @alternative
  	// @method contains (latlng: LatLng): Boolean
  	// Returns `true` if the rectangle contains the given point.
  	contains: function (obj) { // (LatLngBounds) or (LatLng) -> Boolean
  		if (typeof obj[0] === 'number' || obj instanceof LatLng || 'lat' in obj) {
  			obj = toLatLng(obj);
  		} else {
  			obj = toLatLngBounds(obj);
  		}

  		var sw = this._southWest,
  		    ne = this._northEast,
  		    sw2, ne2;

  		if (obj instanceof LatLngBounds) {
  			sw2 = obj.getSouthWest();
  			ne2 = obj.getNorthEast();
  		} else {
  			sw2 = ne2 = obj;
  		}

  		return (sw2.lat >= sw.lat) && (ne2.lat <= ne.lat) &&
  		       (sw2.lng >= sw.lng) && (ne2.lng <= ne.lng);
  	},

  	// @method intersects(otherBounds: LatLngBounds): Boolean
  	// Returns `true` if the rectangle intersects the given bounds. Two bounds intersect if they have at least one point in common.
  	intersects: function (bounds) {
  		bounds = toLatLngBounds(bounds);

  		var sw = this._southWest,
  		    ne = this._northEast,
  		    sw2 = bounds.getSouthWest(),
  		    ne2 = bounds.getNorthEast(),

  		    latIntersects = (ne2.lat >= sw.lat) && (sw2.lat <= ne.lat),
  		    lngIntersects = (ne2.lng >= sw.lng) && (sw2.lng <= ne.lng);

  		return latIntersects && lngIntersects;
  	},

  	// @method overlaps(otherBounds: LatLngBounds): Boolean
  	// Returns `true` if the rectangle overlaps the given bounds. Two bounds overlap if their intersection is an area.
  	overlaps: function (bounds) {
  		bounds = toLatLngBounds(bounds);

  		var sw = this._southWest,
  		    ne = this._northEast,
  		    sw2 = bounds.getSouthWest(),
  		    ne2 = bounds.getNorthEast(),

  		    latOverlaps = (ne2.lat > sw.lat) && (sw2.lat < ne.lat),
  		    lngOverlaps = (ne2.lng > sw.lng) && (sw2.lng < ne.lng);

  		return latOverlaps && lngOverlaps;
  	},

  	// @method toBBoxString(): String
  	// Returns a string with bounding box coordinates in a 'southwest_lng,southwest_lat,northeast_lng,northeast_lat' format. Useful for sending requests to web services that return geo data.
  	toBBoxString: function () {
  		return [this.getWest(), this.getSouth(), this.getEast(), this.getNorth()].join(',');
  	},

  	// @method equals(otherBounds: LatLngBounds, maxMargin?: Number): Boolean
  	// Returns `true` if the rectangle is equivalent (within a small margin of error) to the given bounds. The margin of error can be overridden by setting `maxMargin` to a small number.
  	equals: function (bounds, maxMargin) {
  		if (!bounds) { return false; }

  		bounds = toLatLngBounds(bounds);

  		return this._southWest.equals(bounds.getSouthWest(), maxMargin) &&
  		       this._northEast.equals(bounds.getNorthEast(), maxMargin);
  	},

  	// @method isValid(): Boolean
  	// Returns `true` if the bounds are properly initialized.
  	isValid: function () {
  		return !!(this._southWest && this._northEast);
  	}
  };

  // TODO International date line?

  // @factory L.latLngBounds(corner1: LatLng, corner2: LatLng)
  // Creates a `LatLngBounds` object by defining two diagonally opposite corners of the rectangle.

  // @alternative
  // @factory L.latLngBounds(latlngs: LatLng[])
  // Creates a `LatLngBounds` object defined by the geographical points it contains. Very useful for zooming the map to fit a particular set of locations with [`fitBounds`](#map-fitbounds).
  function toLatLngBounds(a, b) {
  	if (a instanceof LatLngBounds) {
  		return a;
  	}
  	return new LatLngBounds(a, b);
  }

  /* @class LatLng
   * @aka L.LatLng
   *
   * Represents a geographical point with a certain latitude and longitude.
   *
   * @example
   *
   * ```
   * var latlng = L.latLng(50.5, 30.5);
   * ```
   *
   * All Leaflet methods that accept LatLng objects also accept them in a simple Array form and simple object form (unless noted otherwise), so these lines are equivalent:
   *
   * ```
   * map.panTo([50, 30]);
   * map.panTo({lon: 30, lat: 50});
   * map.panTo({lat: 50, lng: 30});
   * map.panTo(L.latLng(50, 30));
   * ```
   *
   * Note that `LatLng` does not inherit from Leaflet's `Class` object,
   * which means new classes can't inherit from it, and new methods
   * can't be added to it with the `include` function.
   */

  function LatLng(lat, lng, alt) {
  	if (isNaN(lat) || isNaN(lng)) {
  		throw new Error('Invalid LatLng object: (' + lat + ', ' + lng + ')');
  	}

  	// @property lat: Number
  	// Latitude in degrees
  	this.lat = +lat;

  	// @property lng: Number
  	// Longitude in degrees
  	this.lng = +lng;

  	// @property alt: Number
  	// Altitude in meters (optional)
  	if (alt !== undefined) {
  		this.alt = +alt;
  	}
  }

  LatLng.prototype = {
  	// @method equals(otherLatLng: LatLng, maxMargin?: Number): Boolean
  	// Returns `true` if the given `LatLng` point is at the same position (within a small margin of error). The margin of error can be overridden by setting `maxMargin` to a small number.
  	equals: function (obj, maxMargin) {
  		if (!obj) { return false; }

  		obj = toLatLng(obj);

  		var margin = Math.max(
  		        Math.abs(this.lat - obj.lat),
  		        Math.abs(this.lng - obj.lng));

  		return margin <= (maxMargin === undefined ? 1.0E-9 : maxMargin);
  	},

  	// @method toString(): String
  	// Returns a string representation of the point (for debugging purposes).
  	toString: function (precision) {
  		return 'LatLng(' +
  		        formatNum(this.lat, precision) + ', ' +
  		        formatNum(this.lng, precision) + ')';
  	},

  	// @method distanceTo(otherLatLng: LatLng): Number
  	// Returns the distance (in meters) to the given `LatLng` calculated using the [Spherical Law of Cosines](https://en.wikipedia.org/wiki/Spherical_law_of_cosines).
  	distanceTo: function (other) {
  		return Earth.distance(this, toLatLng(other));
  	},

  	// @method wrap(): LatLng
  	// Returns a new `LatLng` object with the longitude wrapped so it's always between -180 and +180 degrees.
  	wrap: function () {
  		return Earth.wrapLatLng(this);
  	},

  	// @method toBounds(sizeInMeters: Number): LatLngBounds
  	// Returns a new `LatLngBounds` object in which each boundary is `sizeInMeters/2` meters apart from the `LatLng`.
  	toBounds: function (sizeInMeters) {
  		var latAccuracy = 180 * sizeInMeters / 40075017,
  		    lngAccuracy = latAccuracy / Math.cos((Math.PI / 180) * this.lat);

  		return toLatLngBounds(
  		        [this.lat - latAccuracy, this.lng - lngAccuracy],
  		        [this.lat + latAccuracy, this.lng + lngAccuracy]);
  	},

  	clone: function () {
  		return new LatLng(this.lat, this.lng, this.alt);
  	}
  };



  // @factory L.latLng(latitude: Number, longitude: Number, altitude?: Number): LatLng
  // Creates an object representing a geographical point with the given latitude and longitude (and optionally altitude).

  // @alternative
  // @factory L.latLng(coords: Array): LatLng
  // Expects an array of the form `[Number, Number]` or `[Number, Number, Number]` instead.

  // @alternative
  // @factory L.latLng(coords: Object): LatLng
  // Expects an plain object of the form `{lat: Number, lng: Number}` or `{lat: Number, lng: Number, alt: Number}` instead.

  function toLatLng(a, b, c) {
  	if (a instanceof LatLng) {
  		return a;
  	}
  	if (isArray(a) && typeof a[0] !== 'object') {
  		if (a.length === 3) {
  			return new LatLng(a[0], a[1], a[2]);
  		}
  		if (a.length === 2) {
  			return new LatLng(a[0], a[1]);
  		}
  		return null;
  	}
  	if (a === undefined || a === null) {
  		return a;
  	}
  	if (typeof a === 'object' && 'lat' in a) {
  		return new LatLng(a.lat, 'lng' in a ? a.lng : a.lon, a.alt);
  	}
  	if (b === undefined) {
  		return null;
  	}
  	return new LatLng(a, b, c);
  }

  /*
   * @namespace CRS
   * @crs L.CRS.Base
   * Object that defines coordinate reference systems for projecting
   * geographical points into pixel (screen) coordinates and back (and to
   * coordinates in other units for [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services). See
   * [spatial reference system](https://en.wikipedia.org/wiki/Spatial_reference_system).
   *
   * Leaflet defines the most usual CRSs by default. If you want to use a
   * CRS not defined by default, take a look at the
   * [Proj4Leaflet](https://github.com/kartena/Proj4Leaflet) plugin.
   *
   * Note that the CRS instances do not inherit from Leaflet's `Class` object,
   * and can't be instantiated. Also, new classes can't inherit from them,
   * and methods can't be added to them with the `include` function.
   */

  var CRS = {
  	// @method latLngToPoint(latlng: LatLng, zoom: Number): Point
  	// Projects geographical coordinates into pixel coordinates for a given zoom.
  	latLngToPoint: function (latlng, zoom) {
  		var projectedPoint = this.projection.project(latlng),
  		    scale = this.scale(zoom);

  		return this.transformation._transform(projectedPoint, scale);
  	},

  	// @method pointToLatLng(point: Point, zoom: Number): LatLng
  	// The inverse of `latLngToPoint`. Projects pixel coordinates on a given
  	// zoom into geographical coordinates.
  	pointToLatLng: function (point, zoom) {
  		var scale = this.scale(zoom),
  		    untransformedPoint = this.transformation.untransform(point, scale);

  		return this.projection.unproject(untransformedPoint);
  	},

  	// @method project(latlng: LatLng): Point
  	// Projects geographical coordinates into coordinates in units accepted for
  	// this CRS (e.g. meters for EPSG:3857, for passing it to WMS services).
  	project: function (latlng) {
  		return this.projection.project(latlng);
  	},

  	// @method unproject(point: Point): LatLng
  	// Given a projected coordinate returns the corresponding LatLng.
  	// The inverse of `project`.
  	unproject: function (point) {
  		return this.projection.unproject(point);
  	},

  	// @method scale(zoom: Number): Number
  	// Returns the scale used when transforming projected coordinates into
  	// pixel coordinates for a particular zoom. For example, it returns
  	// `256 * 2^zoom` for Mercator-based CRS.
  	scale: function (zoom) {
  		return 256 * Math.pow(2, zoom);
  	},

  	// @method zoom(scale: Number): Number
  	// Inverse of `scale()`, returns the zoom level corresponding to a scale
  	// factor of `scale`.
  	zoom: function (scale) {
  		return Math.log(scale / 256) / Math.LN2;
  	},

  	// @method getProjectedBounds(zoom: Number): Bounds
  	// Returns the projection's bounds scaled and transformed for the provided `zoom`.
  	getProjectedBounds: function (zoom) {
  		if (this.infinite) { return null; }

  		var b = this.projection.bounds,
  		    s = this.scale(zoom),
  		    min = this.transformation.transform(b.min, s),
  		    max = this.transformation.transform(b.max, s);

  		return new Bounds(min, max);
  	},

  	// @method distance(latlng1: LatLng, latlng2: LatLng): Number
  	// Returns the distance between two geographical coordinates.

  	// @property code: String
  	// Standard code name of the CRS passed into WMS services (e.g. `'EPSG:3857'`)
  	//
  	// @property wrapLng: Number[]
  	// An array of two numbers defining whether the longitude (horizontal) coordinate
  	// axis wraps around a given range and how. Defaults to `[-180, 180]` in most
  	// geographical CRSs. If `undefined`, the longitude axis does not wrap around.
  	//
  	// @property wrapLat: Number[]
  	// Like `wrapLng`, but for the latitude (vertical) axis.

  	// wrapLng: [min, max],
  	// wrapLat: [min, max],

  	// @property infinite: Boolean
  	// If true, the coordinate space will be unbounded (infinite in both axes)
  	infinite: false,

  	// @method wrapLatLng(latlng: LatLng): LatLng
  	// Returns a `LatLng` where lat and lng has been wrapped according to the
  	// CRS's `wrapLat` and `wrapLng` properties, if they are outside the CRS's bounds.
  	wrapLatLng: function (latlng) {
  		var lng = this.wrapLng ? wrapNum(latlng.lng, this.wrapLng, true) : latlng.lng,
  		    lat = this.wrapLat ? wrapNum(latlng.lat, this.wrapLat, true) : latlng.lat,
  		    alt = latlng.alt;

  		return new LatLng(lat, lng, alt);
  	},

  	// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
  	// Returns a `LatLngBounds` with the same size as the given one, ensuring
  	// that its center is within the CRS's bounds.
  	// Only accepts actual `L.LatLngBounds` instances, not arrays.
  	wrapLatLngBounds: function (bounds) {
  		var center = bounds.getCenter(),
  		    newCenter = this.wrapLatLng(center),
  		    latShift = center.lat - newCenter.lat,
  		    lngShift = center.lng - newCenter.lng;

  		if (latShift === 0 && lngShift === 0) {
  			return bounds;
  		}

  		var sw = bounds.getSouthWest(),
  		    ne = bounds.getNorthEast(),
  		    newSw = new LatLng(sw.lat - latShift, sw.lng - lngShift),
  		    newNe = new LatLng(ne.lat - latShift, ne.lng - lngShift);

  		return new LatLngBounds(newSw, newNe);
  	}
  };

  /*
   * @namespace CRS
   * @crs L.CRS.Earth
   *
   * Serves as the base for CRS that are global such that they cover the earth.
   * Can only be used as the base for other CRS and cannot be used directly,
   * since it does not have a `code`, `projection` or `transformation`. `distance()` returns
   * meters.
   */

  var Earth = extend({}, CRS, {
  	wrapLng: [-180, 180],

  	// Mean Earth Radius, as recommended for use by
  	// the International Union of Geodesy and Geophysics,
  	// see https://rosettacode.org/wiki/Haversine_formula
  	R: 6371000,

  	// distance between two geographical points using spherical law of cosines approximation
  	distance: function (latlng1, latlng2) {
  		var rad = Math.PI / 180,
  		    lat1 = latlng1.lat * rad,
  		    lat2 = latlng2.lat * rad,
  		    sinDLat = Math.sin((latlng2.lat - latlng1.lat) * rad / 2),
  		    sinDLon = Math.sin((latlng2.lng - latlng1.lng) * rad / 2),
  		    a = sinDLat * sinDLat + Math.cos(lat1) * Math.cos(lat2) * sinDLon * sinDLon,
  		    c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  		return this.R * c;
  	}
  });

  /*
   * @namespace Projection
   * @projection L.Projection.SphericalMercator
   *
   * Spherical Mercator projection — the most common projection for online maps,
   * used by almost all free and commercial tile providers. Assumes that Earth is
   * a sphere. Used by the `EPSG:3857` CRS.
   */

  var earthRadius = 6378137;

  var SphericalMercator = {

  	R: earthRadius,
  	MAX_LATITUDE: 85.0511287798,

  	project: function (latlng) {
  		var d = Math.PI / 180,
  		    max = this.MAX_LATITUDE,
  		    lat = Math.max(Math.min(max, latlng.lat), -max),
  		    sin = Math.sin(lat * d);

  		return new Point(
  			this.R * latlng.lng * d,
  			this.R * Math.log((1 + sin) / (1 - sin)) / 2);
  	},

  	unproject: function (point) {
  		var d = 180 / Math.PI;

  		return new LatLng(
  			(2 * Math.atan(Math.exp(point.y / this.R)) - (Math.PI / 2)) * d,
  			point.x * d / this.R);
  	},

  	bounds: (function () {
  		var d = earthRadius * Math.PI;
  		return new Bounds([-d, -d], [d, d]);
  	})()
  };

  /*
   * @class Transformation
   * @aka L.Transformation
   *
   * Represents an affine transformation: a set of coefficients `a`, `b`, `c`, `d`
   * for transforming a point of a form `(x, y)` into `(a*x + b, c*y + d)` and doing
   * the reverse. Used by Leaflet in its projections code.
   *
   * @example
   *
   * ```js
   * var transformation = L.transformation(2, 5, -1, 10),
   * 	p = L.point(1, 2),
   * 	p2 = transformation.transform(p), //  L.point(7, 8)
   * 	p3 = transformation.untransform(p2); //  L.point(1, 2)
   * ```
   */


  // factory new L.Transformation(a: Number, b: Number, c: Number, d: Number)
  // Creates a `Transformation` object with the given coefficients.
  function Transformation(a, b, c, d) {
  	if (isArray(a)) {
  		// use array properties
  		this._a = a[0];
  		this._b = a[1];
  		this._c = a[2];
  		this._d = a[3];
  		return;
  	}
  	this._a = a;
  	this._b = b;
  	this._c = c;
  	this._d = d;
  }

  Transformation.prototype = {
  	// @method transform(point: Point, scale?: Number): Point
  	// Returns a transformed point, optionally multiplied by the given scale.
  	// Only accepts actual `L.Point` instances, not arrays.
  	transform: function (point, scale) { // (Point, Number) -> Point
  		return this._transform(point.clone(), scale);
  	},

  	// destructive transform (faster)
  	_transform: function (point, scale) {
  		scale = scale || 1;
  		point.x = scale * (this._a * point.x + this._b);
  		point.y = scale * (this._c * point.y + this._d);
  		return point;
  	},

  	// @method untransform(point: Point, scale?: Number): Point
  	// Returns the reverse transformation of the given point, optionally divided
  	// by the given scale. Only accepts actual `L.Point` instances, not arrays.
  	untransform: function (point, scale) {
  		scale = scale || 1;
  		return new Point(
  		        (point.x / scale - this._b) / this._a,
  		        (point.y / scale - this._d) / this._c);
  	}
  };

  // factory L.transformation(a: Number, b: Number, c: Number, d: Number)

  // @factory L.transformation(a: Number, b: Number, c: Number, d: Number)
  // Instantiates a Transformation object with the given coefficients.

  // @alternative
  // @factory L.transformation(coefficients: Array): Transformation
  // Expects an coefficients array of the form
  // `[a: Number, b: Number, c: Number, d: Number]`.

  function toTransformation(a, b, c, d) {
  	return new Transformation(a, b, c, d);
  }

  /*
   * @namespace CRS
   * @crs L.CRS.EPSG3857
   *
   * The most common CRS for online maps, used by almost all free and commercial
   * tile providers. Uses Spherical Mercator projection. Set in by default in
   * Map's `crs` option.
   */

  var EPSG3857 = extend({}, Earth, {
  	code: 'EPSG:3857',
  	projection: SphericalMercator,

  	transformation: (function () {
  		var scale = 0.5 / (Math.PI * SphericalMercator.R);
  		return toTransformation(scale, 0.5, -scale, 0.5);
  	}())
  });

  var EPSG900913 = extend({}, EPSG3857, {
  	code: 'EPSG:900913'
  });

  // @namespace SVG; @section
  // There are several static functions which can be called without instantiating L.SVG:

  // @function create(name: String): SVGElement
  // Returns a instance of [SVGElement](https://developer.mozilla.org/docs/Web/API/SVGElement),
  // corresponding to the class name passed. For example, using 'line' will return
  // an instance of [SVGLineElement](https://developer.mozilla.org/docs/Web/API/SVGLineElement).
  function svgCreate(name) {
  	return document.createElementNS('http://www.w3.org/2000/svg', name);
  }

  // @function pointsToPath(rings: Point[], closed: Boolean): String
  // Generates a SVG path string for multiple rings, with each ring turning
  // into "M..L..L.." instructions
  function pointsToPath(rings, closed) {
  	var str = '',
  	i, j, len, len2, points, p;

  	for (i = 0, len = rings.length; i < len; i++) {
  		points = rings[i];

  		for (j = 0, len2 = points.length; j < len2; j++) {
  			p = points[j];
  			str += (j ? 'L' : 'M') + p.x + ' ' + p.y;
  		}

  		// closes the ring for polygons; "x" is VML syntax
  		str += closed ? (Browser.svg ? 'z' : 'x') : '';
  	}

  	// SVG complains about empty path strings
  	return str || 'M0 0';
  }

  /*
   * @namespace Browser
   * @aka L.Browser
   *
   * A namespace with static properties for browser/feature detection used by Leaflet internally.
   *
   * @example
   *
   * ```js
   * if (L.Browser.ielt9) {
   *   alert('Upgrade your browser, dude!');
   * }
   * ```
   */

  var style = document.documentElement.style;

  // @property ie: Boolean; `true` for all Internet Explorer versions (not Edge).
  var ie = 'ActiveXObject' in window;

  // @property ielt9: Boolean; `true` for Internet Explorer versions less than 9.
  var ielt9 = ie && !document.addEventListener;

  // @property edge: Boolean; `true` for the Edge web browser.
  var edge = 'msLaunchUri' in navigator && !('documentMode' in document);

  // @property webkit: Boolean;
  // `true` for webkit-based browsers like Chrome and Safari (including mobile versions).
  var webkit = userAgentContains('webkit');

  // @property android: Boolean
  // **Deprecated.** `true` for any browser running on an Android platform.
  var android = userAgentContains('android');

  // @property android23: Boolean; **Deprecated.** `true` for browsers running on Android 2 or Android 3.
  var android23 = userAgentContains('android 2') || userAgentContains('android 3');

  /* See https://stackoverflow.com/a/17961266 for details on detecting stock Android */
  var webkitVer = parseInt(/WebKit\/([0-9]+)|$/.exec(navigator.userAgent)[1], 10); // also matches AppleWebKit
  // @property androidStock: Boolean; **Deprecated.** `true` for the Android stock browser (i.e. not Chrome)
  var androidStock = android && userAgentContains('Google') && webkitVer < 537 && !('AudioNode' in window);

  // @property opera: Boolean; `true` for the Opera browser
  var opera = !!window.opera;

  // @property chrome: Boolean; `true` for the Chrome browser.
  var chrome = !edge && userAgentContains('chrome');

  // @property gecko: Boolean; `true` for gecko-based browsers like Firefox.
  var gecko = userAgentContains('gecko') && !webkit && !opera && !ie;

  // @property safari: Boolean; `true` for the Safari browser.
  var safari = !chrome && userAgentContains('safari');

  var phantom = userAgentContains('phantom');

  // @property opera12: Boolean
  // `true` for the Opera browser supporting CSS transforms (version 12 or later).
  var opera12 = 'OTransition' in style;

  // @property win: Boolean; `true` when the browser is running in a Windows platform
  var win = navigator.platform.indexOf('Win') === 0;

  // @property ie3d: Boolean; `true` for all Internet Explorer versions supporting CSS transforms.
  var ie3d = ie && ('transition' in style);

  // @property webkit3d: Boolean; `true` for webkit-based browsers supporting CSS transforms.
  var webkit3d = ('WebKitCSSMatrix' in window) && ('m11' in new window.WebKitCSSMatrix()) && !android23;

  // @property gecko3d: Boolean; `true` for gecko-based browsers supporting CSS transforms.
  var gecko3d = 'MozPerspective' in style;

  // @property any3d: Boolean
  // `true` for all browsers supporting CSS transforms.
  var any3d = !window.L_DISABLE_3D && (ie3d || webkit3d || gecko3d) && !opera12 && !phantom;

  // @property mobile: Boolean; `true` for all browsers running in a mobile device.
  var mobile = typeof orientation !== 'undefined' || userAgentContains('mobile');

  // @property mobileWebkit: Boolean; `true` for all webkit-based browsers in a mobile device.
  var mobileWebkit = mobile && webkit;

  // @property mobileWebkit3d: Boolean
  // `true` for all webkit-based browsers in a mobile device supporting CSS transforms.
  var mobileWebkit3d = mobile && webkit3d;

  // @property msPointer: Boolean
  // `true` for browsers implementing the Microsoft touch events model (notably IE10).
  var msPointer = !window.PointerEvent && window.MSPointerEvent;

  // @property pointer: Boolean
  // `true` for all browsers supporting [pointer events](https://msdn.microsoft.com/en-us/library/dn433244%28v=vs.85%29.aspx).
  var pointer = !!(window.PointerEvent || msPointer);

  // @property touchNative: Boolean
  // `true` for all browsers supporting [touch events](https://developer.mozilla.org/docs/Web/API/Touch_events).
  // **This does not necessarily mean** that the browser is running in a computer with
  // a touchscreen, it only means that the browser is capable of understanding
  // touch events.
  var touchNative = 'ontouchstart' in window || !!window.TouchEvent;

  // @property touch: Boolean
  // `true` for all browsers supporting either [touch](#browser-touch) or [pointer](#browser-pointer) events.
  // Note: pointer events will be preferred (if available), and processed for all `touch*` listeners.
  var touch = !window.L_NO_TOUCH && (touchNative || pointer);

  // @property mobileOpera: Boolean; `true` for the Opera browser in a mobile device.
  var mobileOpera = mobile && opera;

  // @property mobileGecko: Boolean
  // `true` for gecko-based browsers running in a mobile device.
  var mobileGecko = mobile && gecko;

  // @property retina: Boolean
  // `true` for browsers on a high-resolution "retina" screen or on any screen when browser's display zoom is more than 100%.
  var retina = (window.devicePixelRatio || (window.screen.deviceXDPI / window.screen.logicalXDPI)) > 1;

  // @property passiveEvents: Boolean
  // `true` for browsers that support passive events.
  var passiveEvents = (function () {
  	var supportsPassiveOption = false;
  	try {
  		var opts = Object.defineProperty({}, 'passive', {
  			get: function () { // eslint-disable-line getter-return
  				supportsPassiveOption = true;
  			}
  		});
  		window.addEventListener('testPassiveEventSupport', falseFn, opts);
  		window.removeEventListener('testPassiveEventSupport', falseFn, opts);
  	} catch (e) {
  		// Errors can safely be ignored since this is only a browser support test.
  	}
  	return supportsPassiveOption;
  }());

  // @property canvas: Boolean
  // `true` when the browser supports [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
  var canvas$1 = (function () {
  	return !!document.createElement('canvas').getContext;
  }());

  // @property svg: Boolean
  // `true` when the browser supports [SVG](https://developer.mozilla.org/docs/Web/SVG).
  var svg$1 = !!(document.createElementNS && svgCreate('svg').createSVGRect);

  var inlineSvg = !!svg$1 && (function () {
  	var div = document.createElement('div');
  	div.innerHTML = '<svg/>';
  	return (div.firstChild && div.firstChild.namespaceURI) === 'http://www.w3.org/2000/svg';
  })();

  // @property vml: Boolean
  // `true` if the browser supports [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language).
  var vml = !svg$1 && (function () {
  	try {
  		var div = document.createElement('div');
  		div.innerHTML = '<v:shape adj="1"/>';

  		var shape = div.firstChild;
  		shape.style.behavior = 'url(#default#VML)';

  		return shape && (typeof shape.adj === 'object');

  	} catch (e) {
  		return false;
  	}
  }());


  // @property mac: Boolean; `true` when the browser is running in a Mac platform
  var mac = navigator.platform.indexOf('Mac') === 0;

  // @property mac: Boolean; `true` when the browser is running in a Linux platform
  var linux = navigator.platform.indexOf('Linux') === 0;

  function userAgentContains(str) {
  	return navigator.userAgent.toLowerCase().indexOf(str) >= 0;
  }


  var Browser = {
  	ie: ie,
  	ielt9: ielt9,
  	edge: edge,
  	webkit: webkit,
  	android: android,
  	android23: android23,
  	androidStock: androidStock,
  	opera: opera,
  	chrome: chrome,
  	gecko: gecko,
  	safari: safari,
  	phantom: phantom,
  	opera12: opera12,
  	win: win,
  	ie3d: ie3d,
  	webkit3d: webkit3d,
  	gecko3d: gecko3d,
  	any3d: any3d,
  	mobile: mobile,
  	mobileWebkit: mobileWebkit,
  	mobileWebkit3d: mobileWebkit3d,
  	msPointer: msPointer,
  	pointer: pointer,
  	touch: touch,
  	touchNative: touchNative,
  	mobileOpera: mobileOpera,
  	mobileGecko: mobileGecko,
  	retina: retina,
  	passiveEvents: passiveEvents,
  	canvas: canvas$1,
  	svg: svg$1,
  	vml: vml,
  	inlineSvg: inlineSvg,
  	mac: mac,
  	linux: linux
  };

  /*
   * Extends L.DomEvent to provide touch support for Internet Explorer and Windows-based devices.
   */

  var POINTER_DOWN =   Browser.msPointer ? 'MSPointerDown'   : 'pointerdown';
  var POINTER_MOVE =   Browser.msPointer ? 'MSPointerMove'   : 'pointermove';
  var POINTER_UP =     Browser.msPointer ? 'MSPointerUp'     : 'pointerup';
  var POINTER_CANCEL = Browser.msPointer ? 'MSPointerCancel' : 'pointercancel';
  var pEvent = {
  	touchstart  : POINTER_DOWN,
  	touchmove   : POINTER_MOVE,
  	touchend    : POINTER_UP,
  	touchcancel : POINTER_CANCEL
  };
  var handle = {
  	touchstart  : _onPointerStart,
  	touchmove   : _handlePointer,
  	touchend    : _handlePointer,
  	touchcancel : _handlePointer
  };
  var _pointers = {};
  var _pointerDocListener = false;

  // Provides a touch events wrapper for (ms)pointer events.
  // ref https://www.w3.org/TR/pointerevents/ https://www.w3.org/Bugs/Public/show_bug.cgi?id=22890

  function addPointerListener(obj, type, handler) {
  	if (type === 'touchstart') {
  		_addPointerDocListener();
  	}
  	if (!handle[type]) {
  		console.warn('wrong event specified:', type);
  		return falseFn;
  	}
  	handler = handle[type].bind(this, handler);
  	obj.addEventListener(pEvent[type], handler, false);
  	return handler;
  }

  function removePointerListener(obj, type, handler) {
  	if (!pEvent[type]) {
  		console.warn('wrong event specified:', type);
  		return;
  	}
  	obj.removeEventListener(pEvent[type], handler, false);
  }

  function _globalPointerDown(e) {
  	_pointers[e.pointerId] = e;
  }

  function _globalPointerMove(e) {
  	if (_pointers[e.pointerId]) {
  		_pointers[e.pointerId] = e;
  	}
  }

  function _globalPointerUp(e) {
  	delete _pointers[e.pointerId];
  }

  function _addPointerDocListener() {
  	// need to keep track of what pointers and how many are active to provide e.touches emulation
  	if (!_pointerDocListener) {
  		// we listen document as any drags that end by moving the touch off the screen get fired there
  		document.addEventListener(POINTER_DOWN, _globalPointerDown, true);
  		document.addEventListener(POINTER_MOVE, _globalPointerMove, true);
  		document.addEventListener(POINTER_UP, _globalPointerUp, true);
  		document.addEventListener(POINTER_CANCEL, _globalPointerUp, true);

  		_pointerDocListener = true;
  	}
  }

  function _handlePointer(handler, e) {
  	if (e.pointerType === (e.MSPOINTER_TYPE_MOUSE || 'mouse')) { return; }

  	e.touches = [];
  	for (var i in _pointers) {
  		e.touches.push(_pointers[i]);
  	}
  	e.changedTouches = [e];

  	handler(e);
  }

  function _onPointerStart(handler, e) {
  	// IE10 specific: MsTouch needs preventDefault. See #2000
  	if (e.MSPOINTER_TYPE_TOUCH && e.pointerType === e.MSPOINTER_TYPE_TOUCH) {
  		preventDefault(e);
  	}
  	_handlePointer(handler, e);
  }

  /*
   * Extends the event handling code with double tap support for mobile browsers.
   *
   * Note: currently most browsers fire native dblclick, with only a few exceptions
   * (see https://github.com/Leaflet/Leaflet/issues/7012#issuecomment-595087386)
   */

  function makeDblclick(event) {
  	// in modern browsers `type` cannot be just overridden:
  	// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Getter_only
  	var newEvent = {},
  	    prop, i;
  	for (i in event) {
  		prop = event[i];
  		newEvent[i] = prop && prop.bind ? prop.bind(event) : prop;
  	}
  	event = newEvent;
  	newEvent.type = 'dblclick';
  	newEvent.detail = 2;
  	newEvent.isTrusted = false;
  	newEvent._simulated = true; // for debug purposes
  	return newEvent;
  }

  var delay = 200;
  function addDoubleTapListener(obj, handler) {
  	// Most browsers handle double tap natively
  	obj.addEventListener('dblclick', handler);

  	// On some platforms the browser doesn't fire native dblclicks for touch events.
  	// It seems that in all such cases `detail` property of `click` event is always `1`.
  	// So here we rely on that fact to avoid excessive 'dblclick' simulation when not needed.
  	var last = 0,
  	    detail;
  	function simDblclick(e) {
  		if (e.detail !== 1) {
  			detail = e.detail; // keep in sync to avoid false dblclick in some cases
  			return;
  		}

  		if (e.pointerType === 'mouse' ||
  			(e.sourceCapabilities && !e.sourceCapabilities.firesTouchEvents)) {

  			return;
  		}

  		// When clicking on an <input>, the browser generates a click on its
  		// <label> (and vice versa) triggering two clicks in quick succession.
  		// This ignores clicks on elements which are a label with a 'for'
  		// attribute (or children of such a label), but not children of
  		// a <input>.
  		var path = getPropagationPath(e);
  		if (path.some(function (el) {
  			return el instanceof HTMLLabelElement && el.attributes.for;
  		}) &&
  			!path.some(function (el) {
  				return (
  					el instanceof HTMLInputElement ||
  					el instanceof HTMLSelectElement
  				);
  			})
  		) {
  			return;
  		}

  		var now = Date.now();
  		if (now - last <= delay) {
  			detail++;
  			if (detail === 2) {
  				handler(makeDblclick(e));
  			}
  		} else {
  			detail = 1;
  		}
  		last = now;
  	}

  	obj.addEventListener('click', simDblclick);

  	return {
  		dblclick: handler,
  		simDblclick: simDblclick
  	};
  }

  function removeDoubleTapListener(obj, handlers) {
  	obj.removeEventListener('dblclick', handlers.dblclick);
  	obj.removeEventListener('click', handlers.simDblclick);
  }

  /*
   * @namespace DomUtil
   *
   * Utility functions to work with the [DOM](https://developer.mozilla.org/docs/Web/API/Document_Object_Model)
   * tree, used by Leaflet internally.
   *
   * Most functions expecting or returning a `HTMLElement` also work for
   * SVG elements. The only difference is that classes refer to CSS classes
   * in HTML and SVG classes in SVG.
   */


  // @property TRANSFORM: String
  // Vendor-prefixed transform style name (e.g. `'webkitTransform'` for WebKit).
  var TRANSFORM = testProp(
  	['transform', 'webkitTransform', 'OTransform', 'MozTransform', 'msTransform']);

  // webkitTransition comes first because some browser versions that drop vendor prefix don't do
  // the same for the transitionend event, in particular the Android 4.1 stock browser

  // @property TRANSITION: String
  // Vendor-prefixed transition style name.
  var TRANSITION = testProp(
  	['webkitTransition', 'transition', 'OTransition', 'MozTransition', 'msTransition']);

  // @property TRANSITION_END: String
  // Vendor-prefixed transitionend event name.
  var TRANSITION_END =
  	TRANSITION === 'webkitTransition' || TRANSITION === 'OTransition' ? TRANSITION + 'End' : 'transitionend';


  // @function get(id: String|HTMLElement): HTMLElement
  // Returns an element given its DOM id, or returns the element itself
  // if it was passed directly.
  function get(id) {
  	return typeof id === 'string' ? document.getElementById(id) : id;
  }

  // @function getStyle(el: HTMLElement, styleAttrib: String): String
  // Returns the value for a certain style attribute on an element,
  // including computed values or values set through CSS.
  function getStyle(el, style) {
  	var value = el.style[style] || (el.currentStyle && el.currentStyle[style]);

  	if ((!value || value === 'auto') && document.defaultView) {
  		var css = document.defaultView.getComputedStyle(el, null);
  		value = css ? css[style] : null;
  	}
  	return value === 'auto' ? null : value;
  }

  // @function create(tagName: String, className?: String, container?: HTMLElement): HTMLElement
  // Creates an HTML element with `tagName`, sets its class to `className`, and optionally appends it to `container` element.
  function create$1(tagName, className, container) {
  	var el = document.createElement(tagName);
  	el.className = className || '';

  	if (container) {
  		container.appendChild(el);
  	}
  	return el;
  }

  // @function remove(el: HTMLElement)
  // Removes `el` from its parent element
  function remove(el) {
  	var parent = el.parentNode;
  	if (parent) {
  		parent.removeChild(el);
  	}
  }

  // @function empty(el: HTMLElement)
  // Removes all of `el`'s children elements from `el`
  function empty(el) {
  	while (el.firstChild) {
  		el.removeChild(el.firstChild);
  	}
  }

  // @function toFront(el: HTMLElement)
  // Makes `el` the last child of its parent, so it renders in front of the other children.
  function toFront(el) {
  	var parent = el.parentNode;
  	if (parent && parent.lastChild !== el) {
  		parent.appendChild(el);
  	}
  }

  // @function toBack(el: HTMLElement)
  // Makes `el` the first child of its parent, so it renders behind the other children.
  function toBack(el) {
  	var parent = el.parentNode;
  	if (parent && parent.firstChild !== el) {
  		parent.insertBefore(el, parent.firstChild);
  	}
  }

  // @function hasClass(el: HTMLElement, name: String): Boolean
  // Returns `true` if the element's class attribute contains `name`.
  function hasClass(el, name) {
  	if (el.classList !== undefined) {
  		return el.classList.contains(name);
  	}
  	var className = getClass(el);
  	return className.length > 0 && new RegExp('(^|\\s)' + name + '(\\s|$)').test(className);
  }

  // @function addClass(el: HTMLElement, name: String)
  // Adds `name` to the element's class attribute.
  function addClass(el, name) {
  	if (el.classList !== undefined) {
  		var classes = splitWords(name);
  		for (var i = 0, len = classes.length; i < len; i++) {
  			el.classList.add(classes[i]);
  		}
  	} else if (!hasClass(el, name)) {
  		var className = getClass(el);
  		setClass(el, (className ? className + ' ' : '') + name);
  	}
  }

  // @function removeClass(el: HTMLElement, name: String)
  // Removes `name` from the element's class attribute.
  function removeClass(el, name) {
  	if (el.classList !== undefined) {
  		el.classList.remove(name);
  	} else {
  		setClass(el, trim((' ' + getClass(el) + ' ').replace(' ' + name + ' ', ' ')));
  	}
  }

  // @function setClass(el: HTMLElement, name: String)
  // Sets the element's class.
  function setClass(el, name) {
  	if (el.className.baseVal === undefined) {
  		el.className = name;
  	} else {
  		// in case of SVG element
  		el.className.baseVal = name;
  	}
  }

  // @function getClass(el: HTMLElement): String
  // Returns the element's class.
  function getClass(el) {
  	// Check if the element is an SVGElementInstance and use the correspondingElement instead
  	// (Required for linked SVG elements in IE11.)
  	if (el.correspondingElement) {
  		el = el.correspondingElement;
  	}
  	return el.className.baseVal === undefined ? el.className : el.className.baseVal;
  }

  // @function setOpacity(el: HTMLElement, opacity: Number)
  // Set the opacity of an element (including old IE support).
  // `opacity` must be a number from `0` to `1`.
  function setOpacity(el, value) {
  	if ('opacity' in el.style) {
  		el.style.opacity = value;
  	} else if ('filter' in el.style) {
  		_setOpacityIE(el, value);
  	}
  }

  function _setOpacityIE(el, value) {
  	var filter = false,
  	    filterName = 'DXImageTransform.Microsoft.Alpha';

  	// filters collection throws an error if we try to retrieve a filter that doesn't exist
  	try {
  		filter = el.filters.item(filterName);
  	} catch (e) {
  		// don't set opacity to 1 if we haven't already set an opacity,
  		// it isn't needed and breaks transparent pngs.
  		if (value === 1) { return; }
  	}

  	value = Math.round(value * 100);

  	if (filter) {
  		filter.Enabled = (value !== 100);
  		filter.Opacity = value;
  	} else {
  		el.style.filter += ' progid:' + filterName + '(opacity=' + value + ')';
  	}
  }

  // @function testProp(props: String[]): String|false
  // Goes through the array of style names and returns the first name
  // that is a valid style name for an element. If no such name is found,
  // it returns false. Useful for vendor-prefixed styles like `transform`.
  function testProp(props) {
  	var style = document.documentElement.style;

  	for (var i = 0; i < props.length; i++) {
  		if (props[i] in style) {
  			return props[i];
  		}
  	}
  	return false;
  }

  // @function setTransform(el: HTMLElement, offset: Point, scale?: Number)
  // Resets the 3D CSS transform of `el` so it is translated by `offset` pixels
  // and optionally scaled by `scale`. Does not have an effect if the
  // browser doesn't support 3D CSS transforms.
  function setTransform(el, offset, scale) {
  	var pos = offset || new Point(0, 0);

  	el.style[TRANSFORM] =
  		(Browser.ie3d ?
  			'translate(' + pos.x + 'px,' + pos.y + 'px)' :
  			'translate3d(' + pos.x + 'px,' + pos.y + 'px,0)') +
  		(scale ? ' scale(' + scale + ')' : '');
  }

  // @function setPosition(el: HTMLElement, position: Point)
  // Sets the position of `el` to coordinates specified by `position`,
  // using CSS translate or top/left positioning depending on the browser
  // (used by Leaflet internally to position its layers).
  function setPosition(el, point) {

  	/*eslint-disable */
  	el._leaflet_pos = point;
  	/* eslint-enable */

  	if (Browser.any3d) {
  		setTransform(el, point);
  	} else {
  		el.style.left = point.x + 'px';
  		el.style.top = point.y + 'px';
  	}
  }

  // @function getPosition(el: HTMLElement): Point
  // Returns the coordinates of an element previously positioned with setPosition.
  function getPosition(el) {
  	// this method is only used for elements previously positioned using setPosition,
  	// so it's safe to cache the position for performance

  	return el._leaflet_pos || new Point(0, 0);
  }

  // @function disableTextSelection()
  // Prevents the user from generating `selectstart` DOM events, usually generated
  // when the user drags the mouse through a page with text. Used internally
  // by Leaflet to override the behaviour of any click-and-drag interaction on
  // the map. Affects drag interactions on the whole document.

  // @function enableTextSelection()
  // Cancels the effects of a previous [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection).
  var disableTextSelection;
  var enableTextSelection;
  var _userSelect;
  if ('onselectstart' in document) {
  	disableTextSelection = function () {
  		on(window, 'selectstart', preventDefault);
  	};
  	enableTextSelection = function () {
  		off(window, 'selectstart', preventDefault);
  	};
  } else {
  	var userSelectProperty = testProp(
  		['userSelect', 'WebkitUserSelect', 'OUserSelect', 'MozUserSelect', 'msUserSelect']);

  	disableTextSelection = function () {
  		if (userSelectProperty) {
  			var style = document.documentElement.style;
  			_userSelect = style[userSelectProperty];
  			style[userSelectProperty] = 'none';
  		}
  	};
  	enableTextSelection = function () {
  		if (userSelectProperty) {
  			document.documentElement.style[userSelectProperty] = _userSelect;
  			_userSelect = undefined;
  		}
  	};
  }

  // @function disableImageDrag()
  // As [`L.DomUtil.disableTextSelection`](#domutil-disabletextselection), but
  // for `dragstart` DOM events, usually generated when the user drags an image.
  function disableImageDrag() {
  	on(window, 'dragstart', preventDefault);
  }

  // @function enableImageDrag()
  // Cancels the effects of a previous [`L.DomUtil.disableImageDrag`](#domutil-disabletextselection).
  function enableImageDrag() {
  	off(window, 'dragstart', preventDefault);
  }

  var _outlineElement, _outlineStyle;
  // @function preventOutline(el: HTMLElement)
  // Makes the [outline](https://developer.mozilla.org/docs/Web/CSS/outline)
  // of the element `el` invisible. Used internally by Leaflet to prevent
  // focusable elements from displaying an outline when the user performs a
  // drag interaction on them.
  function preventOutline(element) {
  	while (element.tabIndex === -1) {
  		element = element.parentNode;
  	}
  	if (!element.style) { return; }
  	restoreOutline();
  	_outlineElement = element;
  	_outlineStyle = element.style.outlineStyle;
  	element.style.outlineStyle = 'none';
  	on(window, 'keydown', restoreOutline);
  }

  // @function restoreOutline()
  // Cancels the effects of a previous [`L.DomUtil.preventOutline`]().
  function restoreOutline() {
  	if (!_outlineElement) { return; }
  	_outlineElement.style.outlineStyle = _outlineStyle;
  	_outlineElement = undefined;
  	_outlineStyle = undefined;
  	off(window, 'keydown', restoreOutline);
  }

  // @function getSizedParentNode(el: HTMLElement): HTMLElement
  // Finds the closest parent node which size (width and height) is not null.
  function getSizedParentNode(element) {
  	do {
  		element = element.parentNode;
  	} while ((!element.offsetWidth || !element.offsetHeight) && element !== document.body);
  	return element;
  }

  // @function getScale(el: HTMLElement): Object
  // Computes the CSS scale currently applied on the element.
  // Returns an object with `x` and `y` members as horizontal and vertical scales respectively,
  // and `boundingClientRect` as the result of [`getBoundingClientRect()`](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect).
  function getScale(element) {
  	var rect = element.getBoundingClientRect(); // Read-only in old browsers.

  	return {
  		x: rect.width / element.offsetWidth || 1,
  		y: rect.height / element.offsetHeight || 1,
  		boundingClientRect: rect
  	};
  }

  var DomUtil = {
    __proto__: null,
    TRANSFORM: TRANSFORM,
    TRANSITION: TRANSITION,
    TRANSITION_END: TRANSITION_END,
    get: get,
    getStyle: getStyle,
    create: create$1,
    remove: remove,
    empty: empty,
    toFront: toFront,
    toBack: toBack,
    hasClass: hasClass,
    addClass: addClass,
    removeClass: removeClass,
    setClass: setClass,
    getClass: getClass,
    setOpacity: setOpacity,
    testProp: testProp,
    setTransform: setTransform,
    setPosition: setPosition,
    getPosition: getPosition,
    get disableTextSelection () { return disableTextSelection; },
    get enableTextSelection () { return enableTextSelection; },
    disableImageDrag: disableImageDrag,
    enableImageDrag: enableImageDrag,
    preventOutline: preventOutline,
    restoreOutline: restoreOutline,
    getSizedParentNode: getSizedParentNode,
    getScale: getScale
  };

  /*
   * @namespace DomEvent
   * Utility functions to work with the [DOM events](https://developer.mozilla.org/docs/Web/API/Event), used by Leaflet internally.
   */

  // Inspired by John Resig, Dean Edwards and YUI addEvent implementations.

  // @function on(el: HTMLElement, types: String, fn: Function, context?: Object): this
  // Adds a listener function (`fn`) to a particular DOM event type of the
  // element `el`. You can optionally specify the context of the listener
  // (object the `this` keyword will point to). You can also pass several
  // space-separated types (e.g. `'click dblclick'`).

  // @alternative
  // @function on(el: HTMLElement, eventMap: Object, context?: Object): this
  // Adds a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`
  function on(obj, types, fn, context) {

  	if (types && typeof types === 'object') {
  		for (var type in types) {
  			addOne(obj, type, types[type], fn);
  		}
  	} else {
  		types = splitWords(types);

  		for (var i = 0, len = types.length; i < len; i++) {
  			addOne(obj, types[i], fn, context);
  		}
  	}

  	return this;
  }

  var eventsKey = '_leaflet_events';

  // @function off(el: HTMLElement, types: String, fn: Function, context?: Object): this
  // Removes a previously added listener function.
  // Note that if you passed a custom context to on, you must pass the same
  // context to `off` in order to remove the listener.

  // @alternative
  // @function off(el: HTMLElement, eventMap: Object, context?: Object): this
  // Removes a set of type/listener pairs, e.g. `{click: onClick, mousemove: onMouseMove}`

  // @alternative
  // @function off(el: HTMLElement, types: String): this
  // Removes all previously added listeners of given types.

  // @alternative
  // @function off(el: HTMLElement): this
  // Removes all previously added listeners from given HTMLElement
  function off(obj, types, fn, context) {

  	if (arguments.length === 1) {
  		batchRemove(obj);
  		delete obj[eventsKey];

  	} else if (types && typeof types === 'object') {
  		for (var type in types) {
  			removeOne(obj, type, types[type], fn);
  		}

  	} else {
  		types = splitWords(types);

  		if (arguments.length === 2) {
  			batchRemove(obj, function (type) {
  				return indexOf(types, type) !== -1;
  			});
  		} else {
  			for (var i = 0, len = types.length; i < len; i++) {
  				removeOne(obj, types[i], fn, context);
  			}
  		}
  	}

  	return this;
  }

  function batchRemove(obj, filterFn) {
  	for (var id in obj[eventsKey]) {
  		var type = id.split(/\d/)[0];
  		if (!filterFn || filterFn(type)) {
  			removeOne(obj, type, null, null, id);
  		}
  	}
  }

  var mouseSubst = {
  	mouseenter: 'mouseover',
  	mouseleave: 'mouseout',
  	wheel: !('onwheel' in window) && 'mousewheel'
  };

  function addOne(obj, type, fn, context) {
  	var id = type + stamp(fn) + (context ? '_' + stamp(context) : '');

  	if (obj[eventsKey] && obj[eventsKey][id]) { return this; }

  	var handler = function (e) {
  		return fn.call(context || obj, e || window.event);
  	};

  	var originalHandler = handler;

  	if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
  		// Needs DomEvent.Pointer.js
  		handler = addPointerListener(obj, type, handler);

  	} else if (Browser.touch && (type === 'dblclick')) {
  		handler = addDoubleTapListener(obj, handler);

  	} else if ('addEventListener' in obj) {

  		if (type === 'touchstart' || type === 'touchmove' || type === 'wheel' ||  type === 'mousewheel') {
  			obj.addEventListener(mouseSubst[type] || type, handler, Browser.passiveEvents ? {passive: false} : false);

  		} else if (type === 'mouseenter' || type === 'mouseleave') {
  			handler = function (e) {
  				e = e || window.event;
  				if (isExternalTarget(obj, e)) {
  					originalHandler(e);
  				}
  			};
  			obj.addEventListener(mouseSubst[type], handler, false);

  		} else {
  			obj.addEventListener(type, originalHandler, false);
  		}

  	} else {
  		obj.attachEvent('on' + type, handler);
  	}

  	obj[eventsKey] = obj[eventsKey] || {};
  	obj[eventsKey][id] = handler;
  }

  function removeOne(obj, type, fn, context, id) {
  	id = id || type + stamp(fn) + (context ? '_' + stamp(context) : '');
  	var handler = obj[eventsKey] && obj[eventsKey][id];

  	if (!handler) { return this; }

  	if (!Browser.touchNative && Browser.pointer && type.indexOf('touch') === 0) {
  		removePointerListener(obj, type, handler);

  	} else if (Browser.touch && (type === 'dblclick')) {
  		removeDoubleTapListener(obj, handler);

  	} else if ('removeEventListener' in obj) {

  		obj.removeEventListener(mouseSubst[type] || type, handler, false);

  	} else {
  		obj.detachEvent('on' + type, handler);
  	}

  	obj[eventsKey][id] = null;
  }

  // @function stopPropagation(ev: DOMEvent): this
  // Stop the given event from propagation to parent elements. Used inside the listener functions:
  // ```js
  // L.DomEvent.on(div, 'click', function (ev) {
  // 	L.DomEvent.stopPropagation(ev);
  // });
  // ```
  function stopPropagation(e) {

  	if (e.stopPropagation) {
  		e.stopPropagation();
  	} else if (e.originalEvent) {  // In case of Leaflet event.
  		e.originalEvent._stopped = true;
  	} else {
  		e.cancelBubble = true;
  	}

  	return this;
  }

  // @function disableScrollPropagation(el: HTMLElement): this
  // Adds `stopPropagation` to the element's `'wheel'` events (plus browser variants).
  function disableScrollPropagation(el) {
  	addOne(el, 'wheel', stopPropagation);
  	return this;
  }

  // @function disableClickPropagation(el: HTMLElement): this
  // Adds `stopPropagation` to the element's `'click'`, `'dblclick'`, `'contextmenu'`,
  // `'mousedown'` and `'touchstart'` events (plus browser variants).
  function disableClickPropagation(el) {
  	on(el, 'mousedown touchstart dblclick contextmenu', stopPropagation);
  	el['_leaflet_disable_click'] = true;
  	return this;
  }

  // @function preventDefault(ev: DOMEvent): this
  // Prevents the default action of the DOM Event `ev` from happening (such as
  // following a link in the href of the a element, or doing a POST request
  // with page reload when a `<form>` is submitted).
  // Use it inside listener functions.
  function preventDefault(e) {
  	if (e.preventDefault) {
  		e.preventDefault();
  	} else {
  		e.returnValue = false;
  	}
  	return this;
  }

  // @function stop(ev: DOMEvent): this
  // Does `stopPropagation` and `preventDefault` at the same time.
  function stop(e) {
  	preventDefault(e);
  	stopPropagation(e);
  	return this;
  }

  // @function getPropagationPath(ev: DOMEvent): Array
  // Compatibility polyfill for [`Event.composedPath()`](https://developer.mozilla.org/en-US/docs/Web/API/Event/composedPath).
  // Returns an array containing the `HTMLElement`s that the given DOM event
  // should propagate to (if not stopped).
  function getPropagationPath(ev) {
  	if (ev.composedPath) {
  		return ev.composedPath();
  	}

  	var path = [];
  	var el = ev.target;

  	while (el) {
  		path.push(el);
  		el = el.parentNode;
  	}
  	return path;
  }


  // @function getMousePosition(ev: DOMEvent, container?: HTMLElement): Point
  // Gets normalized mouse position from a DOM event relative to the
  // `container` (border excluded) or to the whole page if not specified.
  function getMousePosition(e, container) {
  	if (!container) {
  		return new Point(e.clientX, e.clientY);
  	}

  	var scale = getScale(container),
  	    offset = scale.boundingClientRect; // left and top  values are in page scale (like the event clientX/Y)

  	return new Point(
  		// offset.left/top values are in page scale (like clientX/Y),
  		// whereas clientLeft/Top (border width) values are the original values (before CSS scale applies).
  		(e.clientX - offset.left) / scale.x - container.clientLeft,
  		(e.clientY - offset.top) / scale.y - container.clientTop
  	);
  }


  //  except , Safari and
  // We need double the scroll pixels (see #7403 and #4538) for all Browsers
  // except OSX (Mac) -> 3x, Chrome running on Linux 1x

  var wheelPxFactor =
  	(Browser.linux && Browser.chrome) ? window.devicePixelRatio :
  	Browser.mac ? window.devicePixelRatio * 3 :
  	window.devicePixelRatio > 0 ? 2 * window.devicePixelRatio : 1;
  // @function getWheelDelta(ev: DOMEvent): Number
  // Gets normalized wheel delta from a wheel DOM event, in vertical
  // pixels scrolled (negative if scrolling down).
  // Events from pointing devices without precise scrolling are mapped to
  // a best guess of 60 pixels.
  function getWheelDelta(e) {
  	return (Browser.edge) ? e.wheelDeltaY / 2 : // Don't trust window-geometry-based delta
  	       (e.deltaY && e.deltaMode === 0) ? -e.deltaY / wheelPxFactor : // Pixels
  	       (e.deltaY && e.deltaMode === 1) ? -e.deltaY * 20 : // Lines
  	       (e.deltaY && e.deltaMode === 2) ? -e.deltaY * 60 : // Pages
  	       (e.deltaX || e.deltaZ) ? 0 :	// Skip horizontal/depth wheel events
  	       e.wheelDelta ? (e.wheelDeltaY || e.wheelDelta) / 2 : // Legacy IE pixels
  	       (e.detail && Math.abs(e.detail) < 32765) ? -e.detail * 20 : // Legacy Moz lines
  	       e.detail ? e.detail / -32765 * 60 : // Legacy Moz pages
  	       0;
  }

  // check if element really left/entered the event target (for mouseenter/mouseleave)
  function isExternalTarget(el, e) {

  	var related = e.relatedTarget;

  	if (!related) { return true; }

  	try {
  		while (related && (related !== el)) {
  			related = related.parentNode;
  		}
  	} catch (err) {
  		return false;
  	}
  	return (related !== el);
  }

  var DomEvent = {
    __proto__: null,
    on: on,
    off: off,
    stopPropagation: stopPropagation,
    disableScrollPropagation: disableScrollPropagation,
    disableClickPropagation: disableClickPropagation,
    preventDefault: preventDefault,
    stop: stop,
    getPropagationPath: getPropagationPath,
    getMousePosition: getMousePosition,
    getWheelDelta: getWheelDelta,
    isExternalTarget: isExternalTarget,
    addListener: on,
    removeListener: off
  };

  /*
   * @class PosAnimation
   * @aka L.PosAnimation
   * @inherits Evented
   * Used internally for panning animations, utilizing CSS3 Transitions for modern browsers and a timer fallback for IE6-9.
   *
   * @example
   * ```js
   * var myPositionMarker = L.marker([48.864716, 2.294694]).addTo(map);
   *
   * myPositionMarker.on("click", function() {
   * 	var pos = map.latLngToLayerPoint(myPositionMarker.getLatLng());
   * 	pos.y -= 25;
   * 	var fx = new L.PosAnimation();
   *
   * 	fx.once('end',function() {
   * 		pos.y += 25;
   * 		fx.run(myPositionMarker._icon, pos, 0.8);
   * 	});
   *
   * 	fx.run(myPositionMarker._icon, pos, 0.3);
   * });
   *
   * ```
   *
   * @constructor L.PosAnimation()
   * Creates a `PosAnimation` object.
   *
   */

  var PosAnimation = Evented.extend({

  	// @method run(el: HTMLElement, newPos: Point, duration?: Number, easeLinearity?: Number)
  	// Run an animation of a given element to a new position, optionally setting
  	// duration in seconds (`0.25` by default) and easing linearity factor (3rd
  	// argument of the [cubic bezier curve](https://cubic-bezier.com/#0,0,.5,1),
  	// `0.5` by default).
  	run: function (el, newPos, duration, easeLinearity) {
  		this.stop();

  		this._el = el;
  		this._inProgress = true;
  		this._duration = duration || 0.25;
  		this._easeOutPower = 1 / Math.max(easeLinearity || 0.5, 0.2);

  		this._startPos = getPosition(el);
  		this._offset = newPos.subtract(this._startPos);
  		this._startTime = +new Date();

  		// @event start: Event
  		// Fired when the animation starts
  		this.fire('start');

  		this._animate();
  	},

  	// @method stop()
  	// Stops the animation (if currently running).
  	stop: function () {
  		if (!this._inProgress) { return; }

  		this._step(true);
  		this._complete();
  	},

  	_animate: function () {
  		// animation loop
  		this._animId = requestAnimFrame(this._animate, this);
  		this._step();
  	},

  	_step: function (round) {
  		var elapsed = (+new Date()) - this._startTime,
  		    duration = this._duration * 1000;

  		if (elapsed < duration) {
  			this._runFrame(this._easeOut(elapsed / duration), round);
  		} else {
  			this._runFrame(1);
  			this._complete();
  		}
  	},

  	_runFrame: function (progress, round) {
  		var pos = this._startPos.add(this._offset.multiplyBy(progress));
  		if (round) {
  			pos._round();
  		}
  		setPosition(this._el, pos);

  		// @event step: Event
  		// Fired continuously during the animation.
  		this.fire('step');
  	},

  	_complete: function () {
  		cancelAnimFrame(this._animId);

  		this._inProgress = false;
  		// @event end: Event
  		// Fired when the animation ends.
  		this.fire('end');
  	},

  	_easeOut: function (t) {
  		return 1 - Math.pow(1 - t, this._easeOutPower);
  	}
  });

  /*
   * @class Map
   * @aka L.Map
   * @inherits Evented
   *
   * The central class of the API — it is used to create a map on a page and manipulate it.
   *
   * @example
   *
   * ```js
   * // initialize the map on the "map" div with a given center and zoom
   * var map = L.map('map', {
   * 	center: [51.505, -0.09],
   * 	zoom: 13
   * });
   * ```
   *
   */

  var Map = Evented.extend({

  	options: {
  		// @section Map State Options
  		// @option crs: CRS = L.CRS.EPSG3857
  		// The [Coordinate Reference System](#crs) to use. Don't change this if you're not
  		// sure what it means.
  		crs: EPSG3857,

  		// @option center: LatLng = undefined
  		// Initial geographic center of the map
  		center: undefined,

  		// @option zoom: Number = undefined
  		// Initial map zoom level
  		zoom: undefined,

  		// @option minZoom: Number = *
  		// Minimum zoom level of the map.
  		// If not specified and at least one `GridLayer` or `TileLayer` is in the map,
  		// the lowest of their `minZoom` options will be used instead.
  		minZoom: undefined,

  		// @option maxZoom: Number = *
  		// Maximum zoom level of the map.
  		// If not specified and at least one `GridLayer` or `TileLayer` is in the map,
  		// the highest of their `maxZoom` options will be used instead.
  		maxZoom: undefined,

  		// @option layers: Layer[] = []
  		// Array of layers that will be added to the map initially
  		layers: [],

  		// @option maxBounds: LatLngBounds = null
  		// When this option is set, the map restricts the view to the given
  		// geographical bounds, bouncing the user back if the user tries to pan
  		// outside the view. To set the restriction dynamically, use
  		// [`setMaxBounds`](#map-setmaxbounds) method.
  		maxBounds: undefined,

  		// @option renderer: Renderer = *
  		// The default method for drawing vector layers on the map. `L.SVG`
  		// or `L.Canvas` by default depending on browser support.
  		renderer: undefined,


  		// @section Animation Options
  		// @option zoomAnimation: Boolean = true
  		// Whether the map zoom animation is enabled. By default it's enabled
  		// in all browsers that support CSS3 Transitions except Android.
  		zoomAnimation: true,

  		// @option zoomAnimationThreshold: Number = 4
  		// Won't animate zoom if the zoom difference exceeds this value.
  		zoomAnimationThreshold: 4,

  		// @option fadeAnimation: Boolean = true
  		// Whether the tile fade animation is enabled. By default it's enabled
  		// in all browsers that support CSS3 Transitions except Android.
  		fadeAnimation: true,

  		// @option markerZoomAnimation: Boolean = true
  		// Whether markers animate their zoom with the zoom animation, if disabled
  		// they will disappear for the length of the animation. By default it's
  		// enabled in all browsers that support CSS3 Transitions except Android.
  		markerZoomAnimation: true,

  		// @option transform3DLimit: Number = 2^23
  		// Defines the maximum size of a CSS translation transform. The default
  		// value should not be changed unless a web browser positions layers in
  		// the wrong place after doing a large `panBy`.
  		transform3DLimit: 8388608, // Precision limit of a 32-bit float

  		// @section Interaction Options
  		// @option zoomSnap: Number = 1
  		// Forces the map's zoom level to always be a multiple of this, particularly
  		// right after a [`fitBounds()`](#map-fitbounds) or a pinch-zoom.
  		// By default, the zoom level snaps to the nearest integer; lower values
  		// (e.g. `0.5` or `0.1`) allow for greater granularity. A value of `0`
  		// means the zoom level will not be snapped after `fitBounds` or a pinch-zoom.
  		zoomSnap: 1,

  		// @option zoomDelta: Number = 1
  		// Controls how much the map's zoom level will change after a
  		// [`zoomIn()`](#map-zoomin), [`zoomOut()`](#map-zoomout), pressing `+`
  		// or `-` on the keyboard, or using the [zoom controls](#control-zoom).
  		// Values smaller than `1` (e.g. `0.5`) allow for greater granularity.
  		zoomDelta: 1,

  		// @option trackResize: Boolean = true
  		// Whether the map automatically handles browser window resize to update itself.
  		trackResize: true
  	},

  	initialize: function (id, options) { // (HTMLElement or String, Object)
  		options = setOptions(this, options);

  		// Make sure to assign internal flags at the beginning,
  		// to avoid inconsistent state in some edge cases.
  		this._handlers = [];
  		this._layers = {};
  		this._zoomBoundLayers = {};
  		this._sizeChanged = true;

  		this._initContainer(id);
  		this._initLayout();

  		// hack for https://github.com/Leaflet/Leaflet/issues/1980
  		this._onResize = bind(this._onResize, this);

  		this._initEvents();

  		if (options.maxBounds) {
  			this.setMaxBounds(options.maxBounds);
  		}

  		if (options.zoom !== undefined) {
  			this._zoom = this._limitZoom(options.zoom);
  		}

  		if (options.center && options.zoom !== undefined) {
  			this.setView(toLatLng(options.center), options.zoom, {reset: true});
  		}

  		this.callInitHooks();

  		// don't animate on browsers without hardware-accelerated transitions or old Android/Opera
  		this._zoomAnimated = TRANSITION && Browser.any3d && !Browser.mobileOpera &&
  				this.options.zoomAnimation;

  		// zoom transitions run with the same duration for all layers, so if one of transitionend events
  		// happens after starting zoom animation (propagating to the map pane), we know that it ended globally
  		if (this._zoomAnimated) {
  			this._createAnimProxy();
  			on(this._proxy, TRANSITION_END, this._catchTransitionEnd, this);
  		}

  		this._addLayers(this.options.layers);
  	},


  	// @section Methods for modifying map state

  	// @method setView(center: LatLng, zoom: Number, options?: Zoom/pan options): this
  	// Sets the view of the map (geographical center and zoom) with the given
  	// animation options.
  	setView: function (center, zoom, options) {

  		zoom = zoom === undefined ? this._zoom : this._limitZoom(zoom);
  		center = this._limitCenter(toLatLng(center), zoom, this.options.maxBounds);
  		options = options || {};

  		this._stop();

  		if (this._loaded && !options.reset && options !== true) {

  			if (options.animate !== undefined) {
  				options.zoom = extend({animate: options.animate}, options.zoom);
  				options.pan = extend({animate: options.animate, duration: options.duration}, options.pan);
  			}

  			// try animating pan or zoom
  			var moved = (this._zoom !== zoom) ?
  				this._tryAnimatedZoom && this._tryAnimatedZoom(center, zoom, options.zoom) :
  				this._tryAnimatedPan(center, options.pan);

  			if (moved) {
  				// prevent resize handler call, the view will refresh after animation anyway
  				clearTimeout(this._sizeTimer);
  				return this;
  			}
  		}

  		// animation didn't start, just reset the map view
  		this._resetView(center, zoom, options.pan && options.pan.noMoveStart);

  		return this;
  	},

  	// @method setZoom(zoom: Number, options?: Zoom/pan options): this
  	// Sets the zoom of the map.
  	setZoom: function (zoom, options) {
  		if (!this._loaded) {
  			this._zoom = zoom;
  			return this;
  		}
  		return this.setView(this.getCenter(), zoom, {zoom: options});
  	},

  	// @method zoomIn(delta?: Number, options?: Zoom options): this
  	// Increases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
  	zoomIn: function (delta, options) {
  		delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
  		return this.setZoom(this._zoom + delta, options);
  	},

  	// @method zoomOut(delta?: Number, options?: Zoom options): this
  	// Decreases the zoom of the map by `delta` ([`zoomDelta`](#map-zoomdelta) by default).
  	zoomOut: function (delta, options) {
  		delta = delta || (Browser.any3d ? this.options.zoomDelta : 1);
  		return this.setZoom(this._zoom - delta, options);
  	},

  	// @method setZoomAround(latlng: LatLng, zoom: Number, options: Zoom options): this
  	// Zooms the map while keeping a specified geographical point on the map
  	// stationary (e.g. used internally for scroll zoom and double-click zoom).
  	// @alternative
  	// @method setZoomAround(offset: Point, zoom: Number, options: Zoom options): this
  	// Zooms the map while keeping a specified pixel on the map (relative to the top-left corner) stationary.
  	setZoomAround: function (latlng, zoom, options) {
  		var scale = this.getZoomScale(zoom),
  		    viewHalf = this.getSize().divideBy(2),
  		    containerPoint = latlng instanceof Point ? latlng : this.latLngToContainerPoint(latlng),

  		    centerOffset = containerPoint.subtract(viewHalf).multiplyBy(1 - 1 / scale),
  		    newCenter = this.containerPointToLatLng(viewHalf.add(centerOffset));

  		return this.setView(newCenter, zoom, {zoom: options});
  	},

  	_getBoundsCenterZoom: function (bounds, options) {

  		options = options || {};
  		bounds = bounds.getBounds ? bounds.getBounds() : toLatLngBounds(bounds);

  		var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
  		    paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),

  		    zoom = this.getBoundsZoom(bounds, false, paddingTL.add(paddingBR));

  		zoom = (typeof options.maxZoom === 'number') ? Math.min(options.maxZoom, zoom) : zoom;

  		if (zoom === Infinity) {
  			return {
  				center: bounds.getCenter(),
  				zoom: zoom
  			};
  		}

  		var paddingOffset = paddingBR.subtract(paddingTL).divideBy(2),

  		    swPoint = this.project(bounds.getSouthWest(), zoom),
  		    nePoint = this.project(bounds.getNorthEast(), zoom),
  		    center = this.unproject(swPoint.add(nePoint).divideBy(2).add(paddingOffset), zoom);

  		return {
  			center: center,
  			zoom: zoom
  		};
  	},

  	// @method fitBounds(bounds: LatLngBounds, options?: fitBounds options): this
  	// Sets a map view that contains the given geographical bounds with the
  	// maximum zoom level possible.
  	fitBounds: function (bounds, options) {

  		bounds = toLatLngBounds(bounds);

  		if (!bounds.isValid()) {
  			throw new Error('Bounds are not valid.');
  		}

  		var target = this._getBoundsCenterZoom(bounds, options);
  		return this.setView(target.center, target.zoom, options);
  	},

  	// @method fitWorld(options?: fitBounds options): this
  	// Sets a map view that mostly contains the whole world with the maximum
  	// zoom level possible.
  	fitWorld: function (options) {
  		return this.fitBounds([[-90, -180], [90, 180]], options);
  	},

  	// @method panTo(latlng: LatLng, options?: Pan options): this
  	// Pans the map to a given center.
  	panTo: function (center, options) { // (LatLng)
  		return this.setView(center, this._zoom, {pan: options});
  	},

  	// @method panBy(offset: Point, options?: Pan options): this
  	// Pans the map by a given number of pixels (animated).
  	panBy: function (offset, options) {
  		offset = toPoint(offset).round();
  		options = options || {};

  		if (!offset.x && !offset.y) {
  			return this.fire('moveend');
  		}
  		// If we pan too far, Chrome gets issues with tiles
  		// and makes them disappear or appear in the wrong place (slightly offset) #2602
  		if (options.animate !== true && !this.getSize().contains(offset)) {
  			this._resetView(this.unproject(this.project(this.getCenter()).add(offset)), this.getZoom());
  			return this;
  		}

  		if (!this._panAnim) {
  			this._panAnim = new PosAnimation();

  			this._panAnim.on({
  				'step': this._onPanTransitionStep,
  				'end': this._onPanTransitionEnd
  			}, this);
  		}

  		// don't fire movestart if animating inertia
  		if (!options.noMoveStart) {
  			this.fire('movestart');
  		}

  		// animate pan unless animate: false specified
  		if (options.animate !== false) {
  			addClass(this._mapPane, 'leaflet-pan-anim');

  			var newPos = this._getMapPanePos().subtract(offset).round();
  			this._panAnim.run(this._mapPane, newPos, options.duration || 0.25, options.easeLinearity);
  		} else {
  			this._rawPanBy(offset);
  			this.fire('move').fire('moveend');
  		}

  		return this;
  	},

  	// @method flyTo(latlng: LatLng, zoom?: Number, options?: Zoom/pan options): this
  	// Sets the view of the map (geographical center and zoom) performing a smooth
  	// pan-zoom animation.
  	flyTo: function (targetCenter, targetZoom, options) {

  		options = options || {};
  		if (options.animate === false || !Browser.any3d) {
  			return this.setView(targetCenter, targetZoom, options);
  		}

  		this._stop();

  		var from = this.project(this.getCenter()),
  		    to = this.project(targetCenter),
  		    size = this.getSize(),
  		    startZoom = this._zoom;

  		targetCenter = toLatLng(targetCenter);
  		targetZoom = targetZoom === undefined ? startZoom : targetZoom;

  		var w0 = Math.max(size.x, size.y),
  		    w1 = w0 * this.getZoomScale(startZoom, targetZoom),
  		    u1 = (to.distanceTo(from)) || 1,
  		    rho = 1.42,
  		    rho2 = rho * rho;

  		function r(i) {
  			var s1 = i ? -1 : 1,
  			    s2 = i ? w1 : w0,
  			    t1 = w1 * w1 - w0 * w0 + s1 * rho2 * rho2 * u1 * u1,
  			    b1 = 2 * s2 * rho2 * u1,
  			    b = t1 / b1,
  			    sq = Math.sqrt(b * b + 1) - b;

  			    // workaround for floating point precision bug when sq = 0, log = -Infinite,
  			    // thus triggering an infinite loop in flyTo
  			    var log = sq < 0.000000001 ? -18 : Math.log(sq);

  			return log;
  		}

  		function sinh(n) { return (Math.exp(n) - Math.exp(-n)) / 2; }
  		function cosh(n) { return (Math.exp(n) + Math.exp(-n)) / 2; }
  		function tanh(n) { return sinh(n) / cosh(n); }

  		var r0 = r(0);

  		function w(s) { return w0 * (cosh(r0) / cosh(r0 + rho * s)); }
  		function u(s) { return w0 * (cosh(r0) * tanh(r0 + rho * s) - sinh(r0)) / rho2; }

  		function easeOut(t) { return 1 - Math.pow(1 - t, 1.5); }

  		var start = Date.now(),
  		    S = (r(1) - r0) / rho,
  		    duration = options.duration ? 1000 * options.duration : 1000 * S * 0.8;

  		function frame() {
  			var t = (Date.now() - start) / duration,
  			    s = easeOut(t) * S;

  			if (t <= 1) {
  				this._flyToFrame = requestAnimFrame(frame, this);

  				this._move(
  					this.unproject(from.add(to.subtract(from).multiplyBy(u(s) / u1)), startZoom),
  					this.getScaleZoom(w0 / w(s), startZoom),
  					{flyTo: true});

  			} else {
  				this
  					._move(targetCenter, targetZoom)
  					._moveEnd(true);
  			}
  		}

  		this._moveStart(true, options.noMoveStart);

  		frame.call(this);
  		return this;
  	},

  	// @method flyToBounds(bounds: LatLngBounds, options?: fitBounds options): this
  	// Sets the view of the map with a smooth animation like [`flyTo`](#map-flyto),
  	// but takes a bounds parameter like [`fitBounds`](#map-fitbounds).
  	flyToBounds: function (bounds, options) {
  		var target = this._getBoundsCenterZoom(bounds, options);
  		return this.flyTo(target.center, target.zoom, options);
  	},

  	// @method setMaxBounds(bounds: LatLngBounds): this
  	// Restricts the map view to the given bounds (see the [maxBounds](#map-maxbounds) option).
  	setMaxBounds: function (bounds) {
  		bounds = toLatLngBounds(bounds);

  		if (this.listens('moveend', this._panInsideMaxBounds)) {
  			this.off('moveend', this._panInsideMaxBounds);
  		}

  		if (!bounds.isValid()) {
  			this.options.maxBounds = null;
  			return this;
  		}

  		this.options.maxBounds = bounds;

  		if (this._loaded) {
  			this._panInsideMaxBounds();
  		}

  		return this.on('moveend', this._panInsideMaxBounds);
  	},

  	// @method setMinZoom(zoom: Number): this
  	// Sets the lower limit for the available zoom levels (see the [minZoom](#map-minzoom) option).
  	setMinZoom: function (zoom) {
  		var oldZoom = this.options.minZoom;
  		this.options.minZoom = zoom;

  		if (this._loaded && oldZoom !== zoom) {
  			this.fire('zoomlevelschange');

  			if (this.getZoom() < this.options.minZoom) {
  				return this.setZoom(zoom);
  			}
  		}

  		return this;
  	},

  	// @method setMaxZoom(zoom: Number): this
  	// Sets the upper limit for the available zoom levels (see the [maxZoom](#map-maxzoom) option).
  	setMaxZoom: function (zoom) {
  		var oldZoom = this.options.maxZoom;
  		this.options.maxZoom = zoom;

  		if (this._loaded && oldZoom !== zoom) {
  			this.fire('zoomlevelschange');

  			if (this.getZoom() > this.options.maxZoom) {
  				return this.setZoom(zoom);
  			}
  		}

  		return this;
  	},

  	// @method panInsideBounds(bounds: LatLngBounds, options?: Pan options): this
  	// Pans the map to the closest view that would lie inside the given bounds (if it's not already), controlling the animation using the options specific, if any.
  	panInsideBounds: function (bounds, options) {
  		this._enforcingBounds = true;
  		var center = this.getCenter(),
  		    newCenter = this._limitCenter(center, this._zoom, toLatLngBounds(bounds));

  		if (!center.equals(newCenter)) {
  			this.panTo(newCenter, options);
  		}

  		this._enforcingBounds = false;
  		return this;
  	},

  	// @method panInside(latlng: LatLng, options?: padding options): this
  	// Pans the map the minimum amount to make the `latlng` visible. Use
  	// padding options to fit the display to more restricted bounds.
  	// If `latlng` is already within the (optionally padded) display bounds,
  	// the map will not be panned.
  	panInside: function (latlng, options) {
  		options = options || {};

  		var paddingTL = toPoint(options.paddingTopLeft || options.padding || [0, 0]),
  		    paddingBR = toPoint(options.paddingBottomRight || options.padding || [0, 0]),
  		    pixelCenter = this.project(this.getCenter()),
  		    pixelPoint = this.project(latlng),
  		    pixelBounds = this.getPixelBounds(),
  		    paddedBounds = toBounds([pixelBounds.min.add(paddingTL), pixelBounds.max.subtract(paddingBR)]),
  		    paddedSize = paddedBounds.getSize();

  		if (!paddedBounds.contains(pixelPoint)) {
  			this._enforcingBounds = true;
  			var centerOffset = pixelPoint.subtract(paddedBounds.getCenter());
  			var offset = paddedBounds.extend(pixelPoint).getSize().subtract(paddedSize);
  			pixelCenter.x += centerOffset.x < 0 ? -offset.x : offset.x;
  			pixelCenter.y += centerOffset.y < 0 ? -offset.y : offset.y;
  			this.panTo(this.unproject(pixelCenter), options);
  			this._enforcingBounds = false;
  		}
  		return this;
  	},

  	// @method invalidateSize(options: Zoom/pan options): this
  	// Checks if the map container size changed and updates the map if so —
  	// call it after you've changed the map size dynamically, also animating
  	// pan by default. If `options.pan` is `false`, panning will not occur.
  	// If `options.debounceMoveend` is `true`, it will delay `moveend` event so
  	// that it doesn't happen often even if the method is called many
  	// times in a row.

  	// @alternative
  	// @method invalidateSize(animate: Boolean): this
  	// Checks if the map container size changed and updates the map if so —
  	// call it after you've changed the map size dynamically, also animating
  	// pan by default.
  	invalidateSize: function (options) {
  		if (!this._loaded) { return this; }

  		options = extend({
  			animate: false,
  			pan: true
  		}, options === true ? {animate: true} : options);

  		var oldSize = this.getSize();
  		this._sizeChanged = true;
  		this._lastCenter = null;

  		var newSize = this.getSize(),
  		    oldCenter = oldSize.divideBy(2).round(),
  		    newCenter = newSize.divideBy(2).round(),
  		    offset = oldCenter.subtract(newCenter);

  		if (!offset.x && !offset.y) { return this; }

  		if (options.animate && options.pan) {
  			this.panBy(offset);

  		} else {
  			if (options.pan) {
  				this._rawPanBy(offset);
  			}

  			this.fire('move');

  			if (options.debounceMoveend) {
  				clearTimeout(this._sizeTimer);
  				this._sizeTimer = setTimeout(bind(this.fire, this, 'moveend'), 200);
  			} else {
  				this.fire('moveend');
  			}
  		}

  		// @section Map state change events
  		// @event resize: ResizeEvent
  		// Fired when the map is resized.
  		return this.fire('resize', {
  			oldSize: oldSize,
  			newSize: newSize
  		});
  	},

  	// @section Methods for modifying map state
  	// @method stop(): this
  	// Stops the currently running `panTo` or `flyTo` animation, if any.
  	stop: function () {
  		this.setZoom(this._limitZoom(this._zoom));
  		if (!this.options.zoomSnap) {
  			this.fire('viewreset');
  		}
  		return this._stop();
  	},

  	// @section Geolocation methods
  	// @method locate(options?: Locate options): this
  	// Tries to locate the user using the Geolocation API, firing a [`locationfound`](#map-locationfound)
  	// event with location data on success or a [`locationerror`](#map-locationerror) event on failure,
  	// and optionally sets the map view to the user's location with respect to
  	// detection accuracy (or to the world view if geolocation failed).
  	// Note that, if your page doesn't use HTTPS, this method will fail in
  	// modern browsers ([Chrome 50 and newer](https://sites.google.com/a/chromium.org/dev/Home/chromium-security/deprecating-powerful-features-on-insecure-origins))
  	// See `Locate options` for more details.
  	locate: function (options) {

  		options = this._locateOptions = extend({
  			timeout: 10000,
  			watch: false
  			// setView: false
  			// maxZoom: <Number>
  			// maximumAge: 0
  			// enableHighAccuracy: false
  		}, options);

  		if (!('geolocation' in navigator)) {
  			this._handleGeolocationError({
  				code: 0,
  				message: 'Geolocation not supported.'
  			});
  			return this;
  		}

  		var onResponse = bind(this._handleGeolocationResponse, this),
  		    onError = bind(this._handleGeolocationError, this);

  		if (options.watch) {
  			this._locationWatchId =
  			        navigator.geolocation.watchPosition(onResponse, onError, options);
  		} else {
  			navigator.geolocation.getCurrentPosition(onResponse, onError, options);
  		}
  		return this;
  	},

  	// @method stopLocate(): this
  	// Stops watching location previously initiated by `map.locate({watch: true})`
  	// and aborts resetting the map view if map.locate was called with
  	// `{setView: true}`.
  	stopLocate: function () {
  		if (navigator.geolocation && navigator.geolocation.clearWatch) {
  			navigator.geolocation.clearWatch(this._locationWatchId);
  		}
  		if (this._locateOptions) {
  			this._locateOptions.setView = false;
  		}
  		return this;
  	},

  	_handleGeolocationError: function (error) {
  		if (!this._container._leaflet_id) { return; }

  		var c = error.code,
  		    message = error.message ||
  		            (c === 1 ? 'permission denied' :
  		            (c === 2 ? 'position unavailable' : 'timeout'));

  		if (this._locateOptions.setView && !this._loaded) {
  			this.fitWorld();
  		}

  		// @section Location events
  		// @event locationerror: ErrorEvent
  		// Fired when geolocation (using the [`locate`](#map-locate) method) failed.
  		this.fire('locationerror', {
  			code: c,
  			message: 'Geolocation error: ' + message + '.'
  		});
  	},

  	_handleGeolocationResponse: function (pos) {
  		if (!this._container._leaflet_id) { return; }

  		var lat = pos.coords.latitude,
  		    lng = pos.coords.longitude,
  		    latlng = new LatLng(lat, lng),
  		    bounds = latlng.toBounds(pos.coords.accuracy * 2),
  		    options = this._locateOptions;

  		if (options.setView) {
  			var zoom = this.getBoundsZoom(bounds);
  			this.setView(latlng, options.maxZoom ? Math.min(zoom, options.maxZoom) : zoom);
  		}

  		var data = {
  			latlng: latlng,
  			bounds: bounds,
  			timestamp: pos.timestamp
  		};

  		for (var i in pos.coords) {
  			if (typeof pos.coords[i] === 'number') {
  				data[i] = pos.coords[i];
  			}
  		}

  		// @event locationfound: LocationEvent
  		// Fired when geolocation (using the [`locate`](#map-locate) method)
  		// went successfully.
  		this.fire('locationfound', data);
  	},

  	// TODO Appropriate docs section?
  	// @section Other Methods
  	// @method addHandler(name: String, HandlerClass: Function): this
  	// Adds a new `Handler` to the map, given its name and constructor function.
  	addHandler: function (name, HandlerClass) {
  		if (!HandlerClass) { return this; }

  		var handler = this[name] = new HandlerClass(this);

  		this._handlers.push(handler);

  		if (this.options[name]) {
  			handler.enable();
  		}

  		return this;
  	},

  	// @method remove(): this
  	// Destroys the map and clears all related event listeners.
  	remove: function () {

  		this._initEvents(true);
  		if (this.options.maxBounds) { this.off('moveend', this._panInsideMaxBounds); }

  		if (this._containerId !== this._container._leaflet_id) {
  			throw new Error('Map container is being reused by another instance');
  		}

  		try {
  			// throws error in IE6-8
  			delete this._container._leaflet_id;
  			delete this._containerId;
  		} catch (e) {
  			/*eslint-disable */
  			this._container._leaflet_id = undefined;
  			/* eslint-enable */
  			this._containerId = undefined;
  		}

  		if (this._locationWatchId !== undefined) {
  			this.stopLocate();
  		}

  		this._stop();

  		remove(this._mapPane);

  		if (this._clearControlPos) {
  			this._clearControlPos();
  		}
  		if (this._resizeRequest) {
  			cancelAnimFrame(this._resizeRequest);
  			this._resizeRequest = null;
  		}

  		this._clearHandlers();

  		if (this._loaded) {
  			// @section Map state change events
  			// @event unload: Event
  			// Fired when the map is destroyed with [remove](#map-remove) method.
  			this.fire('unload');
  		}

  		var i;
  		for (i in this._layers) {
  			this._layers[i].remove();
  		}
  		for (i in this._panes) {
  			remove(this._panes[i]);
  		}

  		this._layers = [];
  		this._panes = [];
  		delete this._mapPane;
  		delete this._renderer;

  		return this;
  	},

  	// @section Other Methods
  	// @method createPane(name: String, container?: HTMLElement): HTMLElement
  	// Creates a new [map pane](#map-pane) with the given name if it doesn't exist already,
  	// then returns it. The pane is created as a child of `container`, or
  	// as a child of the main map pane if not set.
  	createPane: function (name, container) {
  		var className = 'leaflet-pane' + (name ? ' leaflet-' + name.replace('Pane', '') + '-pane' : ''),
  		    pane = create$1('div', className, container || this._mapPane);

  		if (name) {
  			this._panes[name] = pane;
  		}
  		return pane;
  	},

  	// @section Methods for Getting Map State

  	// @method getCenter(): LatLng
  	// Returns the geographical center of the map view
  	getCenter: function () {
  		this._checkIfLoaded();

  		if (this._lastCenter && !this._moved()) {
  			return this._lastCenter.clone();
  		}
  		return this.layerPointToLatLng(this._getCenterLayerPoint());
  	},

  	// @method getZoom(): Number
  	// Returns the current zoom level of the map view
  	getZoom: function () {
  		return this._zoom;
  	},

  	// @method getBounds(): LatLngBounds
  	// Returns the geographical bounds visible in the current map view
  	getBounds: function () {
  		var bounds = this.getPixelBounds(),
  		    sw = this.unproject(bounds.getBottomLeft()),
  		    ne = this.unproject(bounds.getTopRight());

  		return new LatLngBounds(sw, ne);
  	},

  	// @method getMinZoom(): Number
  	// Returns the minimum zoom level of the map (if set in the `minZoom` option of the map or of any layers), or `0` by default.
  	getMinZoom: function () {
  		return this.options.minZoom === undefined ? this._layersMinZoom || 0 : this.options.minZoom;
  	},

  	// @method getMaxZoom(): Number
  	// Returns the maximum zoom level of the map (if set in the `maxZoom` option of the map or of any layers).
  	getMaxZoom: function () {
  		return this.options.maxZoom === undefined ?
  			(this._layersMaxZoom === undefined ? Infinity : this._layersMaxZoom) :
  			this.options.maxZoom;
  	},

  	// @method getBoundsZoom(bounds: LatLngBounds, inside?: Boolean, padding?: Point): Number
  	// Returns the maximum zoom level on which the given bounds fit to the map
  	// view in its entirety. If `inside` (optional) is set to `true`, the method
  	// instead returns the minimum zoom level on which the map view fits into
  	// the given bounds in its entirety.
  	getBoundsZoom: function (bounds, inside, padding) { // (LatLngBounds[, Boolean, Point]) -> Number
  		bounds = toLatLngBounds(bounds);
  		padding = toPoint(padding || [0, 0]);

  		var zoom = this.getZoom() || 0,
  		    min = this.getMinZoom(),
  		    max = this.getMaxZoom(),
  		    nw = bounds.getNorthWest(),
  		    se = bounds.getSouthEast(),
  		    size = this.getSize().subtract(padding),
  		    boundsSize = toBounds(this.project(se, zoom), this.project(nw, zoom)).getSize(),
  		    snap = Browser.any3d ? this.options.zoomSnap : 1,
  		    scalex = size.x / boundsSize.x,
  		    scaley = size.y / boundsSize.y,
  		    scale = inside ? Math.max(scalex, scaley) : Math.min(scalex, scaley);

  		zoom = this.getScaleZoom(scale, zoom);

  		if (snap) {
  			zoom = Math.round(zoom / (snap / 100)) * (snap / 100); // don't jump if within 1% of a snap level
  			zoom = inside ? Math.ceil(zoom / snap) * snap : Math.floor(zoom / snap) * snap;
  		}

  		return Math.max(min, Math.min(max, zoom));
  	},

  	// @method getSize(): Point
  	// Returns the current size of the map container (in pixels).
  	getSize: function () {
  		if (!this._size || this._sizeChanged) {
  			this._size = new Point(
  				this._container.clientWidth || 0,
  				this._container.clientHeight || 0);

  			this._sizeChanged = false;
  		}
  		return this._size.clone();
  	},

  	// @method getPixelBounds(): Bounds
  	// Returns the bounds of the current map view in projected pixel
  	// coordinates (sometimes useful in layer and overlay implementations).
  	getPixelBounds: function (center, zoom) {
  		var topLeftPoint = this._getTopLeftPoint(center, zoom);
  		return new Bounds(topLeftPoint, topLeftPoint.add(this.getSize()));
  	},

  	// TODO: Check semantics - isn't the pixel origin the 0,0 coord relative to
  	// the map pane? "left point of the map layer" can be confusing, specially
  	// since there can be negative offsets.
  	// @method getPixelOrigin(): Point
  	// Returns the projected pixel coordinates of the top left point of
  	// the map layer (useful in custom layer and overlay implementations).
  	getPixelOrigin: function () {
  		this._checkIfLoaded();
  		return this._pixelOrigin;
  	},

  	// @method getPixelWorldBounds(zoom?: Number): Bounds
  	// Returns the world's bounds in pixel coordinates for zoom level `zoom`.
  	// If `zoom` is omitted, the map's current zoom level is used.
  	getPixelWorldBounds: function (zoom) {
  		return this.options.crs.getProjectedBounds(zoom === undefined ? this.getZoom() : zoom);
  	},

  	// @section Other Methods

  	// @method getPane(pane: String|HTMLElement): HTMLElement
  	// Returns a [map pane](#map-pane), given its name or its HTML element (its identity).
  	getPane: function (pane) {
  		return typeof pane === 'string' ? this._panes[pane] : pane;
  	},

  	// @method getPanes(): Object
  	// Returns a plain object containing the names of all [panes](#map-pane) as keys and
  	// the panes as values.
  	getPanes: function () {
  		return this._panes;
  	},

  	// @method getContainer: HTMLElement
  	// Returns the HTML element that contains the map.
  	getContainer: function () {
  		return this._container;
  	},


  	// @section Conversion Methods

  	// @method getZoomScale(toZoom: Number, fromZoom: Number): Number
  	// Returns the scale factor to be applied to a map transition from zoom level
  	// `fromZoom` to `toZoom`. Used internally to help with zoom animations.
  	getZoomScale: function (toZoom, fromZoom) {
  		// TODO replace with universal implementation after refactoring projections
  		var crs = this.options.crs;
  		fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
  		return crs.scale(toZoom) / crs.scale(fromZoom);
  	},

  	// @method getScaleZoom(scale: Number, fromZoom: Number): Number
  	// Returns the zoom level that the map would end up at, if it is at `fromZoom`
  	// level and everything is scaled by a factor of `scale`. Inverse of
  	// [`getZoomScale`](#map-getZoomScale).
  	getScaleZoom: function (scale, fromZoom) {
  		var crs = this.options.crs;
  		fromZoom = fromZoom === undefined ? this._zoom : fromZoom;
  		var zoom = crs.zoom(scale * crs.scale(fromZoom));
  		return isNaN(zoom) ? Infinity : zoom;
  	},

  	// @method project(latlng: LatLng, zoom: Number): Point
  	// Projects a geographical coordinate `LatLng` according to the projection
  	// of the map's CRS, then scales it according to `zoom` and the CRS's
  	// `Transformation`. The result is pixel coordinate relative to
  	// the CRS origin.
  	project: function (latlng, zoom) {
  		zoom = zoom === undefined ? this._zoom : zoom;
  		return this.options.crs.latLngToPoint(toLatLng(latlng), zoom);
  	},

  	// @method unproject(point: Point, zoom: Number): LatLng
  	// Inverse of [`project`](#map-project).
  	unproject: function (point, zoom) {
  		zoom = zoom === undefined ? this._zoom : zoom;
  		return this.options.crs.pointToLatLng(toPoint(point), zoom);
  	},

  	// @method layerPointToLatLng(point: Point): LatLng
  	// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
  	// returns the corresponding geographical coordinate (for the current zoom level).
  	layerPointToLatLng: function (point) {
  		var projectedPoint = toPoint(point).add(this.getPixelOrigin());
  		return this.unproject(projectedPoint);
  	},

  	// @method latLngToLayerPoint(latlng: LatLng): Point
  	// Given a geographical coordinate, returns the corresponding pixel coordinate
  	// relative to the [origin pixel](#map-getpixelorigin).
  	latLngToLayerPoint: function (latlng) {
  		var projectedPoint = this.project(toLatLng(latlng))._round();
  		return projectedPoint._subtract(this.getPixelOrigin());
  	},

  	// @method wrapLatLng(latlng: LatLng): LatLng
  	// Returns a `LatLng` where `lat` and `lng` has been wrapped according to the
  	// map's CRS's `wrapLat` and `wrapLng` properties, if they are outside the
  	// CRS's bounds.
  	// By default this means longitude is wrapped around the dateline so its
  	// value is between -180 and +180 degrees.
  	wrapLatLng: function (latlng) {
  		return this.options.crs.wrapLatLng(toLatLng(latlng));
  	},

  	// @method wrapLatLngBounds(bounds: LatLngBounds): LatLngBounds
  	// Returns a `LatLngBounds` with the same size as the given one, ensuring that
  	// its center is within the CRS's bounds.
  	// By default this means the center longitude is wrapped around the dateline so its
  	// value is between -180 and +180 degrees, and the majority of the bounds
  	// overlaps the CRS's bounds.
  	wrapLatLngBounds: function (latlng) {
  		return this.options.crs.wrapLatLngBounds(toLatLngBounds(latlng));
  	},

  	// @method distance(latlng1: LatLng, latlng2: LatLng): Number
  	// Returns the distance between two geographical coordinates according to
  	// the map's CRS. By default this measures distance in meters.
  	distance: function (latlng1, latlng2) {
  		return this.options.crs.distance(toLatLng(latlng1), toLatLng(latlng2));
  	},

  	// @method containerPointToLayerPoint(point: Point): Point
  	// Given a pixel coordinate relative to the map container, returns the corresponding
  	// pixel coordinate relative to the [origin pixel](#map-getpixelorigin).
  	containerPointToLayerPoint: function (point) { // (Point)
  		return toPoint(point).subtract(this._getMapPanePos());
  	},

  	// @method layerPointToContainerPoint(point: Point): Point
  	// Given a pixel coordinate relative to the [origin pixel](#map-getpixelorigin),
  	// returns the corresponding pixel coordinate relative to the map container.
  	layerPointToContainerPoint: function (point) { // (Point)
  		return toPoint(point).add(this._getMapPanePos());
  	},

  	// @method containerPointToLatLng(point: Point): LatLng
  	// Given a pixel coordinate relative to the map container, returns
  	// the corresponding geographical coordinate (for the current zoom level).
  	containerPointToLatLng: function (point) {
  		var layerPoint = this.containerPointToLayerPoint(toPoint(point));
  		return this.layerPointToLatLng(layerPoint);
  	},

  	// @method latLngToContainerPoint(latlng: LatLng): Point
  	// Given a geographical coordinate, returns the corresponding pixel coordinate
  	// relative to the map container.
  	latLngToContainerPoint: function (latlng) {
  		return this.layerPointToContainerPoint(this.latLngToLayerPoint(toLatLng(latlng)));
  	},

  	// @method mouseEventToContainerPoint(ev: MouseEvent): Point
  	// Given a MouseEvent object, returns the pixel coordinate relative to the
  	// map container where the event took place.
  	mouseEventToContainerPoint: function (e) {
  		return getMousePosition(e, this._container);
  	},

  	// @method mouseEventToLayerPoint(ev: MouseEvent): Point
  	// Given a MouseEvent object, returns the pixel coordinate relative to
  	// the [origin pixel](#map-getpixelorigin) where the event took place.
  	mouseEventToLayerPoint: function (e) {
  		return this.containerPointToLayerPoint(this.mouseEventToContainerPoint(e));
  	},

  	// @method mouseEventToLatLng(ev: MouseEvent): LatLng
  	// Given a MouseEvent object, returns geographical coordinate where the
  	// event took place.
  	mouseEventToLatLng: function (e) { // (MouseEvent)
  		return this.layerPointToLatLng(this.mouseEventToLayerPoint(e));
  	},


  	// map initialization methods

  	_initContainer: function (id) {
  		var container = this._container = get(id);

  		if (!container) {
  			throw new Error('Map container not found.');
  		} else if (container._leaflet_id) {
  			throw new Error('Map container is already initialized.');
  		}

  		on(container, 'scroll', this._onScroll, this);
  		this._containerId = stamp(container);
  	},

  	_initLayout: function () {
  		var container = this._container;

  		this._fadeAnimated = this.options.fadeAnimation && Browser.any3d;

  		addClass(container, 'leaflet-container' +
  			(Browser.touch ? ' leaflet-touch' : '') +
  			(Browser.retina ? ' leaflet-retina' : '') +
  			(Browser.ielt9 ? ' leaflet-oldie' : '') +
  			(Browser.safari ? ' leaflet-safari' : '') +
  			(this._fadeAnimated ? ' leaflet-fade-anim' : ''));

  		var position = getStyle(container, 'position');

  		if (position !== 'absolute' && position !== 'relative' && position !== 'fixed' && position !== 'sticky') {
  			container.style.position = 'relative';
  		}

  		this._initPanes();

  		if (this._initControlPos) {
  			this._initControlPos();
  		}
  	},

  	_initPanes: function () {
  		var panes = this._panes = {};
  		this._paneRenderers = {};

  		// @section
  		//
  		// Panes are DOM elements used to control the ordering of layers on the map. You
  		// can access panes with [`map.getPane`](#map-getpane) or
  		// [`map.getPanes`](#map-getpanes) methods. New panes can be created with the
  		// [`map.createPane`](#map-createpane) method.
  		//
  		// Every map has the following default panes that differ only in zIndex.
  		//
  		// @pane mapPane: HTMLElement = 'auto'
  		// Pane that contains all other map panes

  		this._mapPane = this.createPane('mapPane', this._container);
  		setPosition(this._mapPane, new Point(0, 0));

  		// @pane tilePane: HTMLElement = 200
  		// Pane for `GridLayer`s and `TileLayer`s
  		this.createPane('tilePane');
  		// @pane overlayPane: HTMLElement = 400
  		// Pane for vectors (`Path`s, like `Polyline`s and `Polygon`s), `ImageOverlay`s and `VideoOverlay`s
  		this.createPane('overlayPane');
  		// @pane shadowPane: HTMLElement = 500
  		// Pane for overlay shadows (e.g. `Marker` shadows)
  		this.createPane('shadowPane');
  		// @pane markerPane: HTMLElement = 600
  		// Pane for `Icon`s of `Marker`s
  		this.createPane('markerPane');
  		// @pane tooltipPane: HTMLElement = 650
  		// Pane for `Tooltip`s.
  		this.createPane('tooltipPane');
  		// @pane popupPane: HTMLElement = 700
  		// Pane for `Popup`s.
  		this.createPane('popupPane');

  		if (!this.options.markerZoomAnimation) {
  			addClass(panes.markerPane, 'leaflet-zoom-hide');
  			addClass(panes.shadowPane, 'leaflet-zoom-hide');
  		}
  	},


  	// private methods that modify map state

  	// @section Map state change events
  	_resetView: function (center, zoom, noMoveStart) {
  		setPosition(this._mapPane, new Point(0, 0));

  		var loading = !this._loaded;
  		this._loaded = true;
  		zoom = this._limitZoom(zoom);

  		this.fire('viewprereset');

  		var zoomChanged = this._zoom !== zoom;
  		this
  			._moveStart(zoomChanged, noMoveStart)
  			._move(center, zoom)
  			._moveEnd(zoomChanged);

  		// @event viewreset: Event
  		// Fired when the map needs to redraw its content (this usually happens
  		// on map zoom or load). Very useful for creating custom overlays.
  		this.fire('viewreset');

  		// @event load: Event
  		// Fired when the map is initialized (when its center and zoom are set
  		// for the first time).
  		if (loading) {
  			this.fire('load');
  		}
  	},

  	_moveStart: function (zoomChanged, noMoveStart) {
  		// @event zoomstart: Event
  		// Fired when the map zoom is about to change (e.g. before zoom animation).
  		// @event movestart: Event
  		// Fired when the view of the map starts changing (e.g. user starts dragging the map).
  		if (zoomChanged) {
  			this.fire('zoomstart');
  		}
  		if (!noMoveStart) {
  			this.fire('movestart');
  		}
  		return this;
  	},

  	_move: function (center, zoom, data, supressEvent) {
  		if (zoom === undefined) {
  			zoom = this._zoom;
  		}
  		var zoomChanged = this._zoom !== zoom;

  		this._zoom = zoom;
  		this._lastCenter = center;
  		this._pixelOrigin = this._getNewPixelOrigin(center);

  		if (!supressEvent) {
  			// @event zoom: Event
  			// Fired repeatedly during any change in zoom level,
  			// including zoom and fly animations.
  			if (zoomChanged || (data && data.pinch)) {	// Always fire 'zoom' if pinching because #3530
  				this.fire('zoom', data);
  			}

  			// @event move: Event
  			// Fired repeatedly during any movement of the map,
  			// including pan and fly animations.
  			this.fire('move', data);
  		} else if (data && data.pinch) {	// Always fire 'zoom' if pinching because #3530
  			this.fire('zoom', data);
  		}
  		return this;
  	},

  	_moveEnd: function (zoomChanged) {
  		// @event zoomend: Event
  		// Fired when the map zoom changed, after any animations.
  		if (zoomChanged) {
  			this.fire('zoomend');
  		}

  		// @event moveend: Event
  		// Fired when the center of the map stops changing
  		// (e.g. user stopped dragging the map or after non-centered zoom).
  		return this.fire('moveend');
  	},

  	_stop: function () {
  		cancelAnimFrame(this._flyToFrame);
  		if (this._panAnim) {
  			this._panAnim.stop();
  		}
  		return this;
  	},

  	_rawPanBy: function (offset) {
  		setPosition(this._mapPane, this._getMapPanePos().subtract(offset));
  	},

  	_getZoomSpan: function () {
  		return this.getMaxZoom() - this.getMinZoom();
  	},

  	_panInsideMaxBounds: function () {
  		if (!this._enforcingBounds) {
  			this.panInsideBounds(this.options.maxBounds);
  		}
  	},

  	_checkIfLoaded: function () {
  		if (!this._loaded) {
  			throw new Error('Set map center and zoom first.');
  		}
  	},

  	// DOM event handling

  	// @section Interaction events
  	_initEvents: function (remove) {
  		this._targets = {};
  		this._targets[stamp(this._container)] = this;

  		var onOff = remove ? off : on;

  		// @event click: MouseEvent
  		// Fired when the user clicks (or taps) the map.
  		// @event dblclick: MouseEvent
  		// Fired when the user double-clicks (or double-taps) the map.
  		// @event mousedown: MouseEvent
  		// Fired when the user pushes the mouse button on the map.
  		// @event mouseup: MouseEvent
  		// Fired when the user releases the mouse button on the map.
  		// @event mouseover: MouseEvent
  		// Fired when the mouse enters the map.
  		// @event mouseout: MouseEvent
  		// Fired when the mouse leaves the map.
  		// @event mousemove: MouseEvent
  		// Fired while the mouse moves over the map.
  		// @event contextmenu: MouseEvent
  		// Fired when the user pushes the right mouse button on the map, prevents
  		// default browser context menu from showing if there are listeners on
  		// this event. Also fired on mobile when the user holds a single touch
  		// for a second (also called long press).
  		// @event keypress: KeyboardEvent
  		// Fired when the user presses a key from the keyboard that produces a character value while the map is focused.
  		// @event keydown: KeyboardEvent
  		// Fired when the user presses a key from the keyboard while the map is focused. Unlike the `keypress` event,
  		// the `keydown` event is fired for keys that produce a character value and for keys
  		// that do not produce a character value.
  		// @event keyup: KeyboardEvent
  		// Fired when the user releases a key from the keyboard while the map is focused.
  		onOff(this._container, 'click dblclick mousedown mouseup ' +
  			'mouseover mouseout mousemove contextmenu keypress keydown keyup', this._handleDOMEvent, this);

  		if (this.options.trackResize) {
  			onOff(window, 'resize', this._onResize, this);
  		}

  		if (Browser.any3d && this.options.transform3DLimit) {
  			(remove ? this.off : this.on).call(this, 'moveend', this._onMoveEnd);
  		}
  	},

  	_onResize: function () {
  		cancelAnimFrame(this._resizeRequest);
  		this._resizeRequest = requestAnimFrame(
  		        function () { this.invalidateSize({debounceMoveend: true}); }, this);
  	},

  	_onScroll: function () {
  		this._container.scrollTop  = 0;
  		this._container.scrollLeft = 0;
  	},

  	_onMoveEnd: function () {
  		var pos = this._getMapPanePos();
  		if (Math.max(Math.abs(pos.x), Math.abs(pos.y)) >= this.options.transform3DLimit) {
  			// https://bugzilla.mozilla.org/show_bug.cgi?id=1203873 but Webkit also have
  			// a pixel offset on very high values, see: https://jsfiddle.net/dg6r5hhb/
  			this._resetView(this.getCenter(), this.getZoom());
  		}
  	},

  	_findEventTargets: function (e, type) {
  		var targets = [],
  		    target,
  		    isHover = type === 'mouseout' || type === 'mouseover',
  		    src = e.target || e.srcElement,
  		    dragging = false;

  		while (src) {
  			target = this._targets[stamp(src)];
  			if (target && (type === 'click' || type === 'preclick') && this._draggableMoved(target)) {
  				// Prevent firing click after you just dragged an object.
  				dragging = true;
  				break;
  			}
  			if (target && target.listens(type, true)) {
  				if (isHover && !isExternalTarget(src, e)) { break; }
  				targets.push(target);
  				if (isHover) { break; }
  			}
  			if (src === this._container) { break; }
  			src = src.parentNode;
  		}
  		if (!targets.length && !dragging && !isHover && this.listens(type, true)) {
  			targets = [this];
  		}
  		return targets;
  	},

  	_isClickDisabled: function (el) {
  		while (el && el !== this._container) {
  			if (el['_leaflet_disable_click']) { return true; }
  			el = el.parentNode;
  		}
  	},

  	_handleDOMEvent: function (e) {
  		var el = (e.target || e.srcElement);
  		if (!this._loaded || el['_leaflet_disable_events'] || e.type === 'click' && this._isClickDisabled(el)) {
  			return;
  		}

  		var type = e.type;

  		if (type === 'mousedown') {
  			// prevents outline when clicking on keyboard-focusable element
  			preventOutline(el);
  		}

  		this._fireDOMEvent(e, type);
  	},

  	_mouseEvents: ['click', 'dblclick', 'mouseover', 'mouseout', 'contextmenu'],

  	_fireDOMEvent: function (e, type, canvasTargets) {

  		if (e.type === 'click') {
  			// Fire a synthetic 'preclick' event which propagates up (mainly for closing popups).
  			// @event preclick: MouseEvent
  			// Fired before mouse click on the map (sometimes useful when you
  			// want something to happen on click before any existing click
  			// handlers start running).
  			var synth = extend({}, e);
  			synth.type = 'preclick';
  			this._fireDOMEvent(synth, synth.type, canvasTargets);
  		}

  		// Find the layer the event is propagating from and its parents.
  		var targets = this._findEventTargets(e, type);

  		if (canvasTargets) {
  			var filtered = []; // pick only targets with listeners
  			for (var i = 0; i < canvasTargets.length; i++) {
  				if (canvasTargets[i].listens(type, true)) {
  					filtered.push(canvasTargets[i]);
  				}
  			}
  			targets = filtered.concat(targets);
  		}

  		if (!targets.length) { return; }

  		if (type === 'contextmenu') {
  			preventDefault(e);
  		}

  		var target = targets[0];
  		var data = {
  			originalEvent: e
  		};

  		if (e.type !== 'keypress' && e.type !== 'keydown' && e.type !== 'keyup') {
  			var isMarker = target.getLatLng && (!target._radius || target._radius <= 10);
  			data.containerPoint = isMarker ?
  				this.latLngToContainerPoint(target.getLatLng()) : this.mouseEventToContainerPoint(e);
  			data.layerPoint = this.containerPointToLayerPoint(data.containerPoint);
  			data.latlng = isMarker ? target.getLatLng() : this.layerPointToLatLng(data.layerPoint);
  		}

  		for (i = 0; i < targets.length; i++) {
  			targets[i].fire(type, data, true);
  			if (data.originalEvent._stopped ||
  				(targets[i].options.bubblingMouseEvents === false && indexOf(this._mouseEvents, type) !== -1)) { return; }
  		}
  	},

  	_draggableMoved: function (obj) {
  		obj = obj.dragging && obj.dragging.enabled() ? obj : this;
  		return (obj.dragging && obj.dragging.moved()) || (this.boxZoom && this.boxZoom.moved());
  	},

  	_clearHandlers: function () {
  		for (var i = 0, len = this._handlers.length; i < len; i++) {
  			this._handlers[i].disable();
  		}
  	},

  	// @section Other Methods

  	// @method whenReady(fn: Function, context?: Object): this
  	// Runs the given function `fn` when the map gets initialized with
  	// a view (center and zoom) and at least one layer, or immediately
  	// if it's already initialized, optionally passing a function context.
  	whenReady: function (callback, context) {
  		if (this._loaded) {
  			callback.call(context || this, {target: this});
  		} else {
  			this.on('load', callback, context);
  		}
  		return this;
  	},


  	// private methods for getting map state

  	_getMapPanePos: function () {
  		return getPosition(this._mapPane) || new Point(0, 0);
  	},

  	_moved: function () {
  		var pos = this._getMapPanePos();
  		return pos && !pos.equals([0, 0]);
  	},

  	_getTopLeftPoint: function (center, zoom) {
  		var pixelOrigin = center && zoom !== undefined ?
  			this._getNewPixelOrigin(center, zoom) :
  			this.getPixelOrigin();
  		return pixelOrigin.subtract(this._getMapPanePos());
  	},

  	_getNewPixelOrigin: function (center, zoom) {
  		var viewHalf = this.getSize()._divideBy(2);
  		return this.project(center, zoom)._subtract(viewHalf)._add(this._getMapPanePos())._round();
  	},

  	_latLngToNewLayerPoint: function (latlng, zoom, center) {
  		var topLeft = this._getNewPixelOrigin(center, zoom);
  		return this.project(latlng, zoom)._subtract(topLeft);
  	},

  	_latLngBoundsToNewLayerBounds: function (latLngBounds, zoom, center) {
  		var topLeft = this._getNewPixelOrigin(center, zoom);
  		return toBounds([
  			this.project(latLngBounds.getSouthWest(), zoom)._subtract(topLeft),
  			this.project(latLngBounds.getNorthWest(), zoom)._subtract(topLeft),
  			this.project(latLngBounds.getSouthEast(), zoom)._subtract(topLeft),
  			this.project(latLngBounds.getNorthEast(), zoom)._subtract(topLeft)
  		]);
  	},

  	// layer point of the current center
  	_getCenterLayerPoint: function () {
  		return this.containerPointToLayerPoint(this.getSize()._divideBy(2));
  	},

  	// offset of the specified place to the current center in pixels
  	_getCenterOffset: function (latlng) {
  		return this.latLngToLayerPoint(latlng).subtract(this._getCenterLayerPoint());
  	},

  	// adjust center for view to get inside bounds
  	_limitCenter: function (center, zoom, bounds) {

  		if (!bounds) { return center; }

  		var centerPoint = this.project(center, zoom),
  		    viewHalf = this.getSize().divideBy(2),
  		    viewBounds = new Bounds(centerPoint.subtract(viewHalf), centerPoint.add(viewHalf)),
  		    offset = this._getBoundsOffset(viewBounds, bounds, zoom);

  		// If offset is less than a pixel, ignore.
  		// This prevents unstable projections from getting into
  		// an infinite loop of tiny offsets.
  		if (Math.abs(offset.x) <= 1 && Math.abs(offset.y) <= 1) {
  			return center;
  		}

  		return this.unproject(centerPoint.add(offset), zoom);
  	},

  	// adjust offset for view to get inside bounds
  	_limitOffset: function (offset, bounds) {
  		if (!bounds) { return offset; }

  		var viewBounds = this.getPixelBounds(),
  		    newBounds = new Bounds(viewBounds.min.add(offset), viewBounds.max.add(offset));

  		return offset.add(this._getBoundsOffset(newBounds, bounds));
  	},

  	// returns offset needed for pxBounds to get inside maxBounds at a specified zoom
  	_getBoundsOffset: function (pxBounds, maxBounds, zoom) {
  		var projectedMaxBounds = toBounds(
  		        this.project(maxBounds.getNorthEast(), zoom),
  		        this.project(maxBounds.getSouthWest(), zoom)
  		    ),
  		    minOffset = projectedMaxBounds.min.subtract(pxBounds.min),
  		    maxOffset = projectedMaxBounds.max.subtract(pxBounds.max),

  		    dx = this._rebound(minOffset.x, -maxOffset.x),
  		    dy = this._rebound(minOffset.y, -maxOffset.y);

  		return new Point(dx, dy);
  	},

  	_rebound: function (left, right) {
  		return left + right > 0 ?
  			Math.round(left - right) / 2 :
  			Math.max(0, Math.ceil(left)) - Math.max(0, Math.floor(right));
  	},

  	_limitZoom: function (zoom) {
  		var min = this.getMinZoom(),
  		    max = this.getMaxZoom(),
  		    snap = Browser.any3d ? this.options.zoomSnap : 1;
  		if (snap) {
  			zoom = Math.round(zoom / snap) * snap;
  		}
  		return Math.max(min, Math.min(max, zoom));
  	},

  	_onPanTransitionStep: function () {
  		this.fire('move');
  	},

  	_onPanTransitionEnd: function () {
  		removeClass(this._mapPane, 'leaflet-pan-anim');
  		this.fire('moveend');
  	},

  	_tryAnimatedPan: function (center, options) {
  		// difference between the new and current centers in pixels
  		var offset = this._getCenterOffset(center)._trunc();

  		// don't animate too far unless animate: true specified in options
  		if ((options && options.animate) !== true && !this.getSize().contains(offset)) { return false; }

  		this.panBy(offset, options);

  		return true;
  	},

  	_createAnimProxy: function () {

  		var proxy = this._proxy = create$1('div', 'leaflet-proxy leaflet-zoom-animated');
  		this._panes.mapPane.appendChild(proxy);

  		this.on('zoomanim', function (e) {
  			var prop = TRANSFORM,
  			    transform = this._proxy.style[prop];

  			setTransform(this._proxy, this.project(e.center, e.zoom), this.getZoomScale(e.zoom, 1));

  			// workaround for case when transform is the same and so transitionend event is not fired
  			if (transform === this._proxy.style[prop] && this._animatingZoom) {
  				this._onZoomTransitionEnd();
  			}
  		}, this);

  		this.on('load moveend', this._animMoveEnd, this);

  		this._on('unload', this._destroyAnimProxy, this);
  	},

  	_destroyAnimProxy: function () {
  		remove(this._proxy);
  		this.off('load moveend', this._animMoveEnd, this);
  		delete this._proxy;
  	},

  	_animMoveEnd: function () {
  		var c = this.getCenter(),
  		    z = this.getZoom();
  		setTransform(this._proxy, this.project(c, z), this.getZoomScale(z, 1));
  	},

  	_catchTransitionEnd: function (e) {
  		if (this._animatingZoom && e.propertyName.indexOf('transform') >= 0) {
  			this._onZoomTransitionEnd();
  		}
  	},

  	_nothingToAnimate: function () {
  		return !this._container.getElementsByClassName('leaflet-zoom-animated').length;
  	},

  	_tryAnimatedZoom: function (center, zoom, options) {

  		if (this._animatingZoom) { return true; }

  		options = options || {};

  		// don't animate if disabled, not supported or zoom difference is too large
  		if (!this._zoomAnimated || options.animate === false || this._nothingToAnimate() ||
  		        Math.abs(zoom - this._zoom) > this.options.zoomAnimationThreshold) { return false; }

  		// offset is the pixel coords of the zoom origin relative to the current center
  		var scale = this.getZoomScale(zoom),
  		    offset = this._getCenterOffset(center)._divideBy(1 - 1 / scale);

  		// don't animate if the zoom origin isn't within one screen from the current center, unless forced
  		if (options.animate !== true && !this.getSize().contains(offset)) { return false; }

  		requestAnimFrame(function () {
  			this
  			    ._moveStart(true, options.noMoveStart || false)
  			    ._animateZoom(center, zoom, true);
  		}, this);

  		return true;
  	},

  	_animateZoom: function (center, zoom, startAnim, noUpdate) {
  		if (!this._mapPane) { return; }

  		if (startAnim) {
  			this._animatingZoom = true;

  			// remember what center/zoom to set after animation
  			this._animateToCenter = center;
  			this._animateToZoom = zoom;

  			addClass(this._mapPane, 'leaflet-zoom-anim');
  		}

  		// @section Other Events
  		// @event zoomanim: ZoomAnimEvent
  		// Fired at least once per zoom animation. For continuous zoom, like pinch zooming, fired once per frame during zoom.
  		this.fire('zoomanim', {
  			center: center,
  			zoom: zoom,
  			noUpdate: noUpdate
  		});

  		if (!this._tempFireZoomEvent) {
  			this._tempFireZoomEvent = this._zoom !== this._animateToZoom;
  		}

  		this._move(this._animateToCenter, this._animateToZoom, undefined, true);

  		// Work around webkit not firing 'transitionend', see https://github.com/Leaflet/Leaflet/issues/3689, 2693
  		setTimeout(bind(this._onZoomTransitionEnd, this), 250);
  	},

  	_onZoomTransitionEnd: function () {
  		if (!this._animatingZoom) { return; }

  		if (this._mapPane) {
  			removeClass(this._mapPane, 'leaflet-zoom-anim');
  		}

  		this._animatingZoom = false;

  		this._move(this._animateToCenter, this._animateToZoom, undefined, true);

  		if (this._tempFireZoomEvent) {
  			this.fire('zoom');
  		}
  		delete this._tempFireZoomEvent;

  		this.fire('move');

  		this._moveEnd(true);
  	}
  });

  // @section

  // @factory L.map(id: String, options?: Map options)
  // Instantiates a map object given the DOM ID of a `<div>` element
  // and optionally an object literal with `Map options`.
  //
  // @alternative
  // @factory L.map(el: HTMLElement, options?: Map options)
  // Instantiates a map object given an instance of a `<div>` HTML element
  // and optionally an object literal with `Map options`.
  function createMap(id, options) {
  	return new Map(id, options);
  }

  /*
   * @class Control
   * @aka L.Control
   * @inherits Class
   *
   * L.Control is a base class for implementing map controls. Handles positioning.
   * All other controls extend from this class.
   */

  var Control = Class.extend({
  	// @section
  	// @aka Control Options
  	options: {
  		// @option position: String = 'topright'
  		// The position of the control (one of the map corners). Possible values are `'topleft'`,
  		// `'topright'`, `'bottomleft'` or `'bottomright'`
  		position: 'topright'
  	},

  	initialize: function (options) {
  		setOptions(this, options);
  	},

  	/* @section
  	 * Classes extending L.Control will inherit the following methods:
  	 *
  	 * @method getPosition: string
  	 * Returns the position of the control.
  	 */
  	getPosition: function () {
  		return this.options.position;
  	},

  	// @method setPosition(position: string): this
  	// Sets the position of the control.
  	setPosition: function (position) {
  		var map = this._map;

  		if (map) {
  			map.removeControl(this);
  		}

  		this.options.position = position;

  		if (map) {
  			map.addControl(this);
  		}

  		return this;
  	},

  	// @method getContainer: HTMLElement
  	// Returns the HTMLElement that contains the control.
  	getContainer: function () {
  		return this._container;
  	},

  	// @method addTo(map: Map): this
  	// Adds the control to the given map.
  	addTo: function (map) {
  		this.remove();
  		this._map = map;

  		var container = this._container = this.onAdd(map),
  		    pos = this.getPosition(),
  		    corner = map._controlCorners[pos];

  		addClass(container, 'leaflet-control');

  		if (pos.indexOf('bottom') !== -1) {
  			corner.insertBefore(container, corner.firstChild);
  		} else {
  			corner.appendChild(container);
  		}

  		this._map.on('unload', this.remove, this);

  		return this;
  	},

  	// @method remove: this
  	// Removes the control from the map it is currently active on.
  	remove: function () {
  		if (!this._map) {
  			return this;
  		}

  		remove(this._container);

  		if (this.onRemove) {
  			this.onRemove(this._map);
  		}

  		this._map.off('unload', this.remove, this);
  		this._map = null;

  		return this;
  	},

  	_refocusOnMap: function (e) {
  		// if map exists and event is not a keyboard event
  		if (this._map && e && e.screenX > 0 && e.screenY > 0) {
  			this._map.getContainer().focus();
  		}
  	}
  });

  var control = function (options) {
  	return new Control(options);
  };

  /* @section Extension methods
   * @uninheritable
   *
   * Every control should extend from `L.Control` and (re-)implement the following methods.
   *
   * @method onAdd(map: Map): HTMLElement
   * Should return the container DOM element for the control and add listeners on relevant map events. Called on [`control.addTo(map)`](#control-addTo).
   *
   * @method onRemove(map: Map)
   * Optional method. Should contain all clean up code that removes the listeners previously added in [`onAdd`](#control-onadd). Called on [`control.remove()`](#control-remove).
   */

  /* @namespace Map
   * @section Methods for Layers and Controls
   */
  Map.include({
  	// @method addControl(control: Control): this
  	// Adds the given control to the map
  	addControl: function (control) {
  		control.addTo(this);
  		return this;
  	},

  	// @method removeControl(control: Control): this
  	// Removes the given control from the map
  	removeControl: function (control) {
  		control.remove();
  		return this;
  	},

  	_initControlPos: function () {
  		var corners = this._controlCorners = {},
  		    l = 'leaflet-',
  		    container = this._controlContainer =
  		            create$1('div', l + 'control-container', this._container);

  		function createCorner(vSide, hSide) {
  			var className = l + vSide + ' ' + l + hSide;

  			corners[vSide + hSide] = create$1('div', className, container);
  		}

  		createCorner('top', 'left');
  		createCorner('top', 'right');
  		createCorner('bottom', 'left');
  		createCorner('bottom', 'right');
  	},

  	_clearControlPos: function () {
  		for (var i in this._controlCorners) {
  			remove(this._controlCorners[i]);
  		}
  		remove(this._controlContainer);
  		delete this._controlCorners;
  		delete this._controlContainer;
  	}
  });

  /*
   * @class Control.Layers
   * @aka L.Control.Layers
   * @inherits Control
   *
   * The layers control gives users the ability to switch between different base layers and switch overlays on/off (check out the [detailed example](https://leafletjs.com/examples/layers-control/)). Extends `Control`.
   *
   * @example
   *
   * ```js
   * var baseLayers = {
   * 	"Mapbox": mapbox,
   * 	"OpenStreetMap": osm
   * };
   *
   * var overlays = {
   * 	"Marker": marker,
   * 	"Roads": roadsLayer
   * };
   *
   * L.control.layers(baseLayers, overlays).addTo(map);
   * ```
   *
   * The `baseLayers` and `overlays` parameters are object literals with layer names as keys and `Layer` objects as values:
   *
   * ```js
   * {
   *     "<someName1>": layer1,
   *     "<someName2>": layer2
   * }
   * ```
   *
   * The layer names can contain HTML, which allows you to add additional styling to the items:
   *
   * ```js
   * {"<img src='my-layer-icon' /> <span class='my-layer-item'>My Layer</span>": myLayer}
   * ```
   */

  var Layers = Control.extend({
  	// @section
  	// @aka Control.Layers options
  	options: {
  		// @option collapsed: Boolean = true
  		// If `true`, the control will be collapsed into an icon and expanded on mouse hover, touch, or keyboard activation.
  		collapsed: true,
  		position: 'topright',

  		// @option autoZIndex: Boolean = true
  		// If `true`, the control will assign zIndexes in increasing order to all of its layers so that the order is preserved when switching them on/off.
  		autoZIndex: true,

  		// @option hideSingleBase: Boolean = false
  		// If `true`, the base layers in the control will be hidden when there is only one.
  		hideSingleBase: false,

  		// @option sortLayers: Boolean = false
  		// Whether to sort the layers. When `false`, layers will keep the order
  		// in which they were added to the control.
  		sortLayers: false,

  		// @option sortFunction: Function = *
  		// A [compare function](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
  		// that will be used for sorting the layers, when `sortLayers` is `true`.
  		// The function receives both the `L.Layer` instances and their names, as in
  		// `sortFunction(layerA, layerB, nameA, nameB)`.
  		// By default, it sorts layers alphabetically by their name.
  		sortFunction: function (layerA, layerB, nameA, nameB) {
  			return nameA < nameB ? -1 : (nameB < nameA ? 1 : 0);
  		}
  	},

  	initialize: function (baseLayers, overlays, options) {
  		setOptions(this, options);

  		this._layerControlInputs = [];
  		this._layers = [];
  		this._lastZIndex = 0;
  		this._handlingClick = false;
  		this._preventClick = false;

  		for (var i in baseLayers) {
  			this._addLayer(baseLayers[i], i);
  		}

  		for (i in overlays) {
  			this._addLayer(overlays[i], i, true);
  		}
  	},

  	onAdd: function (map) {
  		this._initLayout();
  		this._update();

  		this._map = map;
  		map.on('zoomend', this._checkDisabledLayers, this);

  		for (var i = 0; i < this._layers.length; i++) {
  			this._layers[i].layer.on('add remove', this._onLayerChange, this);
  		}

  		return this._container;
  	},

  	addTo: function (map) {
  		Control.prototype.addTo.call(this, map);
  		// Trigger expand after Layers Control has been inserted into DOM so that is now has an actual height.
  		return this._expandIfNotCollapsed();
  	},

  	onRemove: function () {
  		this._map.off('zoomend', this._checkDisabledLayers, this);

  		for (var i = 0; i < this._layers.length; i++) {
  			this._layers[i].layer.off('add remove', this._onLayerChange, this);
  		}
  	},

  	// @method addBaseLayer(layer: Layer, name: String): this
  	// Adds a base layer (radio button entry) with the given name to the control.
  	addBaseLayer: function (layer, name) {
  		this._addLayer(layer, name);
  		return (this._map) ? this._update() : this;
  	},

  	// @method addOverlay(layer: Layer, name: String): this
  	// Adds an overlay (checkbox entry) with the given name to the control.
  	addOverlay: function (layer, name) {
  		this._addLayer(layer, name, true);
  		return (this._map) ? this._update() : this;
  	},

  	// @method removeLayer(layer: Layer): this
  	// Remove the given layer from the control.
  	removeLayer: function (layer) {
  		layer.off('add remove', this._onLayerChange, this);

  		var obj = this._getLayer(stamp(layer));
  		if (obj) {
  			this._layers.splice(this._layers.indexOf(obj), 1);
  		}
  		return (this._map) ? this._update() : this;
  	},

  	// @method expand(): this
  	// Expand the control container if collapsed.
  	expand: function () {
  		addClass(this._container, 'leaflet-control-layers-expanded');
  		this._section.style.height = null;
  		var acceptableHeight = this._map.getSize().y - (this._container.offsetTop + 50);
  		if (acceptableHeight < this._section.clientHeight) {
  			addClass(this._section, 'leaflet-control-layers-scrollbar');
  			this._section.style.height = acceptableHeight + 'px';
  		} else {
  			removeClass(this._section, 'leaflet-control-layers-scrollbar');
  		}
  		this._checkDisabledLayers();
  		return this;
  	},

  	// @method collapse(): this
  	// Collapse the control container if expanded.
  	collapse: function () {
  		removeClass(this._container, 'leaflet-control-layers-expanded');
  		return this;
  	},

  	_initLayout: function () {
  		var className = 'leaflet-control-layers',
  		    container = this._container = create$1('div', className),
  		    collapsed = this.options.collapsed;

  		// makes this work on IE touch devices by stopping it from firing a mouseout event when the touch is released
  		container.setAttribute('aria-haspopup', true);

  		disableClickPropagation(container);
  		disableScrollPropagation(container);

  		var section = this._section = create$1('section', className + '-list');

  		if (collapsed) {
  			this._map.on('click', this.collapse, this);

  			on(container, {
  				mouseenter: this._expandSafely,
  				mouseleave: this.collapse
  			}, this);
  		}

  		var link = this._layersLink = create$1('a', className + '-toggle', container);
  		link.href = '#';
  		link.title = 'Layers';
  		link.setAttribute('role', 'button');

  		on(link, {
  			keydown: function (e) {
  				if (e.keyCode === 13) {
  					this._expandSafely();
  				}
  			},
  			// Certain screen readers intercept the key event and instead send a click event
  			click: function (e) {
  				preventDefault(e);
  				this._expandSafely();
  			}
  		}, this);

  		if (!collapsed) {
  			this.expand();
  		}

  		this._baseLayersList = create$1('div', className + '-base', section);
  		this._separator = create$1('div', className + '-separator', section);
  		this._overlaysList = create$1('div', className + '-overlays', section);

  		container.appendChild(section);
  	},

  	_getLayer: function (id) {
  		for (var i = 0; i < this._layers.length; i++) {

  			if (this._layers[i] && stamp(this._layers[i].layer) === id) {
  				return this._layers[i];
  			}
  		}
  	},

  	_addLayer: function (layer, name, overlay) {
  		if (this._map) {
  			layer.on('add remove', this._onLayerChange, this);
  		}

  		this._layers.push({
  			layer: layer,
  			name: name,
  			overlay: overlay
  		});

  		if (this.options.sortLayers) {
  			this._layers.sort(bind(function (a, b) {
  				return this.options.sortFunction(a.layer, b.layer, a.name, b.name);
  			}, this));
  		}

  		if (this.options.autoZIndex && layer.setZIndex) {
  			this._lastZIndex++;
  			layer.setZIndex(this._lastZIndex);
  		}

  		this._expandIfNotCollapsed();
  	},

  	_update: function () {
  		if (!this._container) { return this; }

  		empty(this._baseLayersList);
  		empty(this._overlaysList);

  		this._layerControlInputs = [];
  		var baseLayersPresent, overlaysPresent, i, obj, baseLayersCount = 0;

  		for (i = 0; i < this._layers.length; i++) {
  			obj = this._layers[i];
  			this._addItem(obj);
  			overlaysPresent = overlaysPresent || obj.overlay;
  			baseLayersPresent = baseLayersPresent || !obj.overlay;
  			baseLayersCount += !obj.overlay ? 1 : 0;
  		}

  		// Hide base layers section if there's only one layer.
  		if (this.options.hideSingleBase) {
  			baseLayersPresent = baseLayersPresent && baseLayersCount > 1;
  			this._baseLayersList.style.display = baseLayersPresent ? '' : 'none';
  		}

  		this._separator.style.display = overlaysPresent && baseLayersPresent ? '' : 'none';

  		return this;
  	},

  	_onLayerChange: function (e) {
  		if (!this._handlingClick) {
  			this._update();
  		}

  		var obj = this._getLayer(stamp(e.target));

  		// @namespace Map
  		// @section Layer events
  		// @event baselayerchange: LayersControlEvent
  		// Fired when the base layer is changed through the [layers control](#control-layers).
  		// @event overlayadd: LayersControlEvent
  		// Fired when an overlay is selected through the [layers control](#control-layers).
  		// @event overlayremove: LayersControlEvent
  		// Fired when an overlay is deselected through the [layers control](#control-layers).
  		// @namespace Control.Layers
  		var type = obj.overlay ?
  			(e.type === 'add' ? 'overlayadd' : 'overlayremove') :
  			(e.type === 'add' ? 'baselayerchange' : null);

  		if (type) {
  			this._map.fire(type, obj);
  		}
  	},

  	// IE7 bugs out if you create a radio dynamically, so you have to do it this hacky way (see https://stackoverflow.com/a/119079)
  	_createRadioElement: function (name, checked) {

  		var radioHtml = '<input type="radio" class="leaflet-control-layers-selector" name="' +
  				name + '"' + (checked ? ' checked="checked"' : '') + '/>';

  		var radioFragment = document.createElement('div');
  		radioFragment.innerHTML = radioHtml;

  		return radioFragment.firstChild;
  	},

  	_addItem: function (obj) {
  		var label = document.createElement('label'),
  		    checked = this._map.hasLayer(obj.layer),
  		    input;

  		if (obj.overlay) {
  			input = document.createElement('input');
  			input.type = 'checkbox';
  			input.className = 'leaflet-control-layers-selector';
  			input.defaultChecked = checked;
  		} else {
  			input = this._createRadioElement('leaflet-base-layers_' + stamp(this), checked);
  		}

  		this._layerControlInputs.push(input);
  		input.layerId = stamp(obj.layer);

  		on(input, 'click', this._onInputClick, this);

  		var name = document.createElement('span');
  		name.innerHTML = ' ' + obj.name;

  		// Helps from preventing layer control flicker when checkboxes are disabled
  		// https://github.com/Leaflet/Leaflet/issues/2771
  		var holder = document.createElement('span');

  		label.appendChild(holder);
  		holder.appendChild(input);
  		holder.appendChild(name);

  		var container = obj.overlay ? this._overlaysList : this._baseLayersList;
  		container.appendChild(label);

  		this._checkDisabledLayers();
  		return label;
  	},

  	_onInputClick: function () {
  		// expanding the control on mobile with a click can cause adding a layer - we don't want this
  		if (this._preventClick) {
  			return;
  		}

  		var inputs = this._layerControlInputs,
  		    input, layer;
  		var addedLayers = [],
  		    removedLayers = [];

  		this._handlingClick = true;

  		for (var i = inputs.length - 1; i >= 0; i--) {
  			input = inputs[i];
  			layer = this._getLayer(input.layerId).layer;

  			if (input.checked) {
  				addedLayers.push(layer);
  			} else if (!input.checked) {
  				removedLayers.push(layer);
  			}
  		}

  		// Bugfix issue 2318: Should remove all old layers before readding new ones
  		for (i = 0; i < removedLayers.length; i++) {
  			if (this._map.hasLayer(removedLayers[i])) {
  				this._map.removeLayer(removedLayers[i]);
  			}
  		}
  		for (i = 0; i < addedLayers.length; i++) {
  			if (!this._map.hasLayer(addedLayers[i])) {
  				this._map.addLayer(addedLayers[i]);
  			}
  		}

  		this._handlingClick = false;

  		this._refocusOnMap();
  	},

  	_checkDisabledLayers: function () {
  		var inputs = this._layerControlInputs,
  		    input,
  		    layer,
  		    zoom = this._map.getZoom();

  		for (var i = inputs.length - 1; i >= 0; i--) {
  			input = inputs[i];
  			layer = this._getLayer(input.layerId).layer;
  			input.disabled = (layer.options.minZoom !== undefined && zoom < layer.options.minZoom) ||
  			                 (layer.options.maxZoom !== undefined && zoom > layer.options.maxZoom);

  		}
  	},

  	_expandIfNotCollapsed: function () {
  		if (this._map && !this.options.collapsed) {
  			this.expand();
  		}
  		return this;
  	},

  	_expandSafely: function () {
  		var section = this._section;
  		this._preventClick = true;
  		on(section, 'click', preventDefault);
  		this.expand();
  		var that = this;
  		setTimeout(function () {
  			off(section, 'click', preventDefault);
  			that._preventClick = false;
  		});
  	}

  });


  // @factory L.control.layers(baselayers?: Object, overlays?: Object, options?: Control.Layers options)
  // Creates a layers control with the given layers. Base layers will be switched with radio buttons, while overlays will be switched with checkboxes. Note that all base layers should be passed in the base layers object, but only one should be added to the map during map instantiation.
  var layers = function (baseLayers, overlays, options) {
  	return new Layers(baseLayers, overlays, options);
  };

  /*
   * @class Control.Zoom
   * @aka L.Control.Zoom
   * @inherits Control
   *
   * A basic zoom control with two buttons (zoom in and zoom out). It is put on the map by default unless you set its [`zoomControl` option](#map-zoomcontrol) to `false`. Extends `Control`.
   */

  var Zoom = Control.extend({
  	// @section
  	// @aka Control.Zoom options
  	options: {
  		position: 'topleft',

  		// @option zoomInText: String = '<span aria-hidden="true">+</span>'
  		// The text set on the 'zoom in' button.
  		zoomInText: '<span aria-hidden="true">+</span>',

  		// @option zoomInTitle: String = 'Zoom in'
  		// The title set on the 'zoom in' button.
  		zoomInTitle: 'Zoom in',

  		// @option zoomOutText: String = '<span aria-hidden="true">&#x2212;</span>'
  		// The text set on the 'zoom out' button.
  		zoomOutText: '<span aria-hidden="true">&#x2212;</span>',

  		// @option zoomOutTitle: String = 'Zoom out'
  		// The title set on the 'zoom out' button.
  		zoomOutTitle: 'Zoom out'
  	},

  	onAdd: function (map) {
  		var zoomName = 'leaflet-control-zoom',
  		    container = create$1('div', zoomName + ' leaflet-bar'),
  		    options = this.options;

  		this._zoomInButton  = this._createButton(options.zoomInText, options.zoomInTitle,
  		        zoomName + '-in',  container, this._zoomIn);
  		this._zoomOutButton = this._createButton(options.zoomOutText, options.zoomOutTitle,
  		        zoomName + '-out', container, this._zoomOut);

  		this._updateDisabled();
  		map.on('zoomend zoomlevelschange', this._updateDisabled, this);

  		return container;
  	},

  	onRemove: function (map) {
  		map.off('zoomend zoomlevelschange', this._updateDisabled, this);
  	},

  	disable: function () {
  		this._disabled = true;
  		this._updateDisabled();
  		return this;
  	},

  	enable: function () {
  		this._disabled = false;
  		this._updateDisabled();
  		return this;
  	},

  	_zoomIn: function (e) {
  		if (!this._disabled && this._map._zoom < this._map.getMaxZoom()) {
  			this._map.zoomIn(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
  		}
  	},

  	_zoomOut: function (e) {
  		if (!this._disabled && this._map._zoom > this._map.getMinZoom()) {
  			this._map.zoomOut(this._map.options.zoomDelta * (e.shiftKey ? 3 : 1));
  		}
  	},

  	_createButton: function (html, title, className, container, fn) {
  		var link = create$1('a', className, container);
  		link.innerHTML = html;
  		link.href = '#';
  		link.title = title;

  		/*
  		 * Will force screen readers like VoiceOver to read this as "Zoom in - button"
  		 */
  		link.setAttribute('role', 'button');
  		link.setAttribute('aria-label', title);

  		disableClickPropagation(link);
  		on(link, 'click', stop);
  		on(link, 'click', fn, this);
  		on(link, 'click', this._refocusOnMap, this);

  		return link;
  	},

  	_updateDisabled: function () {
  		var map = this._map,
  		    className = 'leaflet-disabled';

  		removeClass(this._zoomInButton, className);
  		removeClass(this._zoomOutButton, className);
  		this._zoomInButton.setAttribute('aria-disabled', 'false');
  		this._zoomOutButton.setAttribute('aria-disabled', 'false');

  		if (this._disabled || map._zoom === map.getMinZoom()) {
  			addClass(this._zoomOutButton, className);
  			this._zoomOutButton.setAttribute('aria-disabled', 'true');
  		}
  		if (this._disabled || map._zoom === map.getMaxZoom()) {
  			addClass(this._zoomInButton, className);
  			this._zoomInButton.setAttribute('aria-disabled', 'true');
  		}
  	}
  });

  // @namespace Map
  // @section Control options
  // @option zoomControl: Boolean = true
  // Whether a [zoom control](#control-zoom) is added to the map by default.
  Map.mergeOptions({
  	zoomControl: true
  });

  Map.addInitHook(function () {
  	if (this.options.zoomControl) {
  		// @section Controls
  		// @property zoomControl: Control.Zoom
  		// The default zoom control (only available if the
  		// [`zoomControl` option](#map-zoomcontrol) was `true` when creating the map).
  		this.zoomControl = new Zoom();
  		this.addControl(this.zoomControl);
  	}
  });

  // @namespace Control.Zoom
  // @factory L.control.zoom(options: Control.Zoom options)
  // Creates a zoom control
  var zoom = function (options) {
  	return new Zoom(options);
  };

  /*
   * @class Control.Scale
   * @aka L.Control.Scale
   * @inherits Control
   *
   * A simple scale control that shows the scale of the current center of screen in metric (m/km) and imperial (mi/ft) systems. Extends `Control`.
   *
   * @example
   *
   * ```js
   * L.control.scale().addTo(map);
   * ```
   */

  var Scale = Control.extend({
  	// @section
  	// @aka Control.Scale options
  	options: {
  		position: 'bottomleft',

  		// @option maxWidth: Number = 100
  		// Maximum width of the control in pixels. The width is set dynamically to show round values (e.g. 100, 200, 500).
  		maxWidth: 100,

  		// @option metric: Boolean = True
  		// Whether to show the metric scale line (m/km).
  		metric: true,

  		// @option imperial: Boolean = True
  		// Whether to show the imperial scale line (mi/ft).
  		imperial: true

  		// @option updateWhenIdle: Boolean = false
  		// If `true`, the control is updated on [`moveend`](#map-moveend), otherwise it's always up-to-date (updated on [`move`](#map-move)).
  	},

  	onAdd: function (map) {
  		var className = 'leaflet-control-scale',
  		    container = create$1('div', className),
  		    options = this.options;

  		this._addScales(options, className + '-line', container);

  		map.on(options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
  		map.whenReady(this._update, this);

  		return container;
  	},

  	onRemove: function (map) {
  		map.off(this.options.updateWhenIdle ? 'moveend' : 'move', this._update, this);
  	},

  	_addScales: function (options, className, container) {
  		if (options.metric) {
  			this._mScale = create$1('div', className, container);
  		}
  		if (options.imperial) {
  			this._iScale = create$1('div', className, container);
  		}
  	},

  	_update: function () {
  		var map = this._map,
  		    y = map.getSize().y / 2;

  		var maxMeters = map.distance(
  			map.containerPointToLatLng([0, y]),
  			map.containerPointToLatLng([this.options.maxWidth, y]));

  		this._updateScales(maxMeters);
  	},

  	_updateScales: function (maxMeters) {
  		if (this.options.metric && maxMeters) {
  			this._updateMetric(maxMeters);
  		}
  		if (this.options.imperial && maxMeters) {
  			this._updateImperial(maxMeters);
  		}
  	},

  	_updateMetric: function (maxMeters) {
  		var meters = this._getRoundNum(maxMeters),
  		    label = meters < 1000 ? meters + ' m' : (meters / 1000) + ' km';

  		this._updateScale(this._mScale, label, meters / maxMeters);
  	},

  	_updateImperial: function (maxMeters) {
  		var maxFeet = maxMeters * 3.2808399,
  		    maxMiles, miles, feet;

  		if (maxFeet > 5280) {
  			maxMiles = maxFeet / 5280;
  			miles = this._getRoundNum(maxMiles);
  			this._updateScale(this._iScale, miles + ' mi', miles / maxMiles);

  		} else {
  			feet = this._getRoundNum(maxFeet);
  			this._updateScale(this._iScale, feet + ' ft', feet / maxFeet);
  		}
  	},

  	_updateScale: function (scale, text, ratio) {
  		scale.style.width = Math.round(this.options.maxWidth * ratio) + 'px';
  		scale.innerHTML = text;
  	},

  	_getRoundNum: function (num) {
  		var pow10 = Math.pow(10, (Math.floor(num) + '').length - 1),
  		    d = num / pow10;

  		d = d >= 10 ? 10 :
  		    d >= 5 ? 5 :
  		    d >= 3 ? 3 :
  		    d >= 2 ? 2 : 1;

  		return pow10 * d;
  	}
  });


  // @factory L.control.scale(options?: Control.Scale options)
  // Creates an scale control with the given options.
  var scale = function (options) {
  	return new Scale(options);
  };

  var ukrainianFlag = '<svg aria-hidden="true" xmlns="http://www.w3.org/2000/svg" width="12" height="8" viewBox="0 0 12 8" class="leaflet-attribution-flag"><path fill="#4C7BE1" d="M0 0h12v4H0z"/><path fill="#FFD500" d="M0 4h12v3H0z"/><path fill="#E0BC00" d="M0 7h12v1H0z"/></svg>';


  /*
   * @class Control.Attribution
   * @aka L.Control.Attribution
   * @inherits Control
   *
   * The attribution control allows you to display attribution data in a small text box on a map. It is put on the map by default unless you set its [`attributionControl` option](#map-attributioncontrol) to `false`, and it fetches attribution texts from layers with the [`getAttribution` method](#layer-getattribution) automatically. Extends Control.
   */

  var Attribution = Control.extend({
  	// @section
  	// @aka Control.Attribution options
  	options: {
  		position: 'bottomright',

  		// @option prefix: String|false = 'Leaflet'
  		// The HTML text shown before the attributions. Pass `false` to disable.
  		prefix: '<a href="https://leafletjs.com" title="A JavaScript library for interactive maps">' + (Browser.inlineSvg ? ukrainianFlag + ' ' : '') + 'Leaflet</a>'
  	},

  	initialize: function (options) {
  		setOptions(this, options);

  		this._attributions = {};
  	},

  	onAdd: function (map) {
  		map.attributionControl = this;
  		this._container = create$1('div', 'leaflet-control-attribution');
  		disableClickPropagation(this._container);

  		// TODO ugly, refactor
  		for (var i in map._layers) {
  			if (map._layers[i].getAttribution) {
  				this.addAttribution(map._layers[i].getAttribution());
  			}
  		}

  		this._update();

  		map.on('layeradd', this._addAttribution, this);

  		return this._container;
  	},

  	onRemove: function (map) {
  		map.off('layeradd', this._addAttribution, this);
  	},

  	_addAttribution: function (ev) {
  		if (ev.layer.getAttribution) {
  			this.addAttribution(ev.layer.getAttribution());
  			ev.layer.once('remove', function () {
  				this.removeAttribution(ev.layer.getAttribution());
  			}, this);
  		}
  	},

  	// @method setPrefix(prefix: String|false): this
  	// The HTML text shown before the attributions. Pass `false` to disable.
  	setPrefix: function (prefix) {
  		this.options.prefix = prefix;
  		this._update();
  		return this;
  	},

  	// @method addAttribution(text: String): this
  	// Adds an attribution text (e.g. `'&copy; OpenStreetMap contributors'`).
  	addAttribution: function (text) {
  		if (!text) { return this; }

  		if (!this._attributions[text]) {
  			this._attributions[text] = 0;
  		}
  		this._attributions[text]++;

  		this._update();

  		return this;
  	},

  	// @method removeAttribution(text: String): this
  	// Removes an attribution text.
  	removeAttribution: function (text) {
  		if (!text) { return this; }

  		if (this._attributions[text]) {
  			this._attributions[text]--;
  			this._update();
  		}

  		return this;
  	},

  	_update: function () {
  		if (!this._map) { return; }

  		var attribs = [];

  		for (var i in this._attributions) {
  			if (this._attributions[i]) {
  				attribs.push(i);
  			}
  		}

  		var prefixAndAttribs = [];

  		if (this.options.prefix) {
  			prefixAndAttribs.push(this.options.prefix);
  		}
  		if (attribs.length) {
  			prefixAndAttribs.push(attribs.join(', '));
  		}

  		this._container.innerHTML = prefixAndAttribs.join(' <span aria-hidden="true">|</span> ');
  	}
  });

  // @namespace Map
  // @section Control options
  // @option attributionControl: Boolean = true
  // Whether a [attribution control](#control-attribution) is added to the map by default.
  Map.mergeOptions({
  	attributionControl: true
  });

  Map.addInitHook(function () {
  	if (this.options.attributionControl) {
  		new Attribution().addTo(this);
  	}
  });

  // @namespace Control.Attribution
  // @factory L.control.attribution(options: Control.Attribution options)
  // Creates an attribution control.
  var attribution = function (options) {
  	return new Attribution(options);
  };

  Control.Layers = Layers;
  Control.Zoom = Zoom;
  Control.Scale = Scale;
  Control.Attribution = Attribution;

  control.layers = layers;
  control.zoom = zoom;
  control.scale = scale;
  control.attribution = attribution;

  /*
  	L.Handler is a base class for handler classes that are used internally to inject
  	interaction features like dragging to classes like Map and Marker.
  */

  // @class Handler
  // @aka L.Handler
  // Abstract class for map interaction handlers

  var Handler = Class.extend({
  	initialize: function (map) {
  		this._map = map;
  	},

  	// @method enable(): this
  	// Enables the handler
  	enable: function () {
  		if (this._enabled) { return this; }

  		this._enabled = true;
  		this.addHooks();
  		return this;
  	},

  	// @method disable(): this
  	// Disables the handler
  	disable: function () {
  		if (!this._enabled) { return this; }

  		this._enabled = false;
  		this.removeHooks();
  		return this;
  	},

  	// @method enabled(): Boolean
  	// Returns `true` if the handler is enabled
  	enabled: function () {
  		return !!this._enabled;
  	}

  	// @section Extension methods
  	// Classes inheriting from `Handler` must implement the two following methods:
  	// @method addHooks()
  	// Called when the handler is enabled, should add event hooks.
  	// @method removeHooks()
  	// Called when the handler is disabled, should remove the event hooks added previously.
  });

  // @section There is static function which can be called without instantiating L.Handler:
  // @function addTo(map: Map, name: String): this
  // Adds a new Handler to the given map with the given name.
  Handler.addTo = function (map, name) {
  	map.addHandler(name, this);
  	return this;
  };

  var Mixin = {Events: Events};

  /*
   * @class Draggable
   * @aka L.Draggable
   * @inherits Evented
   *
   * A class for making DOM elements draggable (including touch support).
   * Used internally for map and marker dragging. Only works for elements
   * that were positioned with [`L.DomUtil.setPosition`](#domutil-setposition).
   *
   * @example
   * ```js
   * var draggable = new L.Draggable(elementToDrag);
   * draggable.enable();
   * ```
   */

  var START = Browser.touch ? 'touchstart mousedown' : 'mousedown';

  var Draggable = Evented.extend({

  	options: {
  		// @section
  		// @aka Draggable options
  		// @option clickTolerance: Number = 3
  		// The max number of pixels a user can shift the mouse pointer during a click
  		// for it to be considered a valid click (as opposed to a mouse drag).
  		clickTolerance: 3
  	},

  	// @constructor L.Draggable(el: HTMLElement, dragHandle?: HTMLElement, preventOutline?: Boolean, options?: Draggable options)
  	// Creates a `Draggable` object for moving `el` when you start dragging the `dragHandle` element (equals `el` itself by default).
  	initialize: function (element, dragStartTarget, preventOutline, options) {
  		setOptions(this, options);

  		this._element = element;
  		this._dragStartTarget = dragStartTarget || element;
  		this._preventOutline = preventOutline;
  	},

  	// @method enable()
  	// Enables the dragging ability
  	enable: function () {
  		if (this._enabled) { return; }

  		on(this._dragStartTarget, START, this._onDown, this);

  		this._enabled = true;
  	},

  	// @method disable()
  	// Disables the dragging ability
  	disable: function () {
  		if (!this._enabled) { return; }

  		// If we're currently dragging this draggable,
  		// disabling it counts as first ending the drag.
  		if (Draggable._dragging === this) {
  			this.finishDrag(true);
  		}

  		off(this._dragStartTarget, START, this._onDown, this);

  		this._enabled = false;
  		this._moved = false;
  	},

  	_onDown: function (e) {
  		// Ignore the event if disabled; this happens in IE11
  		// under some circumstances, see #3666.
  		if (!this._enabled) { return; }

  		this._moved = false;

  		if (hasClass(this._element, 'leaflet-zoom-anim')) { return; }

  		if (e.touches && e.touches.length !== 1) {
  			// Finish dragging to avoid conflict with touchZoom
  			if (Draggable._dragging === this) {
  				this.finishDrag();
  			}
  			return;
  		}

  		if (Draggable._dragging || e.shiftKey || ((e.which !== 1) && (e.button !== 1) && !e.touches)) { return; }
  		Draggable._dragging = this;  // Prevent dragging multiple objects at once.

  		if (this._preventOutline) {
  			preventOutline(this._element);
  		}

  		disableImageDrag();
  		disableTextSelection();

  		if (this._moving) { return; }

  		// @event down: Event
  		// Fired when a drag is about to start.
  		this.fire('down');

  		var first = e.touches ? e.touches[0] : e,
  		    sizedParent = getSizedParentNode(this._element);

  		this._startPoint = new Point(first.clientX, first.clientY);
  		this._startPos = getPosition(this._element);

  		// Cache the scale, so that we can continuously compensate for it during drag (_onMove).
  		this._parentScale = getScale(sizedParent);

  		var mouseevent = e.type === 'mousedown';
  		on(document, mouseevent ? 'mousemove' : 'touchmove', this._onMove, this);
  		on(document, mouseevent ? 'mouseup' : 'touchend touchcancel', this._onUp, this);
  	},

  	_onMove: function (e) {
  		// Ignore the event if disabled; this happens in IE11
  		// under some circumstances, see #3666.
  		if (!this._enabled) { return; }

  		if (e.touches && e.touches.length > 1) {
  			this._moved = true;
  			return;
  		}

  		var first = (e.touches && e.touches.length === 1 ? e.touches[0] : e),
  		    offset = new Point(first.clientX, first.clientY)._subtract(this._startPoint);

  		if (!offset.x && !offset.y) { return; }
  		if (Math.abs(offset.x) + Math.abs(offset.y) < this.options.clickTolerance) { return; }

  		// We assume that the parent container's position, border and scale do not change for the duration of the drag.
  		// Therefore there is no need to account for the position and border (they are eliminated by the subtraction)
  		// and we can use the cached value for the scale.
  		offset.x /= this._parentScale.x;
  		offset.y /= this._parentScale.y;

  		preventDefault(e);

  		if (!this._moved) {
  			// @event dragstart: Event
  			// Fired when a drag starts
  			this.fire('dragstart');

  			this._moved = true;

  			addClass(document.body, 'leaflet-dragging');

  			this._lastTarget = e.target || e.srcElement;
  			// IE and Edge do not give the <use> element, so fetch it
  			// if necessary
  			if (window.SVGElementInstance && this._lastTarget instanceof window.SVGElementInstance) {
  				this._lastTarget = this._lastTarget.correspondingUseElement;
  			}
  			addClass(this._lastTarget, 'leaflet-drag-target');
  		}

  		this._newPos = this._startPos.add(offset);
  		this._moving = true;

  		this._lastEvent = e;
  		this._updatePosition();
  	},

  	_updatePosition: function () {
  		var e = {originalEvent: this._lastEvent};

  		// @event predrag: Event
  		// Fired continuously during dragging *before* each corresponding
  		// update of the element's position.
  		this.fire('predrag', e);
  		setPosition(this._element, this._newPos);

  		// @event drag: Event
  		// Fired continuously during dragging.
  		this.fire('drag', e);
  	},

  	_onUp: function () {
  		// Ignore the event if disabled; this happens in IE11
  		// under some circumstances, see #3666.
  		if (!this._enabled) { return; }
  		this.finishDrag();
  	},

  	finishDrag: function (noInertia) {
  		removeClass(document.body, 'leaflet-dragging');

  		if (this._lastTarget) {
  			removeClass(this._lastTarget, 'leaflet-drag-target');
  			this._lastTarget = null;
  		}

  		off(document, 'mousemove touchmove', this._onMove, this);
  		off(document, 'mouseup touchend touchcancel', this._onUp, this);

  		enableImageDrag();
  		enableTextSelection();

  		var fireDragend = this._moved && this._moving;

  		this._moving = false;
  		Draggable._dragging = false;

  		if (fireDragend) {
  			// @event dragend: DragEndEvent
  			// Fired when the drag ends.
  			this.fire('dragend', {
  				noInertia: noInertia,
  				distance: this._newPos.distanceTo(this._startPos)
  			});
  		}
  	}

  });

  /*
   * @namespace PolyUtil
   * Various utility functions for polygon geometries.
   */

  /* @function clipPolygon(points: Point[], bounds: Bounds, round?: Boolean): Point[]
   * Clips the polygon geometry defined by the given `points` by the given bounds (using the [Sutherland-Hodgman algorithm](https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm)).
   * Used by Leaflet to only show polygon points that are on the screen or near, increasing
   * performance. Note that polygon points needs different algorithm for clipping
   * than polyline, so there's a separate method for it.
   */
  function clipPolygon(points, bounds, round) {
  	var clippedPoints,
  	    edges = [1, 4, 2, 8],
  	    i, j, k,
  	    a, b,
  	    len, edge, p;

  	for (i = 0, len = points.length; i < len; i++) {
  		points[i]._code = _getBitCode(points[i], bounds);
  	}

  	// for each edge (left, bottom, right, top)
  	for (k = 0; k < 4; k++) {
  		edge = edges[k];
  		clippedPoints = [];

  		for (i = 0, len = points.length, j = len - 1; i < len; j = i++) {
  			a = points[i];
  			b = points[j];

  			// if a is inside the clip window
  			if (!(a._code & edge)) {
  				// if b is outside the clip window (a->b goes out of screen)
  				if (b._code & edge) {
  					p = _getEdgeIntersection(b, a, edge, bounds, round);
  					p._code = _getBitCode(p, bounds);
  					clippedPoints.push(p);
  				}
  				clippedPoints.push(a);

  			// else if b is inside the clip window (a->b enters the screen)
  			} else if (!(b._code & edge)) {
  				p = _getEdgeIntersection(b, a, edge, bounds, round);
  				p._code = _getBitCode(p, bounds);
  				clippedPoints.push(p);
  			}
  		}
  		points = clippedPoints;
  	}

  	return points;
  }

  /* @function polygonCenter(latlngs: LatLng[], crs: CRS): LatLng
   * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polygon.
   */
  function polygonCenter(latlngs, crs) {
  	var i, j, p1, p2, f, area, x, y, center;

  	if (!latlngs || latlngs.length === 0) {
  		throw new Error('latlngs not passed');
  	}

  	if (!isFlat(latlngs)) {
  		console.warn('latlngs are not flat! Only the first ring will be used');
  		latlngs = latlngs[0];
  	}

  	var centroidLatLng = toLatLng([0, 0]);

  	var bounds = toLatLngBounds(latlngs);
  	var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());
  	// tests showed that below 1700 rounding errors are happening
  	if (areaBounds < 1700) {
  		// getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
  		centroidLatLng = centroid(latlngs);
  	}

  	var len = latlngs.length;
  	var points = [];
  	for (i = 0; i < len; i++) {
  		var latlng = toLatLng(latlngs[i]);
  		points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
  	}

  	area = x = y = 0;

  	// polygon centroid algorithm;
  	for (i = 0, j = len - 1; i < len; j = i++) {
  		p1 = points[i];
  		p2 = points[j];

  		f = p1.y * p2.x - p2.y * p1.x;
  		x += (p1.x + p2.x) * f;
  		y += (p1.y + p2.y) * f;
  		area += f * 3;
  	}

  	if (area === 0) {
  		// Polygon is so small that all points are on same pixel.
  		center = points[0];
  	} else {
  		center = [x / area, y / area];
  	}

  	var latlngCenter = crs.unproject(toPoint(center));
  	return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
  }

  /* @function centroid(latlngs: LatLng[]): LatLng
   * Returns the 'center of mass' of the passed LatLngs.
   */
  function centroid(coords) {
  	var latSum = 0;
  	var lngSum = 0;
  	var len = 0;
  	for (var i = 0; i < coords.length; i++) {
  		var latlng = toLatLng(coords[i]);
  		latSum += latlng.lat;
  		lngSum += latlng.lng;
  		len++;
  	}
  	return toLatLng([latSum / len, lngSum / len]);
  }

  var PolyUtil = {
    __proto__: null,
    clipPolygon: clipPolygon,
    polygonCenter: polygonCenter,
    centroid: centroid
  };

  /*
   * @namespace LineUtil
   *
   * Various utility functions for polyline points processing, used by Leaflet internally to make polylines lightning-fast.
   */

  // Simplify polyline with vertex reduction and Douglas-Peucker simplification.
  // Improves rendering performance dramatically by lessening the number of points to draw.

  // @function simplify(points: Point[], tolerance: Number): Point[]
  // Dramatically reduces the number of points in a polyline while retaining
  // its shape and returns a new array of simplified points, using the
  // [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm).
  // Used for a huge performance boost when processing/displaying Leaflet polylines for
  // each zoom level and also reducing visual noise. tolerance affects the amount of
  // simplification (lesser value means higher quality but slower and with more points).
  // Also released as a separated micro-library [Simplify.js](https://mourner.github.io/simplify-js/).
  function simplify(points, tolerance) {
  	if (!tolerance || !points.length) {
  		return points.slice();
  	}

  	var sqTolerance = tolerance * tolerance;

  	    // stage 1: vertex reduction
  	    points = _reducePoints(points, sqTolerance);

  	    // stage 2: Douglas-Peucker simplification
  	    points = _simplifyDP(points, sqTolerance);

  	return points;
  }

  // @function pointToSegmentDistance(p: Point, p1: Point, p2: Point): Number
  // Returns the distance between point `p` and segment `p1` to `p2`.
  function pointToSegmentDistance(p, p1, p2) {
  	return Math.sqrt(_sqClosestPointOnSegment(p, p1, p2, true));
  }

  // @function closestPointOnSegment(p: Point, p1: Point, p2: Point): Number
  // Returns the closest point from a point `p` on a segment `p1` to `p2`.
  function closestPointOnSegment(p, p1, p2) {
  	return _sqClosestPointOnSegment(p, p1, p2);
  }

  // Ramer-Douglas-Peucker simplification, see https://en.wikipedia.org/wiki/Ramer-Douglas-Peucker_algorithm
  function _simplifyDP(points, sqTolerance) {

  	var len = points.length,
  	    ArrayConstructor = typeof Uint8Array !== undefined + '' ? Uint8Array : Array,
  	    markers = new ArrayConstructor(len);

  	    markers[0] = markers[len - 1] = 1;

  	_simplifyDPStep(points, markers, sqTolerance, 0, len - 1);

  	var i,
  	    newPoints = [];

  	for (i = 0; i < len; i++) {
  		if (markers[i]) {
  			newPoints.push(points[i]);
  		}
  	}

  	return newPoints;
  }

  function _simplifyDPStep(points, markers, sqTolerance, first, last) {

  	var maxSqDist = 0,
  	index, i, sqDist;

  	for (i = first + 1; i <= last - 1; i++) {
  		sqDist = _sqClosestPointOnSegment(points[i], points[first], points[last], true);

  		if (sqDist > maxSqDist) {
  			index = i;
  			maxSqDist = sqDist;
  		}
  	}

  	if (maxSqDist > sqTolerance) {
  		markers[index] = 1;

  		_simplifyDPStep(points, markers, sqTolerance, first, index);
  		_simplifyDPStep(points, markers, sqTolerance, index, last);
  	}
  }

  // reduce points that are too close to each other to a single point
  function _reducePoints(points, sqTolerance) {
  	var reducedPoints = [points[0]];

  	for (var i = 1, prev = 0, len = points.length; i < len; i++) {
  		if (_sqDist(points[i], points[prev]) > sqTolerance) {
  			reducedPoints.push(points[i]);
  			prev = i;
  		}
  	}
  	if (prev < len - 1) {
  		reducedPoints.push(points[len - 1]);
  	}
  	return reducedPoints;
  }

  var _lastCode;

  // @function clipSegment(a: Point, b: Point, bounds: Bounds, useLastCode?: Boolean, round?: Boolean): Point[]|Boolean
  // Clips the segment a to b by rectangular bounds with the
  // [Cohen-Sutherland algorithm](https://en.wikipedia.org/wiki/Cohen%E2%80%93Sutherland_algorithm)
  // (modifying the segment points directly!). Used by Leaflet to only show polyline
  // points that are on the screen or near, increasing performance.
  function clipSegment(a, b, bounds, useLastCode, round) {
  	var codeA = useLastCode ? _lastCode : _getBitCode(a, bounds),
  	    codeB = _getBitCode(b, bounds),

  	    codeOut, p, newCode;

  	    // save 2nd code to avoid calculating it on the next segment
  	    _lastCode = codeB;

  	while (true) {
  		// if a,b is inside the clip window (trivial accept)
  		if (!(codeA | codeB)) {
  			return [a, b];
  		}

  		// if a,b is outside the clip window (trivial reject)
  		if (codeA & codeB) {
  			return false;
  		}

  		// other cases
  		codeOut = codeA || codeB;
  		p = _getEdgeIntersection(a, b, codeOut, bounds, round);
  		newCode = _getBitCode(p, bounds);

  		if (codeOut === codeA) {
  			a = p;
  			codeA = newCode;
  		} else {
  			b = p;
  			codeB = newCode;
  		}
  	}
  }

  function _getEdgeIntersection(a, b, code, bounds, round) {
  	var dx = b.x - a.x,
  	    dy = b.y - a.y,
  	    min = bounds.min,
  	    max = bounds.max,
  	    x, y;

  	if (code & 8) { // top
  		x = a.x + dx * (max.y - a.y) / dy;
  		y = max.y;

  	} else if (code & 4) { // bottom
  		x = a.x + dx * (min.y - a.y) / dy;
  		y = min.y;

  	} else if (code & 2) { // right
  		x = max.x;
  		y = a.y + dy * (max.x - a.x) / dx;

  	} else if (code & 1) { // left
  		x = min.x;
  		y = a.y + dy * (min.x - a.x) / dx;
  	}

  	return new Point(x, y, round);
  }

  function _getBitCode(p, bounds) {
  	var code = 0;

  	if (p.x < bounds.min.x) { // left
  		code |= 1;
  	} else if (p.x > bounds.max.x) { // right
  		code |= 2;
  	}

  	if (p.y < bounds.min.y) { // bottom
  		code |= 4;
  	} else if (p.y > bounds.max.y) { // top
  		code |= 8;
  	}

  	return code;
  }

  // square distance (to avoid unnecessary Math.sqrt calls)
  function _sqDist(p1, p2) {
  	var dx = p2.x - p1.x,
  	    dy = p2.y - p1.y;
  	return dx * dx + dy * dy;
  }

  // return closest point on segment or distance to that point
  function _sqClosestPointOnSegment(p, p1, p2, sqDist) {
  	var x = p1.x,
  	    y = p1.y,
  	    dx = p2.x - x,
  	    dy = p2.y - y,
  	    dot = dx * dx + dy * dy,
  	    t;

  	if (dot > 0) {
  		t = ((p.x - x) * dx + (p.y - y) * dy) / dot;

  		if (t > 1) {
  			x = p2.x;
  			y = p2.y;
  		} else if (t > 0) {
  			x += dx * t;
  			y += dy * t;
  		}
  	}

  	dx = p.x - x;
  	dy = p.y - y;

  	return sqDist ? dx * dx + dy * dy : new Point(x, y);
  }


  // @function isFlat(latlngs: LatLng[]): Boolean
  // Returns true if `latlngs` is a flat array, false is nested.
  function isFlat(latlngs) {
  	return !isArray(latlngs[0]) || (typeof latlngs[0][0] !== 'object' && typeof latlngs[0][0] !== 'undefined');
  }

  function _flat(latlngs) {
  	console.warn('Deprecated use of _flat, please use L.LineUtil.isFlat instead.');
  	return isFlat(latlngs);
  }

  /* @function polylineCenter(latlngs: LatLng[], crs: CRS): LatLng
   * Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the passed LatLngs (first ring) from a polyline.
   */
  function polylineCenter(latlngs, crs) {
  	var i, halfDist, segDist, dist, p1, p2, ratio, center;

  	if (!latlngs || latlngs.length === 0) {
  		throw new Error('latlngs not passed');
  	}

  	if (!isFlat(latlngs)) {
  		console.warn('latlngs are not flat! Only the first ring will be used');
  		latlngs = latlngs[0];
  	}

  	var centroidLatLng = toLatLng([0, 0]);

  	var bounds = toLatLngBounds(latlngs);
  	var areaBounds = bounds.getNorthWest().distanceTo(bounds.getSouthWest()) * bounds.getNorthEast().distanceTo(bounds.getNorthWest());
  	// tests showed that below 1700 rounding errors are happening
  	if (areaBounds < 1700) {
  		// getting a inexact center, to move the latlngs near to [0, 0] to prevent rounding errors
  		centroidLatLng = centroid(latlngs);
  	}

  	var len = latlngs.length;
  	var points = [];
  	for (i = 0; i < len; i++) {
  		var latlng = toLatLng(latlngs[i]);
  		points.push(crs.project(toLatLng([latlng.lat - centroidLatLng.lat, latlng.lng - centroidLatLng.lng])));
  	}

  	for (i = 0, halfDist = 0; i < len - 1; i++) {
  		halfDist += points[i].distanceTo(points[i + 1]) / 2;
  	}

  	// The line is so small in the current view that all points are on the same pixel.
  	if (halfDist === 0) {
  		center = points[0];
  	} else {
  		for (i = 0, dist = 0; i < len - 1; i++) {
  			p1 = points[i];
  			p2 = points[i + 1];
  			segDist = p1.distanceTo(p2);
  			dist += segDist;

  			if (dist > halfDist) {
  				ratio = (dist - halfDist) / segDist;
  				center = [
  					p2.x - ratio * (p2.x - p1.x),
  					p2.y - ratio * (p2.y - p1.y)
  				];
  				break;
  			}
  		}
  	}

  	var latlngCenter = crs.unproject(toPoint(center));
  	return toLatLng([latlngCenter.lat + centroidLatLng.lat, latlngCenter.lng + centroidLatLng.lng]);
  }

  var LineUtil = {
    __proto__: null,
    simplify: simplify,
    pointToSegmentDistance: pointToSegmentDistance,
    closestPointOnSegment: closestPointOnSegment,
    clipSegment: clipSegment,
    _getEdgeIntersection: _getEdgeIntersection,
    _getBitCode: _getBitCode,
    _sqClosestPointOnSegment: _sqClosestPointOnSegment,
    isFlat: isFlat,
    _flat: _flat,
    polylineCenter: polylineCenter
  };

  /*
   * @namespace Projection
   * @section
   * Leaflet comes with a set of already defined Projections out of the box:
   *
   * @projection L.Projection.LonLat
   *
   * Equirectangular, or Plate Carree projection — the most simple projection,
   * mostly used by GIS enthusiasts. Directly maps `x` as longitude, and `y` as
   * latitude. Also suitable for flat worlds, e.g. game maps. Used by the
   * `EPSG:4326` and `Simple` CRS.
   */

  var LonLat = {
  	project: function (latlng) {
  		return new Point(latlng.lng, latlng.lat);
  	},

  	unproject: function (point) {
  		return new LatLng(point.y, point.x);
  	},

  	bounds: new Bounds([-180, -90], [180, 90])
  };

  /*
   * @namespace Projection
   * @projection L.Projection.Mercator
   *
   * Elliptical Mercator projection — more complex than Spherical Mercator. Assumes that Earth is an ellipsoid. Used by the EPSG:3395 CRS.
   */

  var Mercator = {
  	R: 6378137,
  	R_MINOR: 6356752.314245179,

  	bounds: new Bounds([-20037508.34279, -15496570.73972], [20037508.34279, 18764656.23138]),

  	project: function (latlng) {
  		var d = Math.PI / 180,
  		    r = this.R,
  		    y = latlng.lat * d,
  		    tmp = this.R_MINOR / r,
  		    e = Math.sqrt(1 - tmp * tmp),
  		    con = e * Math.sin(y);

  		var ts = Math.tan(Math.PI / 4 - y / 2) / Math.pow((1 - con) / (1 + con), e / 2);
  		y = -r * Math.log(Math.max(ts, 1E-10));

  		return new Point(latlng.lng * d * r, y);
  	},

  	unproject: function (point) {
  		var d = 180 / Math.PI,
  		    r = this.R,
  		    tmp = this.R_MINOR / r,
  		    e = Math.sqrt(1 - tmp * tmp),
  		    ts = Math.exp(-point.y / r),
  		    phi = Math.PI / 2 - 2 * Math.atan(ts);

  		for (var i = 0, dphi = 0.1, con; i < 15 && Math.abs(dphi) > 1e-7; i++) {
  			con = e * Math.sin(phi);
  			con = Math.pow((1 - con) / (1 + con), e / 2);
  			dphi = Math.PI / 2 - 2 * Math.atan(ts * con) - phi;
  			phi += dphi;
  		}

  		return new LatLng(phi * d, point.x * d / r);
  	}
  };

  /*
   * @class Projection

   * An object with methods for projecting geographical coordinates of the world onto
   * a flat surface (and back). See [Map projection](https://en.wikipedia.org/wiki/Map_projection).

   * @property bounds: Bounds
   * The bounds (specified in CRS units) where the projection is valid

   * @method project(latlng: LatLng): Point
   * Projects geographical coordinates into a 2D point.
   * Only accepts actual `L.LatLng` instances, not arrays.

   * @method unproject(point: Point): LatLng
   * The inverse of `project`. Projects a 2D point into a geographical location.
   * Only accepts actual `L.Point` instances, not arrays.

   * Note that the projection instances do not inherit from Leaflet's `Class` object,
   * and can't be instantiated. Also, new classes can't inherit from them,
   * and methods can't be added to them with the `include` function.

   */

  var index = {
    __proto__: null,
    LonLat: LonLat,
    Mercator: Mercator,
    SphericalMercator: SphericalMercator
  };

  /*
   * @namespace CRS
   * @crs L.CRS.EPSG3395
   *
   * Rarely used by some commercial tile providers. Uses Elliptical Mercator projection.
   */
  var EPSG3395 = extend({}, Earth, {
  	code: 'EPSG:3395',
  	projection: Mercator,

  	transformation: (function () {
  		var scale = 0.5 / (Math.PI * Mercator.R);
  		return toTransformation(scale, 0.5, -scale, 0.5);
  	}())
  });

  /*
   * @namespace CRS
   * @crs L.CRS.EPSG4326
   *
   * A common CRS among GIS enthusiasts. Uses simple Equirectangular projection.
   *
   * Leaflet 1.0.x complies with the [TMS coordinate scheme for EPSG:4326](https://wiki.osgeo.org/wiki/Tile_Map_Service_Specification#global-geodetic),
   * which is a breaking change from 0.7.x behaviour.  If you are using a `TileLayer`
   * with this CRS, ensure that there are two 256x256 pixel tiles covering the
   * whole earth at zoom level zero, and that the tile coordinate origin is (-180,+90),
   * or (-180,-90) for `TileLayer`s with [the `tms` option](#tilelayer-tms) set.
   */

  var EPSG4326 = extend({}, Earth, {
  	code: 'EPSG:4326',
  	projection: LonLat,
  	transformation: toTransformation(1 / 180, 1, -1 / 180, 0.5)
  });

  /*
   * @namespace CRS
   * @crs L.CRS.Simple
   *
   * A simple CRS that maps longitude and latitude into `x` and `y` directly.
   * May be used for maps of flat surfaces (e.g. game maps). Note that the `y`
   * axis should still be inverted (going from bottom to top). `distance()` returns
   * simple euclidean distance.
   */

  var Simple = extend({}, CRS, {
  	projection: LonLat,
  	transformation: toTransformation(1, 0, -1, 0),

  	scale: function (zoom) {
  		return Math.pow(2, zoom);
  	},

  	zoom: function (scale) {
  		return Math.log(scale) / Math.LN2;
  	},

  	distance: function (latlng1, latlng2) {
  		var dx = latlng2.lng - latlng1.lng,
  		    dy = latlng2.lat - latlng1.lat;

  		return Math.sqrt(dx * dx + dy * dy);
  	},

  	infinite: true
  });

  CRS.Earth = Earth;
  CRS.EPSG3395 = EPSG3395;
  CRS.EPSG3857 = EPSG3857;
  CRS.EPSG900913 = EPSG900913;
  CRS.EPSG4326 = EPSG4326;
  CRS.Simple = Simple;

  /*
   * @class Layer
   * @inherits Evented
   * @aka L.Layer
   * @aka ILayer
   *
   * A set of methods from the Layer base class that all Leaflet layers use.
   * Inherits all methods, options and events from `L.Evented`.
   *
   * @example
   *
   * ```js
   * var layer = L.marker(latlng).addTo(map);
   * layer.addTo(map);
   * layer.remove();
   * ```
   *
   * @event add: Event
   * Fired after the layer is added to a map
   *
   * @event remove: Event
   * Fired after the layer is removed from a map
   */


  var Layer = Evented.extend({

  	// Classes extending `L.Layer` will inherit the following options:
  	options: {
  		// @option pane: String = 'overlayPane'
  		// By default the layer will be added to the map's [overlay pane](#map-overlaypane). Overriding this option will cause the layer to be placed on another pane by default.
  		pane: 'overlayPane',

  		// @option attribution: String = null
  		// String to be shown in the attribution control, e.g. "© OpenStreetMap contributors". It describes the layer data and is often a legal obligation towards copyright holders and tile providers.
  		attribution: null,

  		bubblingMouseEvents: true
  	},

  	/* @section
  	 * Classes extending `L.Layer` will inherit the following methods:
  	 *
  	 * @method addTo(map: Map|LayerGroup): this
  	 * Adds the layer to the given map or layer group.
  	 */
  	addTo: function (map) {
  		map.addLayer(this);
  		return this;
  	},

  	// @method remove: this
  	// Removes the layer from the map it is currently active on.
  	remove: function () {
  		return this.removeFrom(this._map || this._mapToAdd);
  	},

  	// @method removeFrom(map: Map): this
  	// Removes the layer from the given map
  	//
  	// @alternative
  	// @method removeFrom(group: LayerGroup): this
  	// Removes the layer from the given `LayerGroup`
  	removeFrom: function (obj) {
  		if (obj) {
  			obj.removeLayer(this);
  		}
  		return this;
  	},

  	// @method getPane(name? : String): HTMLElement
  	// Returns the `HTMLElement` representing the named pane on the map. If `name` is omitted, returns the pane for this layer.
  	getPane: function (name) {
  		return this._map.getPane(name ? (this.options[name] || name) : this.options.pane);
  	},

  	addInteractiveTarget: function (targetEl) {
  		this._map._targets[stamp(targetEl)] = this;
  		return this;
  	},

  	removeInteractiveTarget: function (targetEl) {
  		delete this._map._targets[stamp(targetEl)];
  		return this;
  	},

  	// @method getAttribution: String
  	// Used by the `attribution control`, returns the [attribution option](#gridlayer-attribution).
  	getAttribution: function () {
  		return this.options.attribution;
  	},

  	_layerAdd: function (e) {
  		var map = e.target;

  		// check in case layer gets added and then removed before the map is ready
  		if (!map.hasLayer(this)) { return; }

  		this._map = map;
  		this._zoomAnimated = map._zoomAnimated;

  		if (this.getEvents) {
  			var events = this.getEvents();
  			map.on(events, this);
  			this.once('remove', function () {
  				map.off(events, this);
  			}, this);
  		}

  		this.onAdd(map);

  		this.fire('add');
  		map.fire('layeradd', {layer: this});
  	}
  });

  /* @section Extension methods
   * @uninheritable
   *
   * Every layer should extend from `L.Layer` and (re-)implement the following methods.
   *
   * @method onAdd(map: Map): this
   * Should contain code that creates DOM elements for the layer, adds them to `map panes` where they should belong and puts listeners on relevant map events. Called on [`map.addLayer(layer)`](#map-addlayer).
   *
   * @method onRemove(map: Map): this
   * Should contain all clean up code that removes the layer's elements from the DOM and removes listeners previously added in [`onAdd`](#layer-onadd). Called on [`map.removeLayer(layer)`](#map-removelayer).
   *
   * @method getEvents(): Object
   * This optional method should return an object like `{ viewreset: this._reset }` for [`addEventListener`](#evented-addeventlistener). The event handlers in this object will be automatically added and removed from the map with your layer.
   *
   * @method getAttribution(): String
   * This optional method should return a string containing HTML to be shown on the `Attribution control` whenever the layer is visible.
   *
   * @method beforeAdd(map: Map): this
   * Optional method. Called on [`map.addLayer(layer)`](#map-addlayer), before the layer is added to the map, before events are initialized, without waiting until the map is in a usable state. Use for early initialization only.
   */


  /* @namespace Map
   * @section Layer events
   *
   * @event layeradd: LayerEvent
   * Fired when a new layer is added to the map.
   *
   * @event layerremove: LayerEvent
   * Fired when some layer is removed from the map
   *
   * @section Methods for Layers and Controls
   */
  Map.include({
  	// @method addLayer(layer: Layer): this
  	// Adds the given layer to the map
  	addLayer: function (layer) {
  		if (!layer._layerAdd) {
  			throw new Error('The provided object is not a Layer.');
  		}

  		var id = stamp(layer);
  		if (this._layers[id]) { return this; }
  		this._layers[id] = layer;

  		layer._mapToAdd = this;

  		if (layer.beforeAdd) {
  			layer.beforeAdd(this);
  		}

  		this.whenReady(layer._layerAdd, layer);

  		return this;
  	},

  	// @method removeLayer(layer: Layer): this
  	// Removes the given layer from the map.
  	removeLayer: function (layer) {
  		var id = stamp(layer);

  		if (!this._layers[id]) { return this; }

  		if (this._loaded) {
  			layer.onRemove(this);
  		}

  		delete this._layers[id];

  		if (this._loaded) {
  			this.fire('layerremove', {layer: layer});
  			layer.fire('remove');
  		}

  		layer._map = layer._mapToAdd = null;

  		return this;
  	},

  	// @method hasLayer(layer: Layer): Boolean
  	// Returns `true` if the given layer is currently added to the map
  	hasLayer: function (layer) {
  		return stamp(layer) in this._layers;
  	},

  	/* @method eachLayer(fn: Function, context?: Object): this
  	 * Iterates over the layers of the map, optionally specifying context of the iterator function.
  	 * ```
  	 * map.eachLayer(function(layer){
  	 *     layer.bindPopup('Hello');
  	 * });
  	 * ```
  	 */
  	eachLayer: function (method, context) {
  		for (var i in this._layers) {
  			method.call(context, this._layers[i]);
  		}
  		return this;
  	},

  	_addLayers: function (layers) {
  		layers = layers ? (isArray(layers) ? layers : [layers]) : [];

  		for (var i = 0, len = layers.length; i < len; i++) {
  			this.addLayer(layers[i]);
  		}
  	},

  	_addZoomLimit: function (layer) {
  		if (!isNaN(layer.options.maxZoom) || !isNaN(layer.options.minZoom)) {
  			this._zoomBoundLayers[stamp(layer)] = layer;
  			this._updateZoomLevels();
  		}
  	},

  	_removeZoomLimit: function (layer) {
  		var id = stamp(layer);

  		if (this._zoomBoundLayers[id]) {
  			delete this._zoomBoundLayers[id];
  			this._updateZoomLevels();
  		}
  	},

  	_updateZoomLevels: function () {
  		var minZoom = Infinity,
  		    maxZoom = -Infinity,
  		    oldZoomSpan = this._getZoomSpan();

  		for (var i in this._zoomBoundLayers) {
  			var options = this._zoomBoundLayers[i].options;

  			minZoom = options.minZoom === undefined ? minZoom : Math.min(minZoom, options.minZoom);
  			maxZoom = options.maxZoom === undefined ? maxZoom : Math.max(maxZoom, options.maxZoom);
  		}

  		this._layersMaxZoom = maxZoom === -Infinity ? undefined : maxZoom;
  		this._layersMinZoom = minZoom === Infinity ? undefined : minZoom;

  		// @section Map state change events
  		// @event zoomlevelschange: Event
  		// Fired when the number of zoomlevels on the map is changed due
  		// to adding or removing a layer.
  		if (oldZoomSpan !== this._getZoomSpan()) {
  			this.fire('zoomlevelschange');
  		}

  		if (this.options.maxZoom === undefined && this._layersMaxZoom && this.getZoom() > this._layersMaxZoom) {
  			this.setZoom(this._layersMaxZoom);
  		}
  		if (this.options.minZoom === undefined && this._layersMinZoom && this.getZoom() < this._layersMinZoom) {
  			this.setZoom(this._layersMinZoom);
  		}
  	}
  });

  /*
   * @class LayerGroup
   * @aka L.LayerGroup
   * @inherits Interactive layer
   *
   * Used to group several layers and handle them as one. If you add it to the map,
   * any layers added or removed from the group will be added/removed on the map as
   * well. Extends `Layer`.
   *
   * @example
   *
   * ```js
   * L.layerGroup([marker1, marker2])
   * 	.addLayer(polyline)
   * 	.addTo(map);
   * ```
   */

  var LayerGroup = Layer.extend({

  	initialize: function (layers, options) {
  		setOptions(this, options);

  		this._layers = {};

  		var i, len;

  		if (layers) {
  			for (i = 0, len = layers.length; i < len; i++) {
  				this.addLayer(layers[i]);
  			}
  		}
  	},

  	// @method addLayer(layer: Layer): this
  	// Adds the given layer to the group.
  	addLayer: function (layer) {
  		var id = this.getLayerId(layer);

  		this._layers[id] = layer;

  		if (this._map) {
  			this._map.addLayer(layer);
  		}

  		return this;
  	},

  	// @method removeLayer(layer: Layer): this
  	// Removes the given layer from the group.
  	// @alternative
  	// @method removeLayer(id: Number): this
  	// Removes the layer with the given internal ID from the group.
  	removeLayer: function (layer) {
  		var id = layer in this._layers ? layer : this.getLayerId(layer);

  		if (this._map && this._layers[id]) {
  			this._map.removeLayer(this._layers[id]);
  		}

  		delete this._layers[id];

  		return this;
  	},

  	// @method hasLayer(layer: Layer): Boolean
  	// Returns `true` if the given layer is currently added to the group.
  	// @alternative
  	// @method hasLayer(id: Number): Boolean
  	// Returns `true` if the given internal ID is currently added to the group.
  	hasLayer: function (layer) {
  		var layerId = typeof layer === 'number' ? layer : this.getLayerId(layer);
  		return layerId in this._layers;
  	},

  	// @method clearLayers(): this
  	// Removes all the layers from the group.
  	clearLayers: function () {
  		return this.eachLayer(this.removeLayer, this);
  	},

  	// @method invoke(methodName: String, …): this
  	// Calls `methodName` on every layer contained in this group, passing any
  	// additional parameters. Has no effect if the layers contained do not
  	// implement `methodName`.
  	invoke: function (methodName) {
  		var args = Array.prototype.slice.call(arguments, 1),
  		    i, layer;

  		for (i in this._layers) {
  			layer = this._layers[i];

  			if (layer[methodName]) {
  				layer[methodName].apply(layer, args);
  			}
  		}

  		return this;
  	},

  	onAdd: function (map) {
  		this.eachLayer(map.addLayer, map);
  	},

  	onRemove: function (map) {
  		this.eachLayer(map.removeLayer, map);
  	},

  	// @method eachLayer(fn: Function, context?: Object): this
  	// Iterates over the layers of the group, optionally specifying context of the iterator function.
  	// ```js
  	// group.eachLayer(function (layer) {
  	// 	layer.bindPopup('Hello');
  	// });
  	// ```
  	eachLayer: function (method, context) {
  		for (var i in this._layers) {
  			method.call(context, this._layers[i]);
  		}
  		return this;
  	},

  	// @method getLayer(id: Number): Layer
  	// Returns the layer with the given internal ID.
  	getLayer: function (id) {
  		return this._layers[id];
  	},

  	// @method getLayers(): Layer[]
  	// Returns an array of all the layers added to the group.
  	getLayers: function () {
  		var layers = [];
  		this.eachLayer(layers.push, layers);
  		return layers;
  	},

  	// @method setZIndex(zIndex: Number): this
  	// Calls `setZIndex` on every layer contained in this group, passing the z-index.
  	setZIndex: function (zIndex) {
  		return this.invoke('setZIndex', zIndex);
  	},

  	// @method getLayerId(layer: Layer): Number
  	// Returns the internal ID for a layer
  	getLayerId: function (layer) {
  		return stamp(layer);
  	}
  });


  // @factory L.layerGroup(layers?: Layer[], options?: Object)
  // Create a layer group, optionally given an initial set of layers and an `options` object.
  var layerGroup = function (layers, options) {
  	return new LayerGroup(layers, options);
  };

  /*
   * @class FeatureGroup
   * @aka L.FeatureGroup
   * @inherits LayerGroup
   *
   * Extended `LayerGroup` that makes it easier to do the same thing to all its member layers:
   *  * [`bindPopup`](#layer-bindpopup) binds a popup to all of the layers at once (likewise with [`bindTooltip`](#layer-bindtooltip))
   *  * Events are propagated to the `FeatureGroup`, so if the group has an event
   * handler, it will handle events from any of the layers. This includes mouse events
   * and custom events.
   *  * Has `layeradd` and `layerremove` events
   *
   * @example
   *
   * ```js
   * L.featureGroup([marker1, marker2, polyline])
   * 	.bindPopup('Hello world!')
   * 	.on('click', function() { alert('Clicked on a member of the group!'); })
   * 	.addTo(map);
   * ```
   */

  var FeatureGroup = LayerGroup.extend({

  	addLayer: function (layer) {
  		if (this.hasLayer(layer)) {
  			return this;
  		}

  		layer.addEventParent(this);

  		LayerGroup.prototype.addLayer.call(this, layer);

  		// @event layeradd: LayerEvent
  		// Fired when a layer is added to this `FeatureGroup`
  		return this.fire('layeradd', {layer: layer});
  	},

  	removeLayer: function (layer) {
  		if (!this.hasLayer(layer)) {
  			return this;
  		}
  		if (layer in this._layers) {
  			layer = this._layers[layer];
  		}

  		layer.removeEventParent(this);

  		LayerGroup.prototype.removeLayer.call(this, layer);

  		// @event layerremove: LayerEvent
  		// Fired when a layer is removed from this `FeatureGroup`
  		return this.fire('layerremove', {layer: layer});
  	},

  	// @method setStyle(style: Path options): this
  	// Sets the given path options to each layer of the group that has a `setStyle` method.
  	setStyle: function (style) {
  		return this.invoke('setStyle', style);
  	},

  	// @method bringToFront(): this
  	// Brings the layer group to the top of all other layers
  	bringToFront: function () {
  		return this.invoke('bringToFront');
  	},

  	// @method bringToBack(): this
  	// Brings the layer group to the back of all other layers
  	bringToBack: function () {
  		return this.invoke('bringToBack');
  	},

  	// @method getBounds(): LatLngBounds
  	// Returns the LatLngBounds of the Feature Group (created from bounds and coordinates of its children).
  	getBounds: function () {
  		var bounds = new LatLngBounds();

  		for (var id in this._layers) {
  			var layer = this._layers[id];
  			bounds.extend(layer.getBounds ? layer.getBounds() : layer.getLatLng());
  		}
  		return bounds;
  	}
  });

  // @factory L.featureGroup(layers?: Layer[], options?: Object)
  // Create a feature group, optionally given an initial set of layers and an `options` object.
  var featureGroup = function (layers, options) {
  	return new FeatureGroup(layers, options);
  };

  /*
   * @class Icon
   * @aka L.Icon
   *
   * Represents an icon to provide when creating a marker.
   *
   * @example
   *
   * ```js
   * var myIcon = L.icon({
   *     iconUrl: 'my-icon.png',
   *     iconRetinaUrl: 'my-icon@2x.png',
   *     iconSize: [38, 95],
   *     iconAnchor: [22, 94],
   *     popupAnchor: [-3, -76],
   *     shadowUrl: 'my-icon-shadow.png',
   *     shadowRetinaUrl: 'my-icon-shadow@2x.png',
   *     shadowSize: [68, 95],
   *     shadowAnchor: [22, 94]
   * });
   *
   * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
   * ```
   *
   * `L.Icon.Default` extends `L.Icon` and is the blue icon Leaflet uses for markers by default.
   *
   */

  var Icon = Class.extend({

  	/* @section
  	 * @aka Icon options
  	 *
  	 * @option iconUrl: String = null
  	 * **(required)** The URL to the icon image (absolute or relative to your script path).
  	 *
  	 * @option iconRetinaUrl: String = null
  	 * The URL to a retina sized version of the icon image (absolute or relative to your
  	 * script path). Used for Retina screen devices.
  	 *
  	 * @option iconSize: Point = null
  	 * Size of the icon image in pixels.
  	 *
  	 * @option iconAnchor: Point = null
  	 * The coordinates of the "tip" of the icon (relative to its top left corner). The icon
  	 * will be aligned so that this point is at the marker's geographical location. Centered
  	 * by default if size is specified, also can be set in CSS with negative margins.
  	 *
  	 * @option popupAnchor: Point = [0, 0]
  	 * The coordinates of the point from which popups will "open", relative to the icon anchor.
  	 *
  	 * @option tooltipAnchor: Point = [0, 0]
  	 * The coordinates of the point from which tooltips will "open", relative to the icon anchor.
  	 *
  	 * @option shadowUrl: String = null
  	 * The URL to the icon shadow image. If not specified, no shadow image will be created.
  	 *
  	 * @option shadowRetinaUrl: String = null
  	 *
  	 * @option shadowSize: Point = null
  	 * Size of the shadow image in pixels.
  	 *
  	 * @option shadowAnchor: Point = null
  	 * The coordinates of the "tip" of the shadow (relative to its top left corner) (the same
  	 * as iconAnchor if not specified).
  	 *
  	 * @option className: String = ''
  	 * A custom class name to assign to both icon and shadow images. Empty by default.
  	 */

  	options: {
  		popupAnchor: [0, 0],
  		tooltipAnchor: [0, 0],

  		// @option crossOrigin: Boolean|String = false
  		// Whether the crossOrigin attribute will be added to the tiles.
  		// If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
  		// Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
  		crossOrigin: false
  	},

  	initialize: function (options) {
  		setOptions(this, options);
  	},

  	// @method createIcon(oldIcon?: HTMLElement): HTMLElement
  	// Called internally when the icon has to be shown, returns a `<img>` HTML element
  	// styled according to the options.
  	createIcon: function (oldIcon) {
  		return this._createIcon('icon', oldIcon);
  	},

  	// @method createShadow(oldIcon?: HTMLElement): HTMLElement
  	// As `createIcon`, but for the shadow beneath it.
  	createShadow: function (oldIcon) {
  		return this._createIcon('shadow', oldIcon);
  	},

  	_createIcon: function (name, oldIcon) {
  		var src = this._getIconUrl(name);

  		if (!src) {
  			if (name === 'icon') {
  				throw new Error('iconUrl not set in Icon options (see the docs).');
  			}
  			return null;
  		}

  		var img = this._createImg(src, oldIcon && oldIcon.tagName === 'IMG' ? oldIcon : null);
  		this._setIconStyles(img, name);

  		if (this.options.crossOrigin || this.options.crossOrigin === '') {
  			img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
  		}

  		return img;
  	},

  	_setIconStyles: function (img, name) {
  		var options = this.options;
  		var sizeOption = options[name + 'Size'];

  		if (typeof sizeOption === 'number') {
  			sizeOption = [sizeOption, sizeOption];
  		}

  		var size = toPoint(sizeOption),
  		    anchor = toPoint(name === 'shadow' && options.shadowAnchor || options.iconAnchor ||
  		            size && size.divideBy(2, true));

  		img.className = 'leaflet-marker-' + name + ' ' + (options.className || '');

  		if (anchor) {
  			img.style.marginLeft = (-anchor.x) + 'px';
  			img.style.marginTop  = (-anchor.y) + 'px';
  		}

  		if (size) {
  			img.style.width  = size.x + 'px';
  			img.style.height = size.y + 'px';
  		}
  	},

  	_createImg: function (src, el) {
  		el = el || document.createElement('img');
  		el.src = src;
  		return el;
  	},

  	_getIconUrl: function (name) {
  		return Browser.retina && this.options[name + 'RetinaUrl'] || this.options[name + 'Url'];
  	}
  });


  // @factory L.icon(options: Icon options)
  // Creates an icon instance with the given options.
  function icon(options) {
  	return new Icon(options);
  }

  /*
   * @miniclass Icon.Default (Icon)
   * @aka L.Icon.Default
   * @section
   *
   * A trivial subclass of `Icon`, represents the icon to use in `Marker`s when
   * no icon is specified. Points to the blue marker image distributed with Leaflet
   * releases.
   *
   * In order to customize the default icon, just change the properties of `L.Icon.Default.prototype.options`
   * (which is a set of `Icon options`).
   *
   * If you want to _completely_ replace the default icon, override the
   * `L.Marker.prototype.options.icon` with your own icon instead.
   */

  var IconDefault = Icon.extend({

  	options: {
  		iconUrl:       'marker-icon.png',
  		iconRetinaUrl: 'marker-icon-2x.png',
  		shadowUrl:     'marker-shadow.png',
  		iconSize:    [25, 41],
  		iconAnchor:  [12, 41],
  		popupAnchor: [1, -34],
  		tooltipAnchor: [16, -28],
  		shadowSize:  [41, 41]
  	},

  	_getIconUrl: function (name) {
  		if (typeof IconDefault.imagePath !== 'string') {	// Deprecated, backwards-compatibility only
  			IconDefault.imagePath = this._detectIconPath();
  		}

  		// @option imagePath: String
  		// `Icon.Default` will try to auto-detect the location of the
  		// blue icon images. If you are placing these images in a non-standard
  		// way, set this option to point to the right path.
  		return (this.options.imagePath || IconDefault.imagePath) + Icon.prototype._getIconUrl.call(this, name);
  	},

  	_stripUrl: function (path) {	// separate function to use in tests
  		var strip = function (str, re, idx) {
  			var match = re.exec(str);
  			return match && match[idx];
  		};
  		path = strip(path, /^url\((['"])?(.+)\1\)$/, 2);
  		return path && strip(path, /^(.*)marker-icon\.png$/, 1);
  	},

  	_detectIconPath: function () {
  		var el = create$1('div',  'leaflet-default-icon-path', document.body);
  		var path = getStyle(el, 'background-image') ||
  		           getStyle(el, 'backgroundImage');	// IE8

  		document.body.removeChild(el);
  		path = this._stripUrl(path);
  		if (path) { return path; }
  		var link = document.querySelector('link[href$="leaflet.css"]');
  		if (!link) { return ''; }
  		return link.href.substring(0, link.href.length - 'leaflet.css'.length - 1);
  	}
  });

  /*
   * L.Handler.MarkerDrag is used internally by L.Marker to make the markers draggable.
   */


  /* @namespace Marker
   * @section Interaction handlers
   *
   * Interaction handlers are properties of a marker instance that allow you to control interaction behavior in runtime, enabling or disabling certain features such as dragging (see `Handler` methods). Example:
   *
   * ```js
   * marker.dragging.disable();
   * ```
   *
   * @property dragging: Handler
   * Marker dragging handler (by both mouse and touch). Only valid when the marker is on the map (Otherwise set [`marker.options.draggable`](#marker-draggable)).
   */

  var MarkerDrag = Handler.extend({
  	initialize: function (marker) {
  		this._marker = marker;
  	},

  	addHooks: function () {
  		var icon = this._marker._icon;

  		if (!this._draggable) {
  			this._draggable = new Draggable(icon, icon, true);
  		}

  		this._draggable.on({
  			dragstart: this._onDragStart,
  			predrag: this._onPreDrag,
  			drag: this._onDrag,
  			dragend: this._onDragEnd
  		}, this).enable();

  		addClass(icon, 'leaflet-marker-draggable');
  	},

  	removeHooks: function () {
  		this._draggable.off({
  			dragstart: this._onDragStart,
  			predrag: this._onPreDrag,
  			drag: this._onDrag,
  			dragend: this._onDragEnd
  		}, this).disable();

  		if (this._marker._icon) {
  			removeClass(this._marker._icon, 'leaflet-marker-draggable');
  		}
  	},

  	moved: function () {
  		return this._draggable && this._draggable._moved;
  	},

  	_adjustPan: function (e) {
  		var marker = this._marker,
  		    map = marker._map,
  		    speed = this._marker.options.autoPanSpeed,
  		    padding = this._marker.options.autoPanPadding,
  		    iconPos = getPosition(marker._icon),
  		    bounds = map.getPixelBounds(),
  		    origin = map.getPixelOrigin();

  		var panBounds = toBounds(
  			bounds.min._subtract(origin).add(padding),
  			bounds.max._subtract(origin).subtract(padding)
  		);

  		if (!panBounds.contains(iconPos)) {
  			// Compute incremental movement
  			var movement = toPoint(
  				(Math.max(panBounds.max.x, iconPos.x) - panBounds.max.x) / (bounds.max.x - panBounds.max.x) -
  				(Math.min(panBounds.min.x, iconPos.x) - panBounds.min.x) / (bounds.min.x - panBounds.min.x),

  				(Math.max(panBounds.max.y, iconPos.y) - panBounds.max.y) / (bounds.max.y - panBounds.max.y) -
  				(Math.min(panBounds.min.y, iconPos.y) - panBounds.min.y) / (bounds.min.y - panBounds.min.y)
  			).multiplyBy(speed);

  			map.panBy(movement, {animate: false});

  			this._draggable._newPos._add(movement);
  			this._draggable._startPos._add(movement);

  			setPosition(marker._icon, this._draggable._newPos);
  			this._onDrag(e);

  			this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
  		}
  	},

  	_onDragStart: function () {
  		// @section Dragging events
  		// @event dragstart: Event
  		// Fired when the user starts dragging the marker.

  		// @event movestart: Event
  		// Fired when the marker starts moving (because of dragging).

  		this._oldLatLng = this._marker.getLatLng();

  		// When using ES6 imports it could not be set when `Popup` was not imported as well
  		this._marker.closePopup && this._marker.closePopup();

  		this._marker
  			.fire('movestart')
  			.fire('dragstart');
  	},

  	_onPreDrag: function (e) {
  		if (this._marker.options.autoPan) {
  			cancelAnimFrame(this._panRequest);
  			this._panRequest = requestAnimFrame(this._adjustPan.bind(this, e));
  		}
  	},

  	_onDrag: function (e) {
  		var marker = this._marker,
  		    shadow = marker._shadow,
  		    iconPos = getPosition(marker._icon),
  		    latlng = marker._map.layerPointToLatLng(iconPos);

  		// update shadow position
  		if (shadow) {
  			setPosition(shadow, iconPos);
  		}

  		marker._latlng = latlng;
  		e.latlng = latlng;
  		e.oldLatLng = this._oldLatLng;

  		// @event drag: Event
  		// Fired repeatedly while the user drags the marker.
  		marker
  		    .fire('move', e)
  		    .fire('drag', e);
  	},

  	_onDragEnd: function (e) {
  		// @event dragend: DragEndEvent
  		// Fired when the user stops dragging the marker.

  		 cancelAnimFrame(this._panRequest);

  		// @event moveend: Event
  		// Fired when the marker stops moving (because of dragging).
  		delete this._oldLatLng;
  		this._marker
  		    .fire('moveend')
  		    .fire('dragend', e);
  	}
  });

  /*
   * @class Marker
   * @inherits Interactive layer
   * @aka L.Marker
   * L.Marker is used to display clickable/draggable icons on the map. Extends `Layer`.
   *
   * @example
   *
   * ```js
   * L.marker([50.5, 30.5]).addTo(map);
   * ```
   */

  var Marker = Layer.extend({

  	// @section
  	// @aka Marker options
  	options: {
  		// @option icon: Icon = *
  		// Icon instance to use for rendering the marker.
  		// See [Icon documentation](#L.Icon) for details on how to customize the marker icon.
  		// If not specified, a common instance of `L.Icon.Default` is used.
  		icon: new IconDefault(),

  		// Option inherited from "Interactive layer" abstract class
  		interactive: true,

  		// @option keyboard: Boolean = true
  		// Whether the marker can be tabbed to with a keyboard and clicked by pressing enter.
  		keyboard: true,

  		// @option title: String = ''
  		// Text for the browser tooltip that appear on marker hover (no tooltip by default).
  		// [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
  		title: '',

  		// @option alt: String = 'Marker'
  		// Text for the `alt` attribute of the icon image.
  		// [Useful for accessibility](https://leafletjs.com/examples/accessibility/#markers-must-be-labelled).
  		alt: 'Marker',

  		// @option zIndexOffset: Number = 0
  		// By default, marker images zIndex is set automatically based on its latitude. Use this option if you want to put the marker on top of all others (or below), specifying a high value like `1000` (or high negative value, respectively).
  		zIndexOffset: 0,

  		// @option opacity: Number = 1.0
  		// The opacity of the marker.
  		opacity: 1,

  		// @option riseOnHover: Boolean = false
  		// If `true`, the marker will get on top of others when you hover the mouse over it.
  		riseOnHover: false,

  		// @option riseOffset: Number = 250
  		// The z-index offset used for the `riseOnHover` feature.
  		riseOffset: 250,

  		// @option pane: String = 'markerPane'
  		// `Map pane` where the markers icon will be added.
  		pane: 'markerPane',

  		// @option shadowPane: String = 'shadowPane'
  		// `Map pane` where the markers shadow will be added.
  		shadowPane: 'shadowPane',

  		// @option bubblingMouseEvents: Boolean = false
  		// When `true`, a mouse event on this marker will trigger the same event on the map
  		// (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
  		bubblingMouseEvents: false,

  		// @option autoPanOnFocus: Boolean = true
  		// When `true`, the map will pan whenever the marker is focused (via
  		// e.g. pressing `tab` on the keyboard) to ensure the marker is
  		// visible within the map's bounds
  		autoPanOnFocus: true,

  		// @section Draggable marker options
  		// @option draggable: Boolean = false
  		// Whether the marker is draggable with mouse/touch or not.
  		draggable: false,

  		// @option autoPan: Boolean = false
  		// Whether to pan the map when dragging this marker near its edge or not.
  		autoPan: false,

  		// @option autoPanPadding: Point = Point(50, 50)
  		// Distance (in pixels to the left/right and to the top/bottom) of the
  		// map edge to start panning the map.
  		autoPanPadding: [50, 50],

  		// @option autoPanSpeed: Number = 10
  		// Number of pixels the map should pan by.
  		autoPanSpeed: 10
  	},

  	/* @section
  	 *
  	 * In addition to [shared layer methods](#Layer) like `addTo()` and `remove()` and [popup methods](#Popup) like bindPopup() you can also use the following methods:
  	 */

  	initialize: function (latlng, options) {
  		setOptions(this, options);
  		this._latlng = toLatLng(latlng);
  	},

  	onAdd: function (map) {
  		this._zoomAnimated = this._zoomAnimated && map.options.markerZoomAnimation;

  		if (this._zoomAnimated) {
  			map.on('zoomanim', this._animateZoom, this);
  		}

  		this._initIcon();
  		this.update();
  	},

  	onRemove: function (map) {
  		if (this.dragging && this.dragging.enabled()) {
  			this.options.draggable = true;
  			this.dragging.removeHooks();
  		}
  		delete this.dragging;

  		if (this._zoomAnimated) {
  			map.off('zoomanim', this._animateZoom, this);
  		}

  		this._removeIcon();
  		this._removeShadow();
  	},

  	getEvents: function () {
  		return {
  			zoom: this.update,
  			viewreset: this.update
  		};
  	},

  	// @method getLatLng: LatLng
  	// Returns the current geographical position of the marker.
  	getLatLng: function () {
  		return this._latlng;
  	},

  	// @method setLatLng(latlng: LatLng): this
  	// Changes the marker position to the given point.
  	setLatLng: function (latlng) {
  		var oldLatLng = this._latlng;
  		this._latlng = toLatLng(latlng);
  		this.update();

  		// @event move: Event
  		// Fired when the marker is moved via [`setLatLng`](#marker-setlatlng) or by [dragging](#marker-dragging). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
  		return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
  	},

  	// @method setZIndexOffset(offset: Number): this
  	// Changes the [zIndex offset](#marker-zindexoffset) of the marker.
  	setZIndexOffset: function (offset) {
  		this.options.zIndexOffset = offset;
  		return this.update();
  	},

  	// @method getIcon: Icon
  	// Returns the current icon used by the marker
  	getIcon: function () {
  		return this.options.icon;
  	},

  	// @method setIcon(icon: Icon): this
  	// Changes the marker icon.
  	setIcon: function (icon) {

  		this.options.icon = icon;

  		if (this._map) {
  			this._initIcon();
  			this.update();
  		}

  		if (this._popup) {
  			this.bindPopup(this._popup, this._popup.options);
  		}

  		return this;
  	},

  	getElement: function () {
  		return this._icon;
  	},

  	update: function () {

  		if (this._icon && this._map) {
  			var pos = this._map.latLngToLayerPoint(this._latlng).round();
  			this._setPos(pos);
  		}

  		return this;
  	},

  	_initIcon: function () {
  		var options = this.options,
  		    classToAdd = 'leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');

  		var icon = options.icon.createIcon(this._icon),
  		    addIcon = false;

  		// if we're not reusing the icon, remove the old one and init new one
  		if (icon !== this._icon) {
  			if (this._icon) {
  				this._removeIcon();
  			}
  			addIcon = true;

  			if (options.title) {
  				icon.title = options.title;
  			}

  			if (icon.tagName === 'IMG') {
  				icon.alt = options.alt || '';
  			}
  		}

  		addClass(icon, classToAdd);

  		if (options.keyboard) {
  			icon.tabIndex = '0';
  			icon.setAttribute('role', 'button');
  		}

  		this._icon = icon;

  		if (options.riseOnHover) {
  			this.on({
  				mouseover: this._bringToFront,
  				mouseout: this._resetZIndex
  			});
  		}

  		if (this.options.autoPanOnFocus) {
  			on(icon, 'focus', this._panOnFocus, this);
  		}

  		var newShadow = options.icon.createShadow(this._shadow),
  		    addShadow = false;

  		if (newShadow !== this._shadow) {
  			this._removeShadow();
  			addShadow = true;
  		}

  		if (newShadow) {
  			addClass(newShadow, classToAdd);
  			newShadow.alt = '';
  		}
  		this._shadow = newShadow;


  		if (options.opacity < 1) {
  			this._updateOpacity();
  		}


  		if (addIcon) {
  			this.getPane().appendChild(this._icon);
  		}
  		this._initInteraction();
  		if (newShadow && addShadow) {
  			this.getPane(options.shadowPane).appendChild(this._shadow);
  		}
  	},

  	_removeIcon: function () {
  		if (this.options.riseOnHover) {
  			this.off({
  				mouseover: this._bringToFront,
  				mouseout: this._resetZIndex
  			});
  		}

  		if (this.options.autoPanOnFocus) {
  			off(this._icon, 'focus', this._panOnFocus, this);
  		}

  		remove(this._icon);
  		this.removeInteractiveTarget(this._icon);

  		this._icon = null;
  	},

  	_removeShadow: function () {
  		if (this._shadow) {
  			remove(this._shadow);
  		}
  		this._shadow = null;
  	},

  	_setPos: function (pos) {

  		if (this._icon) {
  			setPosition(this._icon, pos);
  		}

  		if (this._shadow) {
  			setPosition(this._shadow, pos);
  		}

  		this._zIndex = pos.y + this.options.zIndexOffset;

  		this._resetZIndex();
  	},

  	_updateZIndex: function (offset) {
  		if (this._icon) {
  			this._icon.style.zIndex = this._zIndex + offset;
  		}
  	},

  	_animateZoom: function (opt) {
  		var pos = this._map._latLngToNewLayerPoint(this._latlng, opt.zoom, opt.center).round();

  		this._setPos(pos);
  	},

  	_initInteraction: function () {

  		if (!this.options.interactive) { return; }

  		addClass(this._icon, 'leaflet-interactive');

  		this.addInteractiveTarget(this._icon);

  		if (MarkerDrag) {
  			var draggable = this.options.draggable;
  			if (this.dragging) {
  				draggable = this.dragging.enabled();
  				this.dragging.disable();
  			}

  			this.dragging = new MarkerDrag(this);

  			if (draggable) {
  				this.dragging.enable();
  			}
  		}
  	},

  	// @method setOpacity(opacity: Number): this
  	// Changes the opacity of the marker.
  	setOpacity: function (opacity) {
  		this.options.opacity = opacity;
  		if (this._map) {
  			this._updateOpacity();
  		}

  		return this;
  	},

  	_updateOpacity: function () {
  		var opacity = this.options.opacity;

  		if (this._icon) {
  			setOpacity(this._icon, opacity);
  		}

  		if (this._shadow) {
  			setOpacity(this._shadow, opacity);
  		}
  	},

  	_bringToFront: function () {
  		this._updateZIndex(this.options.riseOffset);
  	},

  	_resetZIndex: function () {
  		this._updateZIndex(0);
  	},

  	_panOnFocus: function () {
  		var map = this._map;
  		if (!map) { return; }

  		var iconOpts = this.options.icon.options;
  		var size = iconOpts.iconSize ? toPoint(iconOpts.iconSize) : toPoint(0, 0);
  		var anchor = iconOpts.iconAnchor ? toPoint(iconOpts.iconAnchor) : toPoint(0, 0);

  		map.panInside(this._latlng, {
  			paddingTopLeft: anchor,
  			paddingBottomRight: size.subtract(anchor)
  		});
  	},

  	_getPopupAnchor: function () {
  		return this.options.icon.options.popupAnchor;
  	},

  	_getTooltipAnchor: function () {
  		return this.options.icon.options.tooltipAnchor;
  	}
  });


  // factory L.marker(latlng: LatLng, options? : Marker options)

  // @factory L.marker(latlng: LatLng, options? : Marker options)
  // Instantiates a Marker object given a geographical point and optionally an options object.
  function marker(latlng, options) {
  	return new Marker(latlng, options);
  }

  /*
   * @class Path
   * @aka L.Path
   * @inherits Interactive layer
   *
   * An abstract class that contains options and constants shared between vector
   * overlays (Polygon, Polyline, Circle). Do not use it directly. Extends `Layer`.
   */

  var Path = Layer.extend({

  	// @section
  	// @aka Path options
  	options: {
  		// @option stroke: Boolean = true
  		// Whether to draw stroke along the path. Set it to `false` to disable borders on polygons or circles.
  		stroke: true,

  		// @option color: String = '#3388ff'
  		// Stroke color
  		color: '#3388ff',

  		// @option weight: Number = 3
  		// Stroke width in pixels
  		weight: 3,

  		// @option opacity: Number = 1.0
  		// Stroke opacity
  		opacity: 1,

  		// @option lineCap: String= 'round'
  		// A string that defines [shape to be used at the end](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linecap) of the stroke.
  		lineCap: 'round',

  		// @option lineJoin: String = 'round'
  		// A string that defines [shape to be used at the corners](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-linejoin) of the stroke.
  		lineJoin: 'round',

  		// @option dashArray: String = null
  		// A string that defines the stroke [dash pattern](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dasharray). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
  		dashArray: null,

  		// @option dashOffset: String = null
  		// A string that defines the [distance into the dash pattern to start the dash](https://developer.mozilla.org/docs/Web/SVG/Attribute/stroke-dashoffset). Doesn't work on `Canvas`-powered layers in [some old browsers](https://developer.mozilla.org/docs/Web/API/CanvasRenderingContext2D/setLineDash#Browser_compatibility).
  		dashOffset: null,

  		// @option fill: Boolean = depends
  		// Whether to fill the path with color. Set it to `false` to disable filling on polygons or circles.
  		fill: false,

  		// @option fillColor: String = *
  		// Fill color. Defaults to the value of the [`color`](#path-color) option
  		fillColor: null,

  		// @option fillOpacity: Number = 0.2
  		// Fill opacity.
  		fillOpacity: 0.2,

  		// @option fillRule: String = 'evenodd'
  		// A string that defines [how the inside of a shape](https://developer.mozilla.org/docs/Web/SVG/Attribute/fill-rule) is determined.
  		fillRule: 'evenodd',

  		// className: '',

  		// Option inherited from "Interactive layer" abstract class
  		interactive: true,

  		// @option bubblingMouseEvents: Boolean = true
  		// When `true`, a mouse event on this path will trigger the same event on the map
  		// (unless [`L.DomEvent.stopPropagation`](#domevent-stoppropagation) is used).
  		bubblingMouseEvents: true
  	},

  	beforeAdd: function (map) {
  		// Renderer is set here because we need to call renderer.getEvents
  		// before this.getEvents.
  		this._renderer = map.getRenderer(this);
  	},

  	onAdd: function () {
  		this._renderer._initPath(this);
  		this._reset();
  		this._renderer._addPath(this);
  	},

  	onRemove: function () {
  		this._renderer._removePath(this);
  	},

  	// @method redraw(): this
  	// Redraws the layer. Sometimes useful after you changed the coordinates that the path uses.
  	redraw: function () {
  		if (this._map) {
  			this._renderer._updatePath(this);
  		}
  		return this;
  	},

  	// @method setStyle(style: Path options): this
  	// Changes the appearance of a Path based on the options in the `Path options` object.
  	setStyle: function (style) {
  		setOptions(this, style);
  		if (this._renderer) {
  			this._renderer._updateStyle(this);
  			if (this.options.stroke && style && Object.prototype.hasOwnProperty.call(style, 'weight')) {
  				this._updateBounds();
  			}
  		}
  		return this;
  	},

  	// @method bringToFront(): this
  	// Brings the layer to the top of all path layers.
  	bringToFront: function () {
  		if (this._renderer) {
  			this._renderer._bringToFront(this);
  		}
  		return this;
  	},

  	// @method bringToBack(): this
  	// Brings the layer to the bottom of all path layers.
  	bringToBack: function () {
  		if (this._renderer) {
  			this._renderer._bringToBack(this);
  		}
  		return this;
  	},

  	getElement: function () {
  		return this._path;
  	},

  	_reset: function () {
  		// defined in child classes
  		this._project();
  		this._update();
  	},

  	_clickTolerance: function () {
  		// used when doing hit detection for Canvas layers
  		return (this.options.stroke ? this.options.weight / 2 : 0) +
  		  (this._renderer.options.tolerance || 0);
  	}
  });

  /*
   * @class CircleMarker
   * @aka L.CircleMarker
   * @inherits Path
   *
   * A circle of a fixed size with radius specified in pixels. Extends `Path`.
   */

  var CircleMarker = Path.extend({

  	// @section
  	// @aka CircleMarker options
  	options: {
  		fill: true,

  		// @option radius: Number = 10
  		// Radius of the circle marker, in pixels
  		radius: 10
  	},

  	initialize: function (latlng, options) {
  		setOptions(this, options);
  		this._latlng = toLatLng(latlng);
  		this._radius = this.options.radius;
  	},

  	// @method setLatLng(latLng: LatLng): this
  	// Sets the position of a circle marker to a new location.
  	setLatLng: function (latlng) {
  		var oldLatLng = this._latlng;
  		this._latlng = toLatLng(latlng);
  		this.redraw();

  		// @event move: Event
  		// Fired when the marker is moved via [`setLatLng`](#circlemarker-setlatlng). Old and new coordinates are included in event arguments as `oldLatLng`, `latlng`.
  		return this.fire('move', {oldLatLng: oldLatLng, latlng: this._latlng});
  	},

  	// @method getLatLng(): LatLng
  	// Returns the current geographical position of the circle marker
  	getLatLng: function () {
  		return this._latlng;
  	},

  	// @method setRadius(radius: Number): this
  	// Sets the radius of a circle marker. Units are in pixels.
  	setRadius: function (radius) {
  		this.options.radius = this._radius = radius;
  		return this.redraw();
  	},

  	// @method getRadius(): Number
  	// Returns the current radius of the circle
  	getRadius: function () {
  		return this._radius;
  	},

  	setStyle : function (options) {
  		var radius = options && options.radius || this._radius;
  		Path.prototype.setStyle.call(this, options);
  		this.setRadius(radius);
  		return this;
  	},

  	_project: function () {
  		this._point = this._map.latLngToLayerPoint(this._latlng);
  		this._updateBounds();
  	},

  	_updateBounds: function () {
  		var r = this._radius,
  		    r2 = this._radiusY || r,
  		    w = this._clickTolerance(),
  		    p = [r + w, r2 + w];
  		this._pxBounds = new Bounds(this._point.subtract(p), this._point.add(p));
  	},

  	_update: function () {
  		if (this._map) {
  			this._updatePath();
  		}
  	},

  	_updatePath: function () {
  		this._renderer._updateCircle(this);
  	},

  	_empty: function () {
  		return this._radius && !this._renderer._bounds.intersects(this._pxBounds);
  	},

  	// Needed by the `Canvas` renderer for interactivity
  	_containsPoint: function (p) {
  		return p.distanceTo(this._point) <= this._radius + this._clickTolerance();
  	}
  });


  // @factory L.circleMarker(latlng: LatLng, options?: CircleMarker options)
  // Instantiates a circle marker object given a geographical point, and an optional options object.
  function circleMarker(latlng, options) {
  	return new CircleMarker(latlng, options);
  }

  /*
   * @class Circle
   * @aka L.Circle
   * @inherits CircleMarker
   *
   * A class for drawing circle overlays on a map. Extends `CircleMarker`.
   *
   * It's an approximation and starts to diverge from a real circle closer to poles (due to projection distortion).
   *
   * @example
   *
   * ```js
   * L.circle([50.5, 30.5], {radius: 200}).addTo(map);
   * ```
   */

  var Circle = CircleMarker.extend({

  	initialize: function (latlng, options, legacyOptions) {
  		if (typeof options === 'number') {
  			// Backwards compatibility with 0.7.x factory (latlng, radius, options?)
  			options = extend({}, legacyOptions, {radius: options});
  		}
  		setOptions(this, options);
  		this._latlng = toLatLng(latlng);

  		if (isNaN(this.options.radius)) { throw new Error('Circle radius cannot be NaN'); }

  		// @section
  		// @aka Circle options
  		// @option radius: Number; Radius of the circle, in meters.
  		this._mRadius = this.options.radius;
  	},

  	// @method setRadius(radius: Number): this
  	// Sets the radius of a circle. Units are in meters.
  	setRadius: function (radius) {
  		this._mRadius = radius;
  		return this.redraw();
  	},

  	// @method getRadius(): Number
  	// Returns the current radius of a circle. Units are in meters.
  	getRadius: function () {
  		return this._mRadius;
  	},

  	// @method getBounds(): LatLngBounds
  	// Returns the `LatLngBounds` of the path.
  	getBounds: function () {
  		var half = [this._radius, this._radiusY || this._radius];

  		return new LatLngBounds(
  			this._map.layerPointToLatLng(this._point.subtract(half)),
  			this._map.layerPointToLatLng(this._point.add(half)));
  	},

  	setStyle: Path.prototype.setStyle,

  	_project: function () {

  		var lng = this._latlng.lng,
  		    lat = this._latlng.lat,
  		    map = this._map,
  		    crs = map.options.crs;

  		if (crs.distance === Earth.distance) {
  			var d = Math.PI / 180,
  			    latR = (this._mRadius / Earth.R) / d,
  			    top = map.project([lat + latR, lng]),
  			    bottom = map.project([lat - latR, lng]),
  			    p = top.add(bottom).divideBy(2),
  			    lat2 = map.unproject(p).lat,
  			    lngR = Math.acos((Math.cos(latR * d) - Math.sin(lat * d) * Math.sin(lat2 * d)) /
  			            (Math.cos(lat * d) * Math.cos(lat2 * d))) / d;

  			if (isNaN(lngR) || lngR === 0) {
  				lngR = latR / Math.cos(Math.PI / 180 * lat); // Fallback for edge case, #2425
  			}

  			this._point = p.subtract(map.getPixelOrigin());
  			this._radius = isNaN(lngR) ? 0 : p.x - map.project([lat2, lng - lngR]).x;
  			this._radiusY = p.y - top.y;

  		} else {
  			var latlng2 = crs.unproject(crs.project(this._latlng).subtract([this._mRadius, 0]));

  			this._point = map.latLngToLayerPoint(this._latlng);
  			this._radius = this._point.x - map.latLngToLayerPoint(latlng2).x;
  		}

  		this._updateBounds();
  	}
  });

  // @factory L.circle(latlng: LatLng, options?: Circle options)
  // Instantiates a circle object given a geographical point, and an options object
  // which contains the circle radius.
  // @alternative
  // @factory L.circle(latlng: LatLng, radius: Number, options?: Circle options)
  // Obsolete way of instantiating a circle, for compatibility with 0.7.x code.
  // Do not use in new applications or plugins.
  function circle(latlng, options, legacyOptions) {
  	return new Circle(latlng, options, legacyOptions);
  }

  /*
   * @class Polyline
   * @aka L.Polyline
   * @inherits Path
   *
   * A class for drawing polyline overlays on a map. Extends `Path`.
   *
   * @example
   *
   * ```js
   * // create a red polyline from an array of LatLng points
   * var latlngs = [
   * 	[45.51, -122.68],
   * 	[37.77, -122.43],
   * 	[34.04, -118.2]
   * ];
   *
   * var polyline = L.polyline(latlngs, {color: 'red'}).addTo(map);
   *
   * // zoom the map to the polyline
   * map.fitBounds(polyline.getBounds());
   * ```
   *
   * You can also pass a multi-dimensional array to represent a `MultiPolyline` shape:
   *
   * ```js
   * // create a red polyline from an array of arrays of LatLng points
   * var latlngs = [
   * 	[[45.51, -122.68],
   * 	 [37.77, -122.43],
   * 	 [34.04, -118.2]],
   * 	[[40.78, -73.91],
   * 	 [41.83, -87.62],
   * 	 [32.76, -96.72]]
   * ];
   * ```
   */


  var Polyline = Path.extend({

  	// @section
  	// @aka Polyline options
  	options: {
  		// @option smoothFactor: Number = 1.0
  		// How much to simplify the polyline on each zoom level. More means
  		// better performance and smoother look, and less means more accurate representation.
  		smoothFactor: 1.0,

  		// @option noClip: Boolean = false
  		// Disable polyline clipping.
  		noClip: false
  	},

  	initialize: function (latlngs, options) {
  		setOptions(this, options);
  		this._setLatLngs(latlngs);
  	},

  	// @method getLatLngs(): LatLng[]
  	// Returns an array of the points in the path, or nested arrays of points in case of multi-polyline.
  	getLatLngs: function () {
  		return this._latlngs;
  	},

  	// @method setLatLngs(latlngs: LatLng[]): this
  	// Replaces all the points in the polyline with the given array of geographical points.
  	setLatLngs: function (latlngs) {
  		this._setLatLngs(latlngs);
  		return this.redraw();
  	},

  	// @method isEmpty(): Boolean
  	// Returns `true` if the Polyline has no LatLngs.
  	isEmpty: function () {
  		return !this._latlngs.length;
  	},

  	// @method closestLayerPoint(p: Point): Point
  	// Returns the point closest to `p` on the Polyline.
  	closestLayerPoint: function (p) {
  		var minDistance = Infinity,
  		    minPoint = null,
  		    closest = _sqClosestPointOnSegment,
  		    p1, p2;

  		for (var j = 0, jLen = this._parts.length; j < jLen; j++) {
  			var points = this._parts[j];

  			for (var i = 1, len = points.length; i < len; i++) {
  				p1 = points[i - 1];
  				p2 = points[i];

  				var sqDist = closest(p, p1, p2, true);

  				if (sqDist < minDistance) {
  					minDistance = sqDist;
  					minPoint = closest(p, p1, p2);
  				}
  			}
  		}
  		if (minPoint) {
  			minPoint.distance = Math.sqrt(minDistance);
  		}
  		return minPoint;
  	},

  	// @method getCenter(): LatLng
  	// Returns the center ([centroid](https://en.wikipedia.org/wiki/Centroid)) of the polyline.
  	getCenter: function () {
  		// throws error when not yet added to map as this center calculation requires projected coordinates
  		if (!this._map) {
  			throw new Error('Must add layer to map before using getCenter()');
  		}
  		return polylineCenter(this._defaultShape(), this._map.options.crs);
  	},

  	// @method getBounds(): LatLngBounds
  	// Returns the `LatLngBounds` of the path.
  	getBounds: function () {
  		return this._bounds;
  	},

  	// @method addLatLng(latlng: LatLng, latlngs?: LatLng[]): this
  	// Adds a given point to the polyline. By default, adds to the first ring of
  	// the polyline in case of a multi-polyline, but can be overridden by passing
  	// a specific ring as a LatLng array (that you can earlier access with [`getLatLngs`](#polyline-getlatlngs)).
  	addLatLng: function (latlng, latlngs) {
  		latlngs = latlngs || this._defaultShape();
  		latlng = toLatLng(latlng);
  		latlngs.push(latlng);
  		this._bounds.extend(latlng);
  		return this.redraw();
  	},

  	_setLatLngs: function (latlngs) {
  		this._bounds = new LatLngBounds();
  		this._latlngs = this._convertLatLngs(latlngs);
  	},

  	_defaultShape: function () {
  		return isFlat(this._latlngs) ? this._latlngs : this._latlngs[0];
  	},

  	// recursively convert latlngs input into actual LatLng instances; calculate bounds along the way
  	_convertLatLngs: function (latlngs) {
  		var result = [],
  		    flat = isFlat(latlngs);

  		for (var i = 0, len = latlngs.length; i < len; i++) {
  			if (flat) {
  				result[i] = toLatLng(latlngs[i]);
  				this._bounds.extend(result[i]);
  			} else {
  				result[i] = this._convertLatLngs(latlngs[i]);
  			}
  		}

  		return result;
  	},

  	_project: function () {
  		var pxBounds = new Bounds();
  		this._rings = [];
  		this._projectLatlngs(this._latlngs, this._rings, pxBounds);

  		if (this._bounds.isValid() && pxBounds.isValid()) {
  			this._rawPxBounds = pxBounds;
  			this._updateBounds();
  		}
  	},

  	_updateBounds: function () {
  		var w = this._clickTolerance(),
  		    p = new Point(w, w);

  		if (!this._rawPxBounds) {
  			return;
  		}

  		this._pxBounds = new Bounds([
  			this._rawPxBounds.min.subtract(p),
  			this._rawPxBounds.max.add(p)
  		]);
  	},

  	// recursively turns latlngs into a set of rings with projected coordinates
  	_projectLatlngs: function (latlngs, result, projectedBounds) {
  		var flat = latlngs[0] instanceof LatLng,
  		    len = latlngs.length,
  		    i, ring;

  		if (flat) {
  			ring = [];
  			for (i = 0; i < len; i++) {
  				ring[i] = this._map.latLngToLayerPoint(latlngs[i]);
  				projectedBounds.extend(ring[i]);
  			}
  			result.push(ring);
  		} else {
  			for (i = 0; i < len; i++) {
  				this._projectLatlngs(latlngs[i], result, projectedBounds);
  			}
  		}
  	},

  	// clip polyline by renderer bounds so that we have less to render for performance
  	_clipPoints: function () {
  		var bounds = this._renderer._bounds;

  		this._parts = [];
  		if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
  			return;
  		}

  		if (this.options.noClip) {
  			this._parts = this._rings;
  			return;
  		}

  		var parts = this._parts,
  		    i, j, k, len, len2, segment, points;

  		for (i = 0, k = 0, len = this._rings.length; i < len; i++) {
  			points = this._rings[i];

  			for (j = 0, len2 = points.length; j < len2 - 1; j++) {
  				segment = clipSegment(points[j], points[j + 1], bounds, j, true);

  				if (!segment) { continue; }

  				parts[k] = parts[k] || [];
  				parts[k].push(segment[0]);

  				// if segment goes out of screen, or it's the last one, it's the end of the line part
  				if ((segment[1] !== points[j + 1]) || (j === len2 - 2)) {
  					parts[k].push(segment[1]);
  					k++;
  				}
  			}
  		}
  	},

  	// simplify each clipped part of the polyline for performance
  	_simplifyPoints: function () {
  		var parts = this._parts,
  		    tolerance = this.options.smoothFactor;

  		for (var i = 0, len = parts.length; i < len; i++) {
  			parts[i] = simplify(parts[i], tolerance);
  		}
  	},

  	_update: function () {
  		if (!this._map) { return; }

  		this._clipPoints();
  		this._simplifyPoints();
  		this._updatePath();
  	},

  	_updatePath: function () {
  		this._renderer._updatePoly(this);
  	},

  	// Needed by the `Canvas` renderer for interactivity
  	_containsPoint: function (p, closed) {
  		var i, j, k, len, len2, part,
  		    w = this._clickTolerance();

  		if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }

  		// hit detection for polylines
  		for (i = 0, len = this._parts.length; i < len; i++) {
  			part = this._parts[i];

  			for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
  				if (!closed && (j === 0)) { continue; }

  				if (pointToSegmentDistance(p, part[k], part[j]) <= w) {
  					return true;
  				}
  			}
  		}
  		return false;
  	}
  });

  // @factory L.polyline(latlngs: LatLng[], options?: Polyline options)
  // Instantiates a polyline object given an array of geographical points and
  // optionally an options object. You can create a `Polyline` object with
  // multiple separate lines (`MultiPolyline`) by passing an array of arrays
  // of geographic points.
  function polyline(latlngs, options) {
  	return new Polyline(latlngs, options);
  }

  // Retrocompat. Allow plugins to support Leaflet versions before and after 1.1.
  Polyline._flat = _flat;

  /*
   * @class Polygon
   * @aka L.Polygon
   * @inherits Polyline
   *
   * A class for drawing polygon overlays on a map. Extends `Polyline`.
   *
   * Note that points you pass when creating a polygon shouldn't have an additional last point equal to the first one — it's better to filter out such points.
   *
   *
   * @example
   *
   * ```js
   * // create a red polygon from an array of LatLng points
   * var latlngs = [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]];
   *
   * var polygon = L.polygon(latlngs, {color: 'red'}).addTo(map);
   *
   * // zoom the map to the polygon
   * map.fitBounds(polygon.getBounds());
   * ```
   *
   * You can also pass an array of arrays of latlngs, with the first array representing the outer shape and the other arrays representing holes in the outer shape:
   *
   * ```js
   * var latlngs = [
   *   [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
   *   [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
   * ];
   * ```
   *
   * Additionally, you can pass a multi-dimensional array to represent a MultiPolygon shape.
   *
   * ```js
   * var latlngs = [
   *   [ // first polygon
   *     [[37, -109.05],[41, -109.03],[41, -102.05],[37, -102.04]], // outer ring
   *     [[37.29, -108.58],[40.71, -108.58],[40.71, -102.50],[37.29, -102.50]] // hole
   *   ],
   *   [ // second polygon
   *     [[41, -111.03],[45, -111.04],[45, -104.05],[41, -104.05]]
   *   ]
   * ];
   * ```
   */

  var Polygon = Polyline.extend({

  	options: {
  		fill: true
  	},

  	isEmpty: function () {
  		return !this._latlngs.length || !this._latlngs[0].length;
  	},

  	// @method getCenter(): LatLng
  	// Returns the center ([centroid](http://en.wikipedia.org/wiki/Centroid)) of the Polygon.
  	getCenter: function () {
  		// throws error when not yet added to map as this center calculation requires projected coordinates
  		if (!this._map) {
  			throw new Error('Must add layer to map before using getCenter()');
  		}
  		return polygonCenter(this._defaultShape(), this._map.options.crs);
  	},

  	_convertLatLngs: function (latlngs) {
  		var result = Polyline.prototype._convertLatLngs.call(this, latlngs),
  		    len = result.length;

  		// remove last point if it equals first one
  		if (len >= 2 && result[0] instanceof LatLng && result[0].equals(result[len - 1])) {
  			result.pop();
  		}
  		return result;
  	},

  	_setLatLngs: function (latlngs) {
  		Polyline.prototype._setLatLngs.call(this, latlngs);
  		if (isFlat(this._latlngs)) {
  			this._latlngs = [this._latlngs];
  		}
  	},

  	_defaultShape: function () {
  		return isFlat(this._latlngs[0]) ? this._latlngs[0] : this._latlngs[0][0];
  	},

  	_clipPoints: function () {
  		// polygons need a different clipping algorithm so we redefine that

  		var bounds = this._renderer._bounds,
  		    w = this.options.weight,
  		    p = new Point(w, w);

  		// increase clip padding by stroke width to avoid stroke on clip edges
  		bounds = new Bounds(bounds.min.subtract(p), bounds.max.add(p));

  		this._parts = [];
  		if (!this._pxBounds || !this._pxBounds.intersects(bounds)) {
  			return;
  		}

  		if (this.options.noClip) {
  			this._parts = this._rings;
  			return;
  		}

  		for (var i = 0, len = this._rings.length, clipped; i < len; i++) {
  			clipped = clipPolygon(this._rings[i], bounds, true);
  			if (clipped.length) {
  				this._parts.push(clipped);
  			}
  		}
  	},

  	_updatePath: function () {
  		this._renderer._updatePoly(this, true);
  	},

  	// Needed by the `Canvas` renderer for interactivity
  	_containsPoint: function (p) {
  		var inside = false,
  		    part, p1, p2, i, j, k, len, len2;

  		if (!this._pxBounds || !this._pxBounds.contains(p)) { return false; }

  		// ray casting algorithm for detecting if point is in polygon
  		for (i = 0, len = this._parts.length; i < len; i++) {
  			part = this._parts[i];

  			for (j = 0, len2 = part.length, k = len2 - 1; j < len2; k = j++) {
  				p1 = part[j];
  				p2 = part[k];

  				if (((p1.y > p.y) !== (p2.y > p.y)) && (p.x < (p2.x - p1.x) * (p.y - p1.y) / (p2.y - p1.y) + p1.x)) {
  					inside = !inside;
  				}
  			}
  		}

  		// also check if it's on polygon stroke
  		return inside || Polyline.prototype._containsPoint.call(this, p, true);
  	}

  });


  // @factory L.polygon(latlngs: LatLng[], options?: Polyline options)
  function polygon(latlngs, options) {
  	return new Polygon(latlngs, options);
  }

  /*
   * @class GeoJSON
   * @aka L.GeoJSON
   * @inherits FeatureGroup
   *
   * Represents a GeoJSON object or an array of GeoJSON objects. Allows you to parse
   * GeoJSON data and display it on the map. Extends `FeatureGroup`.
   *
   * @example
   *
   * ```js
   * L.geoJSON(data, {
   * 	style: function (feature) {
   * 		return {color: feature.properties.color};
   * 	}
   * }).bindPopup(function (layer) {
   * 	return layer.feature.properties.description;
   * }).addTo(map);
   * ```
   */

  var GeoJSON = FeatureGroup.extend({

  	/* @section
  	 * @aka GeoJSON options
  	 *
  	 * @option pointToLayer: Function = *
  	 * A `Function` defining how GeoJSON points spawn Leaflet layers. It is internally
  	 * called when data is added, passing the GeoJSON point feature and its `LatLng`.
  	 * The default is to spawn a default `Marker`:
  	 * ```js
  	 * function(geoJsonPoint, latlng) {
  	 * 	return L.marker(latlng);
  	 * }
  	 * ```
  	 *
  	 * @option style: Function = *
  	 * A `Function` defining the `Path options` for styling GeoJSON lines and polygons,
  	 * called internally when data is added.
  	 * The default value is to not override any defaults:
  	 * ```js
  	 * function (geoJsonFeature) {
  	 * 	return {}
  	 * }
  	 * ```
  	 *
  	 * @option onEachFeature: Function = *
  	 * A `Function` that will be called once for each created `Feature`, after it has
  	 * been created and styled. Useful for attaching events and popups to features.
  	 * The default is to do nothing with the newly created layers:
  	 * ```js
  	 * function (feature, layer) {}
  	 * ```
  	 *
  	 * @option filter: Function = *
  	 * A `Function` that will be used to decide whether to include a feature or not.
  	 * The default is to include all features:
  	 * ```js
  	 * function (geoJsonFeature) {
  	 * 	return true;
  	 * }
  	 * ```
  	 * Note: dynamically changing the `filter` option will have effect only on newly
  	 * added data. It will _not_ re-evaluate already included features.
  	 *
  	 * @option coordsToLatLng: Function = *
  	 * A `Function` that will be used for converting GeoJSON coordinates to `LatLng`s.
  	 * The default is the `coordsToLatLng` static method.
  	 *
  	 * @option markersInheritOptions: Boolean = false
  	 * Whether default Markers for "Point" type Features inherit from group options.
  	 */

  	initialize: function (geojson, options) {
  		setOptions(this, options);

  		this._layers = {};

  		if (geojson) {
  			this.addData(geojson);
  		}
  	},

  	// @method addData( <GeoJSON> data ): this
  	// Adds a GeoJSON object to the layer.
  	addData: function (geojson) {
  		var features = isArray(geojson) ? geojson : geojson.features,
  		    i, len, feature;

  		if (features) {
  			for (i = 0, len = features.length; i < len; i++) {
  				// only add this if geometry or geometries are set and not null
  				feature = features[i];
  				if (feature.geometries || feature.geometry || feature.features || feature.coordinates) {
  					this.addData(feature);
  				}
  			}
  			return this;
  		}

  		var options = this.options;

  		if (options.filter && !options.filter(geojson)) { return this; }

  		var layer = geometryToLayer(geojson, options);
  		if (!layer) {
  			return this;
  		}
  		layer.feature = asFeature(geojson);

  		layer.defaultOptions = layer.options;
  		this.resetStyle(layer);

  		if (options.onEachFeature) {
  			options.onEachFeature(geojson, layer);
  		}

  		return this.addLayer(layer);
  	},

  	// @method resetStyle( <Path> layer? ): this
  	// Resets the given vector layer's style to the original GeoJSON style, useful for resetting style after hover events.
  	// If `layer` is omitted, the style of all features in the current layer is reset.
  	resetStyle: function (layer) {
  		if (layer === undefined) {
  			return this.eachLayer(this.resetStyle, this);
  		}
  		// reset any custom styles
  		layer.options = extend({}, layer.defaultOptions);
  		this._setLayerStyle(layer, this.options.style);
  		return this;
  	},

  	// @method setStyle( <Function> style ): this
  	// Changes styles of GeoJSON vector layers with the given style function.
  	setStyle: function (style) {
  		return this.eachLayer(function (layer) {
  			this._setLayerStyle(layer, style);
  		}, this);
  	},

  	_setLayerStyle: function (layer, style) {
  		if (layer.setStyle) {
  			if (typeof style === 'function') {
  				style = style(layer.feature);
  			}
  			layer.setStyle(style);
  		}
  	}
  });

  // @section
  // There are several static functions which can be called without instantiating L.GeoJSON:

  // @function geometryToLayer(featureData: Object, options?: GeoJSON options): Layer
  // Creates a `Layer` from a given GeoJSON feature. Can use a custom
  // [`pointToLayer`](#geojson-pointtolayer) and/or [`coordsToLatLng`](#geojson-coordstolatlng)
  // functions if provided as options.
  function geometryToLayer(geojson, options) {

  	var geometry = geojson.type === 'Feature' ? geojson.geometry : geojson,
  	    coords = geometry ? geometry.coordinates : null,
  	    layers = [],
  	    pointToLayer = options && options.pointToLayer,
  	    _coordsToLatLng = options && options.coordsToLatLng || coordsToLatLng,
  	    latlng, latlngs, i, len;

  	if (!coords && !geometry) {
  		return null;
  	}

  	switch (geometry.type) {
  	case 'Point':
  		latlng = _coordsToLatLng(coords);
  		return _pointToLayer(pointToLayer, geojson, latlng, options);

  	case 'MultiPoint':
  		for (i = 0, len = coords.length; i < len; i++) {
  			latlng = _coordsToLatLng(coords[i]);
  			layers.push(_pointToLayer(pointToLayer, geojson, latlng, options));
  		}
  		return new FeatureGroup(layers);

  	case 'LineString':
  	case 'MultiLineString':
  		latlngs = coordsToLatLngs(coords, geometry.type === 'LineString' ? 0 : 1, _coordsToLatLng);
  		return new Polyline(latlngs, options);

  	case 'Polygon':
  	case 'MultiPolygon':
  		latlngs = coordsToLatLngs(coords, geometry.type === 'Polygon' ? 1 : 2, _coordsToLatLng);
  		return new Polygon(latlngs, options);

  	case 'GeometryCollection':
  		for (i = 0, len = geometry.geometries.length; i < len; i++) {
  			var geoLayer = geometryToLayer({
  				geometry: geometry.geometries[i],
  				type: 'Feature',
  				properties: geojson.properties
  			}, options);

  			if (geoLayer) {
  				layers.push(geoLayer);
  			}
  		}
  		return new FeatureGroup(layers);

  	case 'FeatureCollection':
  		for (i = 0, len = geometry.features.length; i < len; i++) {
  			var featureLayer = geometryToLayer(geometry.features[i], options);

  			if (featureLayer) {
  				layers.push(featureLayer);
  			}
  		}
  		return new FeatureGroup(layers);

  	default:
  		throw new Error('Invalid GeoJSON object.');
  	}
  }

  function _pointToLayer(pointToLayerFn, geojson, latlng, options) {
  	return pointToLayerFn ?
  		pointToLayerFn(geojson, latlng) :
  		new Marker(latlng, options && options.markersInheritOptions && options);
  }

  // @function coordsToLatLng(coords: Array): LatLng
  // Creates a `LatLng` object from an array of 2 numbers (longitude, latitude)
  // or 3 numbers (longitude, latitude, altitude) used in GeoJSON for points.
  function coordsToLatLng(coords) {
  	return new LatLng(coords[1], coords[0], coords[2]);
  }

  // @function coordsToLatLngs(coords: Array, levelsDeep?: Number, coordsToLatLng?: Function): Array
  // Creates a multidimensional array of `LatLng`s from a GeoJSON coordinates array.
  // `levelsDeep` specifies the nesting level (0 is for an array of points, 1 for an array of arrays of points, etc., 0 by default).
  // Can use a custom [`coordsToLatLng`](#geojson-coordstolatlng) function.
  function coordsToLatLngs(coords, levelsDeep, _coordsToLatLng) {
  	var latlngs = [];

  	for (var i = 0, len = coords.length, latlng; i < len; i++) {
  		latlng = levelsDeep ?
  			coordsToLatLngs(coords[i], levelsDeep - 1, _coordsToLatLng) :
  			(_coordsToLatLng || coordsToLatLng)(coords[i]);

  		latlngs.push(latlng);
  	}

  	return latlngs;
  }

  // @function latLngToCoords(latlng: LatLng, precision?: Number|false): Array
  // Reverse of [`coordsToLatLng`](#geojson-coordstolatlng)
  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
  function latLngToCoords(latlng, precision) {
  	latlng = toLatLng(latlng);
  	return latlng.alt !== undefined ?
  		[formatNum(latlng.lng, precision), formatNum(latlng.lat, precision), formatNum(latlng.alt, precision)] :
  		[formatNum(latlng.lng, precision), formatNum(latlng.lat, precision)];
  }

  // @function latLngsToCoords(latlngs: Array, levelsDeep?: Number, closed?: Boolean, precision?: Number|false): Array
  // Reverse of [`coordsToLatLngs`](#geojson-coordstolatlngs)
  // `closed` determines whether the first point should be appended to the end of the array to close the feature, only used when `levelsDeep` is 0. False by default.
  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function.
  function latLngsToCoords(latlngs, levelsDeep, closed, precision) {
  	var coords = [];

  	for (var i = 0, len = latlngs.length; i < len; i++) {
  		// Check for flat arrays required to ensure unbalanced arrays are correctly converted in recursion
  		coords.push(levelsDeep ?
  			latLngsToCoords(latlngs[i], isFlat(latlngs[i]) ? 0 : levelsDeep - 1, closed, precision) :
  			latLngToCoords(latlngs[i], precision));
  	}

  	if (!levelsDeep && closed && coords.length > 0) {
  		coords.push(coords[0].slice());
  	}

  	return coords;
  }

  function getFeature(layer, newGeometry) {
  	return layer.feature ?
  		extend({}, layer.feature, {geometry: newGeometry}) :
  		asFeature(newGeometry);
  }

  // @function asFeature(geojson: Object): Object
  // Normalize GeoJSON geometries/features into GeoJSON features.
  function asFeature(geojson) {
  	if (geojson.type === 'Feature' || geojson.type === 'FeatureCollection') {
  		return geojson;
  	}

  	return {
  		type: 'Feature',
  		properties: {},
  		geometry: geojson
  	};
  }

  var PointToGeoJSON = {
  	toGeoJSON: function (precision) {
  		return getFeature(this, {
  			type: 'Point',
  			coordinates: latLngToCoords(this.getLatLng(), precision)
  		});
  	}
  };

  // @namespace Marker
  // @section Other methods
  // @method toGeoJSON(precision?: Number|false): Object
  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
  // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the marker (as a GeoJSON `Point` Feature).
  Marker.include(PointToGeoJSON);

  // @namespace CircleMarker
  // @method toGeoJSON(precision?: Number|false): Object
  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
  // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the circle marker (as a GeoJSON `Point` Feature).
  Circle.include(PointToGeoJSON);
  CircleMarker.include(PointToGeoJSON);


  // @namespace Polyline
  // @method toGeoJSON(precision?: Number|false): Object
  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
  // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polyline (as a GeoJSON `LineString` or `MultiLineString` Feature).
  Polyline.include({
  	toGeoJSON: function (precision) {
  		var multi = !isFlat(this._latlngs);

  		var coords = latLngsToCoords(this._latlngs, multi ? 1 : 0, false, precision);

  		return getFeature(this, {
  			type: (multi ? 'Multi' : '') + 'LineString',
  			coordinates: coords
  		});
  	}
  });

  // @namespace Polygon
  // @method toGeoJSON(precision?: Number|false): Object
  // Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
  // Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the polygon (as a GeoJSON `Polygon` or `MultiPolygon` Feature).
  Polygon.include({
  	toGeoJSON: function (precision) {
  		var holes = !isFlat(this._latlngs),
  		    multi = holes && !isFlat(this._latlngs[0]);

  		var coords = latLngsToCoords(this._latlngs, multi ? 2 : holes ? 1 : 0, true, precision);

  		if (!holes) {
  			coords = [coords];
  		}

  		return getFeature(this, {
  			type: (multi ? 'Multi' : '') + 'Polygon',
  			coordinates: coords
  		});
  	}
  });


  // @namespace LayerGroup
  LayerGroup.include({
  	toMultiPoint: function (precision) {
  		var coords = [];

  		this.eachLayer(function (layer) {
  			coords.push(layer.toGeoJSON(precision).geometry.coordinates);
  		});

  		return getFeature(this, {
  			type: 'MultiPoint',
  			coordinates: coords
  		});
  	},

  	// @method toGeoJSON(precision?: Number|false): Object
  	// Coordinates values are rounded with [`formatNum`](#util-formatnum) function with given `precision`.
  	// Returns a [`GeoJSON`](https://en.wikipedia.org/wiki/GeoJSON) representation of the layer group (as a GeoJSON `FeatureCollection`, `GeometryCollection`, or `MultiPoint`).
  	toGeoJSON: function (precision) {

  		var type = this.feature && this.feature.geometry && this.feature.geometry.type;

  		if (type === 'MultiPoint') {
  			return this.toMultiPoint(precision);
  		}

  		var isGeometryCollection = type === 'GeometryCollection',
  		    jsons = [];

  		this.eachLayer(function (layer) {
  			if (layer.toGeoJSON) {
  				var json = layer.toGeoJSON(precision);
  				if (isGeometryCollection) {
  					jsons.push(json.geometry);
  				} else {
  					var feature = asFeature(json);
  					// Squash nested feature collections
  					if (feature.type === 'FeatureCollection') {
  						jsons.push.apply(jsons, feature.features);
  					} else {
  						jsons.push(feature);
  					}
  				}
  			}
  		});

  		if (isGeometryCollection) {
  			return getFeature(this, {
  				geometries: jsons,
  				type: 'GeometryCollection'
  			});
  		}

  		return {
  			type: 'FeatureCollection',
  			features: jsons
  		};
  	}
  });

  // @namespace GeoJSON
  // @factory L.geoJSON(geojson?: Object, options?: GeoJSON options)
  // Creates a GeoJSON layer. Optionally accepts an object in
  // [GeoJSON format](https://tools.ietf.org/html/rfc7946) to display on the map
  // (you can alternatively add it later with `addData` method) and an `options` object.
  function geoJSON(geojson, options) {
  	return new GeoJSON(geojson, options);
  }

  // Backward compatibility.
  var geoJson = geoJSON;

  /*
   * @class ImageOverlay
   * @aka L.ImageOverlay
   * @inherits Interactive layer
   *
   * Used to load and display a single image over specific bounds of the map. Extends `Layer`.
   *
   * @example
   *
   * ```js
   * var imageUrl = 'https://maps.lib.utexas.edu/maps/historical/newark_nj_1922.jpg',
   * 	imageBounds = [[40.712216, -74.22655], [40.773941, -74.12544]];
   * L.imageOverlay(imageUrl, imageBounds).addTo(map);
   * ```
   */

  var ImageOverlay = Layer.extend({

  	// @section
  	// @aka ImageOverlay options
  	options: {
  		// @option opacity: Number = 1.0
  		// The opacity of the image overlay.
  		opacity: 1,

  		// @option alt: String = ''
  		// Text for the `alt` attribute of the image (useful for accessibility).
  		alt: '',

  		// @option interactive: Boolean = false
  		// If `true`, the image overlay will emit [mouse events](#interactive-layer) when clicked or hovered.
  		interactive: false,

  		// @option crossOrigin: Boolean|String = false
  		// Whether the crossOrigin attribute will be added to the image.
  		// If a String is provided, the image will have its crossOrigin attribute set to the String provided. This is needed if you want to access image pixel data.
  		// Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
  		crossOrigin: false,

  		// @option errorOverlayUrl: String = ''
  		// URL to the overlay image to show in place of the overlay that failed to load.
  		errorOverlayUrl: '',

  		// @option zIndex: Number = 1
  		// The explicit [zIndex](https://developer.mozilla.org/docs/Web/CSS/CSS_Positioning/Understanding_z_index) of the overlay layer.
  		zIndex: 1,

  		// @option className: String = ''
  		// A custom class name to assign to the image. Empty by default.
  		className: ''
  	},

  	initialize: function (url, bounds, options) { // (String, LatLngBounds, Object)
  		this._url = url;
  		this._bounds = toLatLngBounds(bounds);

  		setOptions(this, options);
  	},

  	onAdd: function () {
  		if (!this._image) {
  			this._initImage();

  			if (this.options.opacity < 1) {
  				this._updateOpacity();
  			}
  		}

  		if (this.options.interactive) {
  			addClass(this._image, 'leaflet-interactive');
  			this.addInteractiveTarget(this._image);
  		}

  		this.getPane().appendChild(this._image);
  		this._reset();
  	},

  	onRemove: function () {
  		remove(this._image);
  		if (this.options.interactive) {
  			this.removeInteractiveTarget(this._image);
  		}
  	},

  	// @method setOpacity(opacity: Number): this
  	// Sets the opacity of the overlay.
  	setOpacity: function (opacity) {
  		this.options.opacity = opacity;

  		if (this._image) {
  			this._updateOpacity();
  		}
  		return this;
  	},

  	setStyle: function (styleOpts) {
  		if (styleOpts.opacity) {
  			this.setOpacity(styleOpts.opacity);
  		}
  		return this;
  	},

  	// @method bringToFront(): this
  	// Brings the layer to the top of all overlays.
  	bringToFront: function () {
  		if (this._map) {
  			toFront(this._image);
  		}
  		return this;
  	},

  	// @method bringToBack(): this
  	// Brings the layer to the bottom of all overlays.
  	bringToBack: function () {
  		if (this._map) {
  			toBack(this._image);
  		}
  		return this;
  	},

  	// @method setUrl(url: String): this
  	// Changes the URL of the image.
  	setUrl: function (url) {
  		this._url = url;

  		if (this._image) {
  			this._image.src = url;
  		}
  		return this;
  	},

  	// @method setBounds(bounds: LatLngBounds): this
  	// Update the bounds that this ImageOverlay covers
  	setBounds: function (bounds) {
  		this._bounds = toLatLngBounds(bounds);

  		if (this._map) {
  			this._reset();
  		}
  		return this;
  	},

  	getEvents: function () {
  		var events = {
  			zoom: this._reset,
  			viewreset: this._reset
  		};

  		if (this._zoomAnimated) {
  			events.zoomanim = this._animateZoom;
  		}

  		return events;
  	},

  	// @method setZIndex(value: Number): this
  	// Changes the [zIndex](#imageoverlay-zindex) of the image overlay.
  	setZIndex: function (value) {
  		this.options.zIndex = value;
  		this._updateZIndex();
  		return this;
  	},

  	// @method getBounds(): LatLngBounds
  	// Get the bounds that this ImageOverlay covers
  	getBounds: function () {
  		return this._bounds;
  	},

  	// @method getElement(): HTMLElement
  	// Returns the instance of [`HTMLImageElement`](https://developer.mozilla.org/docs/Web/API/HTMLImageElement)
  	// used by this overlay.
  	getElement: function () {
  		return this._image;
  	},

  	_initImage: function () {
  		var wasElementSupplied = this._url.tagName === 'IMG';
  		var img = this._image = wasElementSupplied ? this._url : create$1('img');

  		addClass(img, 'leaflet-image-layer');
  		if (this._zoomAnimated) { addClass(img, 'leaflet-zoom-animated'); }
  		if (this.options.className) { addClass(img, this.options.className); }

  		img.onselectstart = falseFn;
  		img.onmousemove = falseFn;

  		// @event load: Event
  		// Fired when the ImageOverlay layer has loaded its image
  		img.onload = bind(this.fire, this, 'load');
  		img.onerror = bind(this._overlayOnError, this, 'error');

  		if (this.options.crossOrigin || this.options.crossOrigin === '') {
  			img.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
  		}

  		if (this.options.zIndex) {
  			this._updateZIndex();
  		}

  		if (wasElementSupplied) {
  			this._url = img.src;
  			return;
  		}

  		img.src = this._url;
  		img.alt = this.options.alt;
  	},

  	_animateZoom: function (e) {
  		var scale = this._map.getZoomScale(e.zoom),
  		    offset = this._map._latLngBoundsToNewLayerBounds(this._bounds, e.zoom, e.center).min;

  		setTransform(this._image, offset, scale);
  	},

  	_reset: function () {
  		var image = this._image,
  		    bounds = new Bounds(
  		        this._map.latLngToLayerPoint(this._bounds.getNorthWest()),
  		        this._map.latLngToLayerPoint(this._bounds.getSouthEast())),
  		    size = bounds.getSize();

  		setPosition(image, bounds.min);

  		image.style.width  = size.x + 'px';
  		image.style.height = size.y + 'px';
  	},

  	_updateOpacity: function () {
  		setOpacity(this._image, this.options.opacity);
  	},

  	_updateZIndex: function () {
  		if (this._image && this.options.zIndex !== undefined && this.options.zIndex !== null) {
  			this._image.style.zIndex = this.options.zIndex;
  		}
  	},

  	_overlayOnError: function () {
  		// @event error: Event
  		// Fired when the ImageOverlay layer fails to load its image
  		this.fire('error');

  		var errorUrl = this.options.errorOverlayUrl;
  		if (errorUrl && this._url !== errorUrl) {
  			this._url = errorUrl;
  			this._image.src = errorUrl;
  		}
  	},

  	// @method getCenter(): LatLng
  	// Returns the center of the ImageOverlay.
  	getCenter: function () {
  		return this._bounds.getCenter();
  	}
  });

  // @factory L.imageOverlay(imageUrl: String, bounds: LatLngBounds, options?: ImageOverlay options)
  // Instantiates an image overlay object given the URL of the image and the
  // geographical bounds it is tied to.
  var imageOverlay = function (url, bounds, options) {
  	return new ImageOverlay(url, bounds, options);
  };

  /*
   * @class VideoOverlay
   * @aka L.VideoOverlay
   * @inherits ImageOverlay
   *
   * Used to load and display a video player over specific bounds of the map. Extends `ImageOverlay`.
   *
   * A video overlay uses the [`<video>`](https://developer.mozilla.org/docs/Web/HTML/Element/video)
   * HTML5 element.
   *
   * @example
   *
   * ```js
   * var videoUrl = 'https://www.mapbox.com/bites/00188/patricia_nasa.webm',
   * 	videoBounds = [[ 32, -130], [ 13, -100]];
   * L.videoOverlay(videoUrl, videoBounds ).addTo(map);
   * ```
   */

  var VideoOverlay = ImageOverlay.extend({

  	// @section
  	// @aka VideoOverlay options
  	options: {
  		// @option autoplay: Boolean = true
  		// Whether the video starts playing automatically when loaded.
  		// On some browsers autoplay will only work with `muted: true`
  		autoplay: true,

  		// @option loop: Boolean = true
  		// Whether the video will loop back to the beginning when played.
  		loop: true,

  		// @option keepAspectRatio: Boolean = true
  		// Whether the video will save aspect ratio after the projection.
  		// Relevant for supported browsers. See [browser compatibility](https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit)
  		keepAspectRatio: true,

  		// @option muted: Boolean = false
  		// Whether the video starts on mute when loaded.
  		muted: false,

  		// @option playsInline: Boolean = true
  		// Mobile browsers will play the video right where it is instead of open it up in fullscreen mode.
  		playsInline: true
  	},

  	_initImage: function () {
  		var wasElementSupplied = this._url.tagName === 'VIDEO';
  		var vid = this._image = wasElementSupplied ? this._url : create$1('video');

  		addClass(vid, 'leaflet-image-layer');
  		if (this._zoomAnimated) { addClass(vid, 'leaflet-zoom-animated'); }
  		if (this.options.className) { addClass(vid, this.options.className); }

  		vid.onselectstart = falseFn;
  		vid.onmousemove = falseFn;

  		// @event load: Event
  		// Fired when the video has finished loading the first frame
  		vid.onloadeddata = bind(this.fire, this, 'load');

  		if (wasElementSupplied) {
  			var sourceElements = vid.getElementsByTagName('source');
  			var sources = [];
  			for (var j = 0; j < sourceElements.length; j++) {
  				sources.push(sourceElements[j].src);
  			}

  			this._url = (sourceElements.length > 0) ? sources : [vid.src];
  			return;
  		}

  		if (!isArray(this._url)) { this._url = [this._url]; }

  		if (!this.options.keepAspectRatio && Object.prototype.hasOwnProperty.call(vid.style, 'objectFit')) {
  			vid.style['objectFit'] = 'fill';
  		}
  		vid.autoplay = !!this.options.autoplay;
  		vid.loop = !!this.options.loop;
  		vid.muted = !!this.options.muted;
  		vid.playsInline = !!this.options.playsInline;
  		for (var i = 0; i < this._url.length; i++) {
  			var source = create$1('source');
  			source.src = this._url[i];
  			vid.appendChild(source);
  		}
  	}

  	// @method getElement(): HTMLVideoElement
  	// Returns the instance of [`HTMLVideoElement`](https://developer.mozilla.org/docs/Web/API/HTMLVideoElement)
  	// used by this overlay.
  });


  // @factory L.videoOverlay(video: String|Array|HTMLVideoElement, bounds: LatLngBounds, options?: VideoOverlay options)
  // Instantiates an image overlay object given the URL of the video (or array of URLs, or even a video element) and the
  // geographical bounds it is tied to.

  function videoOverlay(video, bounds, options) {
  	return new VideoOverlay(video, bounds, options);
  }

  /*
   * @class SVGOverlay
   * @aka L.SVGOverlay
   * @inherits ImageOverlay
   *
   * Used to load, display and provide DOM access to an SVG file over specific bounds of the map. Extends `ImageOverlay`.
   *
   * An SVG overlay uses the [`<svg>`](https://developer.mozilla.org/docs/Web/SVG/Element/svg) element.
   *
   * @example
   *
   * ```js
   * var svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg");
   * svgElement.setAttribute('xmlns', "http://www.w3.org/2000/svg");
   * svgElement.setAttribute('viewBox', "0 0 200 200");
   * svgElement.innerHTML = '<rect width="200" height="200"/><rect x="75" y="23" width="50" height="50" style="fill:red"/><rect x="75" y="123" width="50" height="50" style="fill:#0013ff"/>';
   * var svgElementBounds = [ [ 32, -130 ], [ 13, -100 ] ];
   * L.svgOverlay(svgElement, svgElementBounds).addTo(map);
   * ```
   */

  var SVGOverlay = ImageOverlay.extend({
  	_initImage: function () {
  		var el = this._image = this._url;

  		addClass(el, 'leaflet-image-layer');
  		if (this._zoomAnimated) { addClass(el, 'leaflet-zoom-animated'); }
  		if (this.options.className) { addClass(el, this.options.className); }

  		el.onselectstart = falseFn;
  		el.onmousemove = falseFn;
  	}

  	// @method getElement(): SVGElement
  	// Returns the instance of [`SVGElement`](https://developer.mozilla.org/docs/Web/API/SVGElement)
  	// used by this overlay.
  });


  // @factory L.svgOverlay(svg: String|SVGElement, bounds: LatLngBounds, options?: SVGOverlay options)
  // Instantiates an image overlay object given an SVG element and the geographical bounds it is tied to.
  // A viewBox attribute is required on the SVG element to zoom in and out properly.

  function svgOverlay(el, bounds, options) {
  	return new SVGOverlay(el, bounds, options);
  }

  /*
   * @class DivOverlay
   * @inherits Interactive layer
   * @aka L.DivOverlay
   * Base model for L.Popup and L.Tooltip. Inherit from it for custom overlays like plugins.
   */

  // @namespace DivOverlay
  var DivOverlay = Layer.extend({

  	// @section
  	// @aka DivOverlay options
  	options: {
  		// @option interactive: Boolean = false
  		// If true, the popup/tooltip will listen to the mouse events.
  		interactive: false,

  		// @option offset: Point = Point(0, 0)
  		// The offset of the overlay position.
  		offset: [0, 0],

  		// @option className: String = ''
  		// A custom CSS class name to assign to the overlay.
  		className: '',

  		// @option pane: String = undefined
  		// `Map pane` where the overlay will be added.
  		pane: undefined,

  		// @option content: String|HTMLElement|Function = ''
  		// Sets the HTML content of the overlay while initializing. If a function is passed the source layer will be
  		// passed to the function. The function should return a `String` or `HTMLElement` to be used in the overlay.
  		content: ''
  	},

  	initialize: function (options, source) {
  		if (options && (options instanceof LatLng || isArray(options))) {
  			this._latlng = toLatLng(options);
  			setOptions(this, source);
  		} else {
  			setOptions(this, options);
  			this._source = source;
  		}
  		if (this.options.content) {
  			this._content = this.options.content;
  		}
  	},

  	// @method openOn(map: Map): this
  	// Adds the overlay to the map.
  	// Alternative to `map.openPopup(popup)`/`.openTooltip(tooltip)`.
  	openOn: function (map) {
  		map = arguments.length ? map : this._source._map; // experimental, not the part of public api
  		if (!map.hasLayer(this)) {
  			map.addLayer(this);
  		}
  		return this;
  	},

  	// @method close(): this
  	// Closes the overlay.
  	// Alternative to `map.closePopup(popup)`/`.closeTooltip(tooltip)`
  	// and `layer.closePopup()`/`.closeTooltip()`.
  	close: function () {
  		if (this._map) {
  			this._map.removeLayer(this);
  		}
  		return this;
  	},

  	// @method toggle(layer?: Layer): this
  	// Opens or closes the overlay bound to layer depending on its current state.
  	// Argument may be omitted only for overlay bound to layer.
  	// Alternative to `layer.togglePopup()`/`.toggleTooltip()`.
  	toggle: function (layer) {
  		if (this._map) {
  			this.close();
  		} else {
  			if (arguments.length) {
  				this._source = layer;
  			} else {
  				layer = this._source;
  			}
  			this._prepareOpen();

  			// open the overlay on the map
  			this.openOn(layer._map);
  		}
  		return this;
  	},

  	onAdd: function (map) {
  		this._zoomAnimated = map._zoomAnimated;

  		if (!this._container) {
  			this._initLayout();
  		}

  		if (map._fadeAnimated) {
  			setOpacity(this._container, 0);
  		}

  		clearTimeout(this._removeTimeout);
  		this.getPane().appendChild(this._container);
  		this.update();

  		if (map._fadeAnimated) {
  			setOpacity(this._container, 1);
  		}

  		this.bringToFront();

  		if (this.options.interactive) {
  			addClass(this._container, 'leaflet-interactive');
  			this.addInteractiveTarget(this._container);
  		}
  	},

  	onRemove: function (map) {
  		if (map._fadeAnimated) {
  			setOpacity(this._container, 0);
  			this._removeTimeout = setTimeout(bind(remove, undefined, this._container), 200);
  		} else {
  			remove(this._container);
  		}

  		if (this.options.interactive) {
  			removeClass(this._container, 'leaflet-interactive');
  			this.removeInteractiveTarget(this._container);
  		}
  	},

  	// @namespace DivOverlay
  	// @method getLatLng: LatLng
  	// Returns the geographical point of the overlay.
  	getLatLng: function () {
  		return this._latlng;
  	},

  	// @method setLatLng(latlng: LatLng): this
  	// Sets the geographical point where the overlay will open.
  	setLatLng: function (latlng) {
  		this._latlng = toLatLng(latlng);
  		if (this._map) {
  			this._updatePosition();
  			this._adjustPan();
  		}
  		return this;
  	},

  	// @method getContent: String|HTMLElement
  	// Returns the content of the overlay.
  	getContent: function () {
  		return this._content;
  	},

  	// @method setContent(htmlContent: String|HTMLElement|Function): this
  	// Sets the HTML content of the overlay. If a function is passed the source layer will be passed to the function.
  	// The function should return a `String` or `HTMLElement` to be used in the overlay.
  	setContent: function (content) {
  		this._content = content;
  		this.update();
  		return this;
  	},

  	// @method getElement: String|HTMLElement
  	// Returns the HTML container of the overlay.
  	getElement: function () {
  		return this._container;
  	},

  	// @method update: null
  	// Updates the overlay content, layout and position. Useful for updating the overlay after something inside changed, e.g. image loaded.
  	update: function () {
  		if (!this._map) { return; }

  		this._container.style.visibility = 'hidden';

  		this._updateContent();
  		this._updateLayout();
  		this._updatePosition();

  		this._container.style.visibility = '';

  		this._adjustPan();
  	},

  	getEvents: function () {
  		var events = {
  			zoom: this._updatePosition,
  			viewreset: this._updatePosition
  		};

  		if (this._zoomAnimated) {
  			events.zoomanim = this._animateZoom;
  		}
  		return events;
  	},

  	// @method isOpen: Boolean
  	// Returns `true` when the overlay is visible on the map.
  	isOpen: function () {
  		return !!this._map && this._map.hasLayer(this);
  	},

  	// @method bringToFront: this
  	// Brings this overlay in front of other overlays (in the same map pane).
  	bringToFront: function () {
  		if (this._map) {
  			toFront(this._container);
  		}
  		return this;
  	},

  	// @method bringToBack: this
  	// Brings this overlay to the back of other overlays (in the same map pane).
  	bringToBack: function () {
  		if (this._map) {
  			toBack(this._container);
  		}
  		return this;
  	},

  	// prepare bound overlay to open: update latlng pos / content source (for FeatureGroup)
  	_prepareOpen: function (latlng) {
  		var source = this._source;
  		if (!source._map) { return false; }

  		if (source instanceof FeatureGroup) {
  			source = null;
  			var layers = this._source._layers;
  			for (var id in layers) {
  				if (layers[id]._map) {
  					source = layers[id];
  					break;
  				}
  			}
  			if (!source) { return false; } // Unable to get source layer.

  			// set overlay source to this layer
  			this._source = source;
  		}

  		if (!latlng) {
  			if (source.getCenter) {
  				latlng = source.getCenter();
  			} else if (source.getLatLng) {
  				latlng = source.getLatLng();
  			} else if (source.getBounds) {
  				latlng = source.getBounds().getCenter();
  			} else {
  				throw new Error('Unable to get source layer LatLng.');
  			}
  		}
  		this.setLatLng(latlng);

  		if (this._map) {
  			// update the overlay (content, layout, etc...)
  			this.update();
  		}

  		return true;
  	},

  	_updateContent: function () {
  		if (!this._content) { return; }

  		var node = this._contentNode;
  		var content = (typeof this._content === 'function') ? this._content(this._source || this) : this._content;

  		if (typeof content === 'string') {
  			node.innerHTML = content;
  		} else {
  			while (node.hasChildNodes()) {
  				node.removeChild(node.firstChild);
  			}
  			node.appendChild(content);
  		}

  		// @namespace DivOverlay
  		// @section DivOverlay events
  		// @event contentupdate: Event
  		// Fired when the content of the overlay is updated
  		this.fire('contentupdate');
  	},

  	_updatePosition: function () {
  		if (!this._map) { return; }

  		var pos = this._map.latLngToLayerPoint(this._latlng),
  		    offset = toPoint(this.options.offset),
  		    anchor = this._getAnchor();

  		if (this._zoomAnimated) {
  			setPosition(this._container, pos.add(anchor));
  		} else {
  			offset = offset.add(pos).add(anchor);
  		}

  		var bottom = this._containerBottom = -offset.y,
  		    left = this._containerLeft = -Math.round(this._containerWidth / 2) + offset.x;

  		// bottom position the overlay in case the height of the overlay changes (images loading etc)
  		this._container.style.bottom = bottom + 'px';
  		this._container.style.left = left + 'px';
  	},

  	_getAnchor: function () {
  		return [0, 0];
  	}

  });

  Map.include({
  	_initOverlay: function (OverlayClass, content, latlng, options) {
  		var overlay = content;
  		if (!(overlay instanceof OverlayClass)) {
  			overlay = new OverlayClass(options).setContent(content);
  		}
  		if (latlng) {
  			overlay.setLatLng(latlng);
  		}
  		return overlay;
  	}
  });


  Layer.include({
  	_initOverlay: function (OverlayClass, old, content, options) {
  		var overlay = content;
  		if (overlay instanceof OverlayClass) {
  			setOptions(overlay, options);
  			overlay._source = this;
  		} else {
  			overlay = (old && !options) ? old : new OverlayClass(options, this);
  			overlay.setContent(content);
  		}
  		return overlay;
  	}
  });

  /*
   * @class Popup
   * @inherits DivOverlay
   * @aka L.Popup
   * Used to open popups in certain places of the map. Use [Map.openPopup](#map-openpopup) to
   * open popups while making sure that only one popup is open at one time
   * (recommended for usability), or use [Map.addLayer](#map-addlayer) to open as many as you want.
   *
   * @example
   *
   * If you want to just bind a popup to marker click and then open it, it's really easy:
   *
   * ```js
   * marker.bindPopup(popupContent).openPopup();
   * ```
   * Path overlays like polylines also have a `bindPopup` method.
   *
   * A popup can be also standalone:
   *
   * ```js
   * var popup = L.popup()
   * 	.setLatLng(latlng)
   * 	.setContent('<p>Hello world!<br />This is a nice popup.</p>')
   * 	.openOn(map);
   * ```
   * or
   * ```js
   * var popup = L.popup(latlng, {content: '<p>Hello world!<br />This is a nice popup.</p>')
   * 	.openOn(map);
   * ```
   */


  // @namespace Popup
  var Popup = DivOverlay.extend({

  	// @section
  	// @aka Popup options
  	options: {
  		// @option pane: String = 'popupPane'
  		// `Map pane` where the popup will be added.
  		pane: 'popupPane',

  		// @option offset: Point = Point(0, 7)
  		// The offset of the popup position.
  		offset: [0, 7],

  		// @option maxWidth: Number = 300
  		// Max width of the popup, in pixels.
  		maxWidth: 300,

  		// @option minWidth: Number = 50
  		// Min width of the popup, in pixels.
  		minWidth: 50,

  		// @option maxHeight: Number = null
  		// If set, creates a scrollable container of the given height
  		// inside a popup if its content exceeds it.
  		// The scrollable container can be styled using the
  		// `leaflet-popup-scrolled` CSS class selector.
  		maxHeight: null,

  		// @option autoPan: Boolean = true
  		// Set it to `false` if you don't want the map to do panning animation
  		// to fit the opened popup.
  		autoPan: true,

  		// @option autoPanPaddingTopLeft: Point = null
  		// The margin between the popup and the top left corner of the map
  		// view after autopanning was performed.
  		autoPanPaddingTopLeft: null,

  		// @option autoPanPaddingBottomRight: Point = null
  		// The margin between the popup and the bottom right corner of the map
  		// view after autopanning was performed.
  		autoPanPaddingBottomRight: null,

  		// @option autoPanPadding: Point = Point(5, 5)
  		// Equivalent of setting both top left and bottom right autopan padding to the same value.
  		autoPanPadding: [5, 5],

  		// @option keepInView: Boolean = false
  		// Set it to `true` if you want to prevent users from panning the popup
  		// off of the screen while it is open.
  		keepInView: false,

  		// @option closeButton: Boolean = true
  		// Controls the presence of a close button in the popup.
  		closeButton: true,

  		// @option autoClose: Boolean = true
  		// Set it to `false` if you want to override the default behavior of
  		// the popup closing when another popup is opened.
  		autoClose: true,

  		// @option closeOnEscapeKey: Boolean = true
  		// Set it to `false` if you want to override the default behavior of
  		// the ESC key for closing of the popup.
  		closeOnEscapeKey: true,

  		// @option closeOnClick: Boolean = *
  		// Set it if you want to override the default behavior of the popup closing when user clicks
  		// on the map. Defaults to the map's [`closePopupOnClick`](#map-closepopuponclick) option.

  		// @option className: String = ''
  		// A custom CSS class name to assign to the popup.
  		className: ''
  	},

  	// @namespace Popup
  	// @method openOn(map: Map): this
  	// Alternative to `map.openPopup(popup)`.
  	// Adds the popup to the map and closes the previous one.
  	openOn: function (map) {
  		map = arguments.length ? map : this._source._map; // experimental, not the part of public api

  		if (!map.hasLayer(this) && map._popup && map._popup.options.autoClose) {
  			map.removeLayer(map._popup);
  		}
  		map._popup = this;

  		return DivOverlay.prototype.openOn.call(this, map);
  	},

  	onAdd: function (map) {
  		DivOverlay.prototype.onAdd.call(this, map);

  		// @namespace Map
  		// @section Popup events
  		// @event popupopen: PopupEvent
  		// Fired when a popup is opened in the map
  		map.fire('popupopen', {popup: this});

  		if (this._source) {
  			// @namespace Layer
  			// @section Popup events
  			// @event popupopen: PopupEvent
  			// Fired when a popup bound to this layer is opened
  			this._source.fire('popupopen', {popup: this}, true);
  			// For non-path layers, we toggle the popup when clicking
  			// again the layer, so prevent the map to reopen it.
  			if (!(this._source instanceof Path)) {
  				this._source.on('preclick', stopPropagation);
  			}
  		}
  	},

  	onRemove: function (map) {
  		DivOverlay.prototype.onRemove.call(this, map);

  		// @namespace Map
  		// @section Popup events
  		// @event popupclose: PopupEvent
  		// Fired when a popup in the map is closed
  		map.fire('popupclose', {popup: this});

  		if (this._source) {
  			// @namespace Layer
  			// @section Popup events
  			// @event popupclose: PopupEvent
  			// Fired when a popup bound to this layer is closed
  			this._source.fire('popupclose', {popup: this}, true);
  			if (!(this._source instanceof Path)) {
  				this._source.off('preclick', stopPropagation);
  			}
  		}
  	},

  	getEvents: function () {
  		var events = DivOverlay.prototype.getEvents.call(this);

  		if (this.options.closeOnClick !== undefined ? this.options.closeOnClick : this._map.options.closePopupOnClick) {
  			events.preclick = this.close;
  		}

  		if (this.options.keepInView) {
  			events.moveend = this._adjustPan;
  		}

  		return events;
  	},

  	_initLayout: function () {
  		var prefix = 'leaflet-popup',
  		    container = this._container = create$1('div',
  			prefix + ' ' + (this.options.className || '') +
  			' leaflet-zoom-animated');

  		var wrapper = this._wrapper = create$1('div', prefix + '-content-wrapper', container);
  		this._contentNode = create$1('div', prefix + '-content', wrapper);

  		disableClickPropagation(container);
  		disableScrollPropagation(this._contentNode);
  		on(container, 'contextmenu', stopPropagation);

  		this._tipContainer = create$1('div', prefix + '-tip-container', container);
  		this._tip = create$1('div', prefix + '-tip', this._tipContainer);

  		if (this.options.closeButton) {
  			var closeButton = this._closeButton = create$1('a', prefix + '-close-button', container);
  			closeButton.setAttribute('role', 'button'); // overrides the implicit role=link of <a> elements #7399
  			closeButton.setAttribute('aria-label', 'Close popup');
  			closeButton.href = '#close';
  			closeButton.innerHTML = '<span aria-hidden="true">&#215;</span>';

  			on(closeButton, 'click', function (ev) {
  				preventDefault(ev);
  				this.close();
  			}, this);
  		}
  	},

  	_updateLayout: function () {
  		var container = this._contentNode,
  		    style = container.style;

  		style.width = '';
  		style.whiteSpace = 'nowrap';

  		var width = container.offsetWidth;
  		width = Math.min(width, this.options.maxWidth);
  		width = Math.max(width, this.options.minWidth);

  		style.width = (width + 1) + 'px';
  		style.whiteSpace = '';

  		style.height = '';

  		var height = container.offsetHeight,
  		    maxHeight = this.options.maxHeight,
  		    scrolledClass = 'leaflet-popup-scrolled';

  		if (maxHeight && height > maxHeight) {
  			style.height = maxHeight + 'px';
  			addClass(container, scrolledClass);
  		} else {
  			removeClass(container, scrolledClass);
  		}

  		this._containerWidth = this._container.offsetWidth;
  	},

  	_animateZoom: function (e) {
  		var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center),
  		    anchor = this._getAnchor();
  		setPosition(this._container, pos.add(anchor));
  	},

  	_adjustPan: function () {
  		if (!this.options.autoPan) { return; }
  		if (this._map._panAnim) { this._map._panAnim.stop(); }

  		// We can endlessly recurse if keepInView is set and the view resets.
  		// Let's guard against that by exiting early if we're responding to our own autopan.
  		if (this._autopanning) {
  			this._autopanning = false;
  			return;
  		}

  		var map = this._map,
  		    marginBottom = parseInt(getStyle(this._container, 'marginBottom'), 10) || 0,
  		    containerHeight = this._container.offsetHeight + marginBottom,
  		    containerWidth = this._containerWidth,
  		    layerPos = new Point(this._containerLeft, -containerHeight - this._containerBottom);

  		layerPos._add(getPosition(this._container));

  		var containerPos = map.layerPointToContainerPoint(layerPos),
  		    padding = toPoint(this.options.autoPanPadding),
  		    paddingTL = toPoint(this.options.autoPanPaddingTopLeft || padding),
  		    paddingBR = toPoint(this.options.autoPanPaddingBottomRight || padding),
  		    size = map.getSize(),
  		    dx = 0,
  		    dy = 0;

  		if (containerPos.x + containerWidth + paddingBR.x > size.x) { // right
  			dx = containerPos.x + containerWidth - size.x + paddingBR.x;
  		}
  		if (containerPos.x - dx - paddingTL.x < 0) { // left
  			dx = containerPos.x - paddingTL.x;
  		}
  		if (containerPos.y + containerHeight + paddingBR.y > size.y) { // bottom
  			dy = containerPos.y + containerHeight - size.y + paddingBR.y;
  		}
  		if (containerPos.y - dy - paddingTL.y < 0) { // top
  			dy = containerPos.y - paddingTL.y;
  		}

  		// @namespace Map
  		// @section Popup events
  		// @event autopanstart: Event
  		// Fired when the map starts autopanning when opening a popup.
  		if (dx || dy) {
  			// Track that we're autopanning, as this function will be re-ran on moveend
  			if (this.options.keepInView) {
  				this._autopanning = true;
  			}

  			map
  			    .fire('autopanstart')
  			    .panBy([dx, dy]);
  		}
  	},

  	_getAnchor: function () {
  		// Where should we anchor the popup on the source layer?
  		return toPoint(this._source && this._source._getPopupAnchor ? this._source._getPopupAnchor() : [0, 0]);
  	}

  });

  // @namespace Popup
  // @factory L.popup(options?: Popup options, source?: Layer)
  // Instantiates a `Popup` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the popup with a reference to the Layer to which it refers.
  // @alternative
  // @factory L.popup(latlng: LatLng, options?: Popup options)
  // Instantiates a `Popup` object given `latlng` where the popup will open and an optional `options` object that describes its appearance and location.
  var popup = function (options, source) {
  	return new Popup(options, source);
  };


  /* @namespace Map
   * @section Interaction Options
   * @option closePopupOnClick: Boolean = true
   * Set it to `false` if you don't want popups to close when user clicks the map.
   */
  Map.mergeOptions({
  	closePopupOnClick: true
  });


  // @namespace Map
  // @section Methods for Layers and Controls
  Map.include({
  	// @method openPopup(popup: Popup): this
  	// Opens the specified popup while closing the previously opened (to make sure only one is opened at one time for usability).
  	// @alternative
  	// @method openPopup(content: String|HTMLElement, latlng: LatLng, options?: Popup options): this
  	// Creates a popup with the specified content and options and opens it in the given point on a map.
  	openPopup: function (popup, latlng, options) {
  		this._initOverlay(Popup, popup, latlng, options)
  		  .openOn(this);

  		return this;
  	},

  	// @method closePopup(popup?: Popup): this
  	// Closes the popup previously opened with [openPopup](#map-openpopup) (or the given one).
  	closePopup: function (popup) {
  		popup = arguments.length ? popup : this._popup;
  		if (popup) {
  			popup.close();
  		}
  		return this;
  	}
  });

  /*
   * @namespace Layer
   * @section Popup methods example
   *
   * All layers share a set of methods convenient for binding popups to it.
   *
   * ```js
   * var layer = L.Polygon(latlngs).bindPopup('Hi There!').addTo(map);
   * layer.openPopup();
   * layer.closePopup();
   * ```
   *
   * Popups will also be automatically opened when the layer is clicked on and closed when the layer is removed from the map or another popup is opened.
   */

  // @section Popup methods
  Layer.include({

  	// @method bindPopup(content: String|HTMLElement|Function|Popup, options?: Popup options): this
  	// Binds a popup to the layer with the passed `content` and sets up the
  	// necessary event listeners. If a `Function` is passed it will receive
  	// the layer as the first argument and should return a `String` or `HTMLElement`.
  	bindPopup: function (content, options) {
  		this._popup = this._initOverlay(Popup, this._popup, content, options);
  		if (!this._popupHandlersAdded) {
  			this.on({
  				click: this._openPopup,
  				keypress: this._onKeyPress,
  				remove: this.closePopup,
  				move: this._movePopup
  			});
  			this._popupHandlersAdded = true;
  		}

  		return this;
  	},

  	// @method unbindPopup(): this
  	// Removes the popup previously bound with `bindPopup`.
  	unbindPopup: function () {
  		if (this._popup) {
  			this.off({
  				click: this._openPopup,
  				keypress: this._onKeyPress,
  				remove: this.closePopup,
  				move: this._movePopup
  			});
  			this._popupHandlersAdded = false;
  			this._popup = null;
  		}
  		return this;
  	},

  	// @method openPopup(latlng?: LatLng): this
  	// Opens the bound popup at the specified `latlng` or at the default popup anchor if no `latlng` is passed.
  	openPopup: function (latlng) {
  		if (this._popup) {
  			if (!(this instanceof FeatureGroup)) {
  				this._popup._source = this;
  			}
  			if (this._popup._prepareOpen(latlng || this._latlng)) {
  				// open the popup on the map
  				this._popup.openOn(this._map);
  			}
  		}
  		return this;
  	},

  	// @method closePopup(): this
  	// Closes the popup bound to this layer if it is open.
  	closePopup: function () {
  		if (this._popup) {
  			this._popup.close();
  		}
  		return this;
  	},

  	// @method togglePopup(): this
  	// Opens or closes the popup bound to this layer depending on its current state.
  	togglePopup: function () {
  		if (this._popup) {
  			this._popup.toggle(this);
  		}
  		return this;
  	},

  	// @method isPopupOpen(): boolean
  	// Returns `true` if the popup bound to this layer is currently open.
  	isPopupOpen: function () {
  		return (this._popup ? this._popup.isOpen() : false);
  	},

  	// @method setPopupContent(content: String|HTMLElement|Popup): this
  	// Sets the content of the popup bound to this layer.
  	setPopupContent: function (content) {
  		if (this._popup) {
  			this._popup.setContent(content);
  		}
  		return this;
  	},

  	// @method getPopup(): Popup
  	// Returns the popup bound to this layer.
  	getPopup: function () {
  		return this._popup;
  	},

  	_openPopup: function (e) {
  		if (!this._popup || !this._map) {
  			return;
  		}
  		// prevent map click
  		stop(e);

  		var target = e.layer || e.target;
  		if (this._popup._source === target && !(target instanceof Path)) {
  			// treat it like a marker and figure out
  			// if we should toggle it open/closed
  			if (this._map.hasLayer(this._popup)) {
  				this.closePopup();
  			} else {
  				this.openPopup(e.latlng);
  			}
  			return;
  		}
  		this._popup._source = target;
  		this.openPopup(e.latlng);
  	},

  	_movePopup: function (e) {
  		this._popup.setLatLng(e.latlng);
  	},

  	_onKeyPress: function (e) {
  		if (e.originalEvent.keyCode === 13) {
  			this._openPopup(e);
  		}
  	}
  });

  /*
   * @class Tooltip
   * @inherits DivOverlay
   * @aka L.Tooltip
   * Used to display small texts on top of map layers.
   *
   * @example
   * If you want to just bind a tooltip to marker:
   *
   * ```js
   * marker.bindTooltip("my tooltip text").openTooltip();
   * ```
   * Path overlays like polylines also have a `bindTooltip` method.
   *
   * A tooltip can be also standalone:
   *
   * ```js
   * var tooltip = L.tooltip()
   * 	.setLatLng(latlng)
   * 	.setContent('Hello world!<br />This is a nice tooltip.')
   * 	.addTo(map);
   * ```
   * or
   * ```js
   * var tooltip = L.tooltip(latlng, {content: 'Hello world!<br />This is a nice tooltip.'})
   * 	.addTo(map);
   * ```
   *
   *
   * Note about tooltip offset. Leaflet takes two options in consideration
   * for computing tooltip offsetting:
   * - the `offset` Tooltip option: it defaults to [0, 0], and it's specific to one tooltip.
   *   Add a positive x offset to move the tooltip to the right, and a positive y offset to
   *   move it to the bottom. Negatives will move to the left and top.
   * - the `tooltipAnchor` Icon option: this will only be considered for Marker. You
   *   should adapt this value if you use a custom icon.
   */


  // @namespace Tooltip
  var Tooltip = DivOverlay.extend({

  	// @section
  	// @aka Tooltip options
  	options: {
  		// @option pane: String = 'tooltipPane'
  		// `Map pane` where the tooltip will be added.
  		pane: 'tooltipPane',

  		// @option offset: Point = Point(0, 0)
  		// Optional offset of the tooltip position.
  		offset: [0, 0],

  		// @option direction: String = 'auto'
  		// Direction where to open the tooltip. Possible values are: `right`, `left`,
  		// `top`, `bottom`, `center`, `auto`.
  		// `auto` will dynamically switch between `right` and `left` according to the tooltip
  		// position on the map.
  		direction: 'auto',

  		// @option permanent: Boolean = false
  		// Whether to open the tooltip permanently or only on mouseover.
  		permanent: false,

  		// @option sticky: Boolean = false
  		// If true, the tooltip will follow the mouse instead of being fixed at the feature center.
  		sticky: false,

  		// @option opacity: Number = 0.9
  		// Tooltip container opacity.
  		opacity: 0.9
  	},

  	onAdd: function (map) {
  		DivOverlay.prototype.onAdd.call(this, map);
  		this.setOpacity(this.options.opacity);

  		// @namespace Map
  		// @section Tooltip events
  		// @event tooltipopen: TooltipEvent
  		// Fired when a tooltip is opened in the map.
  		map.fire('tooltipopen', {tooltip: this});

  		if (this._source) {
  			this.addEventParent(this._source);

  			// @namespace Layer
  			// @section Tooltip events
  			// @event tooltipopen: TooltipEvent
  			// Fired when a tooltip bound to this layer is opened.
  			this._source.fire('tooltipopen', {tooltip: this}, true);
  		}
  	},

  	onRemove: function (map) {
  		DivOverlay.prototype.onRemove.call(this, map);

  		// @namespace Map
  		// @section Tooltip events
  		// @event tooltipclose: TooltipEvent
  		// Fired when a tooltip in the map is closed.
  		map.fire('tooltipclose', {tooltip: this});

  		if (this._source) {
  			this.removeEventParent(this._source);

  			// @namespace Layer
  			// @section Tooltip events
  			// @event tooltipclose: TooltipEvent
  			// Fired when a tooltip bound to this layer is closed.
  			this._source.fire('tooltipclose', {tooltip: this}, true);
  		}
  	},

  	getEvents: function () {
  		var events = DivOverlay.prototype.getEvents.call(this);

  		if (!this.options.permanent) {
  			events.preclick = this.close;
  		}

  		return events;
  	},

  	_initLayout: function () {
  		var prefix = 'leaflet-tooltip',
  		    className = prefix + ' ' + (this.options.className || '') + ' leaflet-zoom-' + (this._zoomAnimated ? 'animated' : 'hide');

  		this._contentNode = this._container = create$1('div', className);

  		this._container.setAttribute('role', 'tooltip');
  		this._container.setAttribute('id', 'leaflet-tooltip-' + stamp(this));
  	},

  	_updateLayout: function () {},

  	_adjustPan: function () {},

  	_setPosition: function (pos) {
  		var subX, subY,
  		    map = this._map,
  		    container = this._container,
  		    centerPoint = map.latLngToContainerPoint(map.getCenter()),
  		    tooltipPoint = map.layerPointToContainerPoint(pos),
  		    direction = this.options.direction,
  		    tooltipWidth = container.offsetWidth,
  		    tooltipHeight = container.offsetHeight,
  		    offset = toPoint(this.options.offset),
  		    anchor = this._getAnchor();

  		if (direction === 'top') {
  			subX = tooltipWidth / 2;
  			subY = tooltipHeight;
  		} else if (direction === 'bottom') {
  			subX = tooltipWidth / 2;
  			subY = 0;
  		} else if (direction === 'center') {
  			subX = tooltipWidth / 2;
  			subY = tooltipHeight / 2;
  		} else if (direction === 'right') {
  			subX = 0;
  			subY = tooltipHeight / 2;
  		} else if (direction === 'left') {
  			subX = tooltipWidth;
  			subY = tooltipHeight / 2;
  		} else if (tooltipPoint.x < centerPoint.x) {
  			direction = 'right';
  			subX = 0;
  			subY = tooltipHeight / 2;
  		} else {
  			direction = 'left';
  			subX = tooltipWidth + (offset.x + anchor.x) * 2;
  			subY = tooltipHeight / 2;
  		}

  		pos = pos.subtract(toPoint(subX, subY, true)).add(offset).add(anchor);

  		removeClass(container, 'leaflet-tooltip-right');
  		removeClass(container, 'leaflet-tooltip-left');
  		removeClass(container, 'leaflet-tooltip-top');
  		removeClass(container, 'leaflet-tooltip-bottom');
  		addClass(container, 'leaflet-tooltip-' + direction);
  		setPosition(container, pos);
  	},

  	_updatePosition: function () {
  		var pos = this._map.latLngToLayerPoint(this._latlng);
  		this._setPosition(pos);
  	},

  	setOpacity: function (opacity) {
  		this.options.opacity = opacity;

  		if (this._container) {
  			setOpacity(this._container, opacity);
  		}
  	},

  	_animateZoom: function (e) {
  		var pos = this._map._latLngToNewLayerPoint(this._latlng, e.zoom, e.center);
  		this._setPosition(pos);
  	},

  	_getAnchor: function () {
  		// Where should we anchor the tooltip on the source layer?
  		return toPoint(this._source && this._source._getTooltipAnchor && !this.options.sticky ? this._source._getTooltipAnchor() : [0, 0]);
  	}

  });

  // @namespace Tooltip
  // @factory L.tooltip(options?: Tooltip options, source?: Layer)
  // Instantiates a `Tooltip` object given an optional `options` object that describes its appearance and location and an optional `source` object that is used to tag the tooltip with a reference to the Layer to which it refers.
  // @alternative
  // @factory L.tooltip(latlng: LatLng, options?: Tooltip options)
  // Instantiates a `Tooltip` object given `latlng` where the tooltip will open and an optional `options` object that describes its appearance and location.
  var tooltip = function (options, source) {
  	return new Tooltip(options, source);
  };

  // @namespace Map
  // @section Methods for Layers and Controls
  Map.include({

  	// @method openTooltip(tooltip: Tooltip): this
  	// Opens the specified tooltip.
  	// @alternative
  	// @method openTooltip(content: String|HTMLElement, latlng: LatLng, options?: Tooltip options): this
  	// Creates a tooltip with the specified content and options and open it.
  	openTooltip: function (tooltip, latlng, options) {
  		this._initOverlay(Tooltip, tooltip, latlng, options)
  		  .openOn(this);

  		return this;
  	},

  	// @method closeTooltip(tooltip: Tooltip): this
  	// Closes the tooltip given as parameter.
  	closeTooltip: function (tooltip) {
  		tooltip.close();
  		return this;
  	}

  });

  /*
   * @namespace Layer
   * @section Tooltip methods example
   *
   * All layers share a set of methods convenient for binding tooltips to it.
   *
   * ```js
   * var layer = L.Polygon(latlngs).bindTooltip('Hi There!').addTo(map);
   * layer.openTooltip();
   * layer.closeTooltip();
   * ```
   */

  // @section Tooltip methods
  Layer.include({

  	// @method bindTooltip(content: String|HTMLElement|Function|Tooltip, options?: Tooltip options): this
  	// Binds a tooltip to the layer with the passed `content` and sets up the
  	// necessary event listeners. If a `Function` is passed it will receive
  	// the layer as the first argument and should return a `String` or `HTMLElement`.
  	bindTooltip: function (content, options) {

  		if (this._tooltip && this.isTooltipOpen()) {
  			this.unbindTooltip();
  		}

  		this._tooltip = this._initOverlay(Tooltip, this._tooltip, content, options);
  		this._initTooltipInteractions();

  		if (this._tooltip.options.permanent && this._map && this._map.hasLayer(this)) {
  			this.openTooltip();
  		}

  		return this;
  	},

  	// @method unbindTooltip(): this
  	// Removes the tooltip previously bound with `bindTooltip`.
  	unbindTooltip: function () {
  		if (this._tooltip) {
  			this._initTooltipInteractions(true);
  			this.closeTooltip();
  			this._tooltip = null;
  		}
  		return this;
  	},

  	_initTooltipInteractions: function (remove) {
  		if (!remove && this._tooltipHandlersAdded) { return; }
  		var onOff = remove ? 'off' : 'on',
  		    events = {
  			remove: this.closeTooltip,
  			move: this._moveTooltip
  		    };
  		if (!this._tooltip.options.permanent) {
  			events.mouseover = this._openTooltip;
  			events.mouseout = this.closeTooltip;
  			events.click = this._openTooltip;
  			if (this._map) {
  				this._addFocusListeners();
  			} else {
  				events.add = this._addFocusListeners;
  			}
  		} else {
  			events.add = this._openTooltip;
  		}
  		if (this._tooltip.options.sticky) {
  			events.mousemove = this._moveTooltip;
  		}
  		this[onOff](events);
  		this._tooltipHandlersAdded = !remove;
  	},

  	// @method openTooltip(latlng?: LatLng): this
  	// Opens the bound tooltip at the specified `latlng` or at the default tooltip anchor if no `latlng` is passed.
  	openTooltip: function (latlng) {
  		if (this._tooltip) {
  			if (!(this instanceof FeatureGroup)) {
  				this._tooltip._source = this;
  			}
  			if (this._tooltip._prepareOpen(latlng)) {
  				// open the tooltip on the map
  				this._tooltip.openOn(this._map);

  				if (this.getElement) {
  					this._setAriaDescribedByOnLayer(this);
  				} else if (this.eachLayer) {
  					this.eachLayer(this._setAriaDescribedByOnLayer, this);
  				}
  			}
  		}
  		return this;
  	},

  	// @method closeTooltip(): this
  	// Closes the tooltip bound to this layer if it is open.
  	closeTooltip: function () {
  		if (this._tooltip) {
  			return this._tooltip.close();
  		}
  	},

  	// @method toggleTooltip(): this
  	// Opens or closes the tooltip bound to this layer depending on its current state.
  	toggleTooltip: function () {
  		if (this._tooltip) {
  			this._tooltip.toggle(this);
  		}
  		return this;
  	},

  	// @method isTooltipOpen(): boolean
  	// Returns `true` if the tooltip bound to this layer is currently open.
  	isTooltipOpen: function () {
  		return this._tooltip.isOpen();
  	},

  	// @method setTooltipContent(content: String|HTMLElement|Tooltip): this
  	// Sets the content of the tooltip bound to this layer.
  	setTooltipContent: function (content) {
  		if (this._tooltip) {
  			this._tooltip.setContent(content);
  		}
  		return this;
  	},

  	// @method getTooltip(): Tooltip
  	// Returns the tooltip bound to this layer.
  	getTooltip: function () {
  		return this._tooltip;
  	},

  	_addFocusListeners: function () {
  		if (this.getElement) {
  			this._addFocusListenersOnLayer(this);
  		} else if (this.eachLayer) {
  			this.eachLayer(this._addFocusListenersOnLayer, this);
  		}
  	},

  	_addFocusListenersOnLayer: function (layer) {
  		var el = typeof layer.getElement === 'function' && layer.getElement();
  		if (el) {
  			on(el, 'focus', function () {
  				this._tooltip._source = layer;
  				this.openTooltip();
  			}, this);
  			on(el, 'blur', this.closeTooltip, this);
  		}
  	},

  	_setAriaDescribedByOnLayer: function (layer) {
  		var el = typeof layer.getElement === 'function' && layer.getElement();
  		if (el) {
  			el.setAttribute('aria-describedby', this._tooltip._container.id);
  		}
  	},


  	_openTooltip: function (e) {
  		if (!this._tooltip || !this._map) {
  			return;
  		}

  		// If the map is moving, we will show the tooltip after it's done.
  		if (this._map.dragging && this._map.dragging.moving() && !this._openOnceFlag) {
  			this._openOnceFlag = true;
  			var that = this;
  			this._map.once('moveend', function () {
  				that._openOnceFlag = false;
  				that._openTooltip(e);
  			});
  			return;
  		}

  		this._tooltip._source = e.layer || e.target;

  		this.openTooltip(this._tooltip.options.sticky ? e.latlng : undefined);
  	},

  	_moveTooltip: function (e) {
  		var latlng = e.latlng, containerPoint, layerPoint;
  		if (this._tooltip.options.sticky && e.originalEvent) {
  			containerPoint = this._map.mouseEventToContainerPoint(e.originalEvent);
  			layerPoint = this._map.containerPointToLayerPoint(containerPoint);
  			latlng = this._map.layerPointToLatLng(layerPoint);
  		}
  		this._tooltip.setLatLng(latlng);
  	}
  });

  /*
   * @class DivIcon
   * @aka L.DivIcon
   * @inherits Icon
   *
   * Represents a lightweight icon for markers that uses a simple `<div>`
   * element instead of an image. Inherits from `Icon` but ignores the `iconUrl` and shadow options.
   *
   * @example
   * ```js
   * var myIcon = L.divIcon({className: 'my-div-icon'});
   * // you can set .my-div-icon styles in CSS
   *
   * L.marker([50.505, 30.57], {icon: myIcon}).addTo(map);
   * ```
   *
   * By default, it has a 'leaflet-div-icon' CSS class and is styled as a little white square with a shadow.
   */

  var DivIcon = Icon.extend({
  	options: {
  		// @section
  		// @aka DivIcon options
  		iconSize: [12, 12], // also can be set through CSS

  		// iconAnchor: (Point),
  		// popupAnchor: (Point),

  		// @option html: String|HTMLElement = ''
  		// Custom HTML code to put inside the div element, empty by default. Alternatively,
  		// an instance of `HTMLElement`.
  		html: false,

  		// @option bgPos: Point = [0, 0]
  		// Optional relative position of the background, in pixels
  		bgPos: null,

  		className: 'leaflet-div-icon'
  	},

  	createIcon: function (oldIcon) {
  		var div = (oldIcon && oldIcon.tagName === 'DIV') ? oldIcon : document.createElement('div'),
  		    options = this.options;

  		if (options.html instanceof Element) {
  			empty(div);
  			div.appendChild(options.html);
  		} else {
  			div.innerHTML = options.html !== false ? options.html : '';
  		}

  		if (options.bgPos) {
  			var bgPos = toPoint(options.bgPos);
  			div.style.backgroundPosition = (-bgPos.x) + 'px ' + (-bgPos.y) + 'px';
  		}
  		this._setIconStyles(div, 'icon');

  		return div;
  	},

  	createShadow: function () {
  		return null;
  	}
  });

  // @factory L.divIcon(options: DivIcon options)
  // Creates a `DivIcon` instance with the given options.
  function divIcon(options) {
  	return new DivIcon(options);
  }

  Icon.Default = IconDefault;

  /*
   * @class GridLayer
   * @inherits Layer
   * @aka L.GridLayer
   *
   * Generic class for handling a tiled grid of HTML elements. This is the base class for all tile layers and replaces `TileLayer.Canvas`.
   * GridLayer can be extended to create a tiled grid of HTML elements like `<canvas>`, `<img>` or `<div>`. GridLayer will handle creating and animating these DOM elements for you.
   *
   *
   * @section Synchronous usage
   * @example
   *
   * To create a custom layer, extend GridLayer and implement the `createTile()` method, which will be passed a `Point` object with the `x`, `y`, and `z` (zoom level) coordinates to draw your tile.
   *
   * ```js
   * var CanvasLayer = L.GridLayer.extend({
   *     createTile: function(coords){
   *         // create a <canvas> element for drawing
   *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
   *
   *         // setup tile width and height according to the options
   *         var size = this.getTileSize();
   *         tile.width = size.x;
   *         tile.height = size.y;
   *
   *         // get a canvas context and draw something on it using coords.x, coords.y and coords.z
   *         var ctx = tile.getContext('2d');
   *
   *         // return the tile so it can be rendered on screen
   *         return tile;
   *     }
   * });
   * ```
   *
   * @section Asynchronous usage
   * @example
   *
   * Tile creation can also be asynchronous, this is useful when using a third-party drawing library. Once the tile is finished drawing it can be passed to the `done()` callback.
   *
   * ```js
   * var CanvasLayer = L.GridLayer.extend({
   *     createTile: function(coords, done){
   *         var error;
   *
   *         // create a <canvas> element for drawing
   *         var tile = L.DomUtil.create('canvas', 'leaflet-tile');
   *
   *         // setup tile width and height according to the options
   *         var size = this.getTileSize();
   *         tile.width = size.x;
   *         tile.height = size.y;
   *
   *         // draw something asynchronously and pass the tile to the done() callback
   *         setTimeout(function() {
   *             done(error, tile);
   *         }, 1000);
   *
   *         return tile;
   *     }
   * });
   * ```
   *
   * @section
   */


  var GridLayer = Layer.extend({

  	// @section
  	// @aka GridLayer options
  	options: {
  		// @option tileSize: Number|Point = 256
  		// Width and height of tiles in the grid. Use a number if width and height are equal, or `L.point(width, height)` otherwise.
  		tileSize: 256,

  		// @option opacity: Number = 1.0
  		// Opacity of the tiles. Can be used in the `createTile()` function.
  		opacity: 1,

  		// @option updateWhenIdle: Boolean = (depends)
  		// Load new tiles only when panning ends.
  		// `true` by default on mobile browsers, in order to avoid too many requests and keep smooth navigation.
  		// `false` otherwise in order to display new tiles _during_ panning, since it is easy to pan outside the
  		// [`keepBuffer`](#gridlayer-keepbuffer) option in desktop browsers.
  		updateWhenIdle: Browser.mobile,

  		// @option updateWhenZooming: Boolean = true
  		// By default, a smooth zoom animation (during a [touch zoom](#map-touchzoom) or a [`flyTo()`](#map-flyto)) will update grid layers every integer zoom level. Setting this option to `false` will update the grid layer only when the smooth animation ends.
  		updateWhenZooming: true,

  		// @option updateInterval: Number = 200
  		// Tiles will not update more than once every `updateInterval` milliseconds when panning.
  		updateInterval: 200,

  		// @option zIndex: Number = 1
  		// The explicit zIndex of the tile layer.
  		zIndex: 1,

  		// @option bounds: LatLngBounds = undefined
  		// If set, tiles will only be loaded inside the set `LatLngBounds`.
  		bounds: null,

  		// @option minZoom: Number = 0
  		// The minimum zoom level down to which this layer will be displayed (inclusive).
  		minZoom: 0,

  		// @option maxZoom: Number = undefined
  		// The maximum zoom level up to which this layer will be displayed (inclusive).
  		maxZoom: undefined,

  		// @option maxNativeZoom: Number = undefined
  		// Maximum zoom number the tile source has available. If it is specified,
  		// the tiles on all zoom levels higher than `maxNativeZoom` will be loaded
  		// from `maxNativeZoom` level and auto-scaled.
  		maxNativeZoom: undefined,

  		// @option minNativeZoom: Number = undefined
  		// Minimum zoom number the tile source has available. If it is specified,
  		// the tiles on all zoom levels lower than `minNativeZoom` will be loaded
  		// from `minNativeZoom` level and auto-scaled.
  		minNativeZoom: undefined,

  		// @option noWrap: Boolean = false
  		// Whether the layer is wrapped around the antimeridian. If `true`, the
  		// GridLayer will only be displayed once at low zoom levels. Has no
  		// effect when the [map CRS](#map-crs) doesn't wrap around. Can be used
  		// in combination with [`bounds`](#gridlayer-bounds) to prevent requesting
  		// tiles outside the CRS limits.
  		noWrap: false,

  		// @option pane: String = 'tilePane'
  		// `Map pane` where the grid layer will be added.
  		pane: 'tilePane',

  		// @option className: String = ''
  		// A custom class name to assign to the tile layer. Empty by default.
  		className: '',

  		// @option keepBuffer: Number = 2
  		// When panning the map, keep this many rows and columns of tiles before unloading them.
  		keepBuffer: 2
  	},

  	initialize: function (options) {
  		setOptions(this, options);
  	},

  	onAdd: function () {
  		this._initContainer();

  		this._levels = {};
  		this._tiles = {};

  		this._resetView(); // implicit _update() call
  	},

  	beforeAdd: function (map) {
  		map._addZoomLimit(this);
  	},

  	onRemove: function (map) {
  		this._removeAllTiles();
  		remove(this._container);
  		map._removeZoomLimit(this);
  		this._container = null;
  		this._tileZoom = undefined;
  	},

  	// @method bringToFront: this
  	// Brings the tile layer to the top of all tile layers.
  	bringToFront: function () {
  		if (this._map) {
  			toFront(this._container);
  			this._setAutoZIndex(Math.max);
  		}
  		return this;
  	},

  	// @method bringToBack: this
  	// Brings the tile layer to the bottom of all tile layers.
  	bringToBack: function () {
  		if (this._map) {
  			toBack(this._container);
  			this._setAutoZIndex(Math.min);
  		}
  		return this;
  	},

  	// @method getContainer: HTMLElement
  	// Returns the HTML element that contains the tiles for this layer.
  	getContainer: function () {
  		return this._container;
  	},

  	// @method setOpacity(opacity: Number): this
  	// Changes the [opacity](#gridlayer-opacity) of the grid layer.
  	setOpacity: function (opacity) {
  		this.options.opacity = opacity;
  		this._updateOpacity();
  		return this;
  	},

  	// @method setZIndex(zIndex: Number): this
  	// Changes the [zIndex](#gridlayer-zindex) of the grid layer.
  	setZIndex: function (zIndex) {
  		this.options.zIndex = zIndex;
  		this._updateZIndex();

  		return this;
  	},

  	// @method isLoading: Boolean
  	// Returns `true` if any tile in the grid layer has not finished loading.
  	isLoading: function () {
  		return this._loading;
  	},

  	// @method redraw: this
  	// Causes the layer to clear all the tiles and request them again.
  	redraw: function () {
  		if (this._map) {
  			this._removeAllTiles();
  			var tileZoom = this._clampZoom(this._map.getZoom());
  			if (tileZoom !== this._tileZoom) {
  				this._tileZoom = tileZoom;
  				this._updateLevels();
  			}
  			this._update();
  		}
  		return this;
  	},

  	getEvents: function () {
  		var events = {
  			viewprereset: this._invalidateAll,
  			viewreset: this._resetView,
  			zoom: this._resetView,
  			moveend: this._onMoveEnd
  		};

  		if (!this.options.updateWhenIdle) {
  			// update tiles on move, but not more often than once per given interval
  			if (!this._onMove) {
  				this._onMove = throttle(this._onMoveEnd, this.options.updateInterval, this);
  			}

  			events.move = this._onMove;
  		}

  		if (this._zoomAnimated) {
  			events.zoomanim = this._animateZoom;
  		}

  		return events;
  	},

  	// @section Extension methods
  	// Layers extending `GridLayer` shall reimplement the following method.
  	// @method createTile(coords: Object, done?: Function): HTMLElement
  	// Called only internally, must be overridden by classes extending `GridLayer`.
  	// Returns the `HTMLElement` corresponding to the given `coords`. If the `done` callback
  	// is specified, it must be called when the tile has finished loading and drawing.
  	createTile: function () {
  		return document.createElement('div');
  	},

  	// @section
  	// @method getTileSize: Point
  	// Normalizes the [tileSize option](#gridlayer-tilesize) into a point. Used by the `createTile()` method.
  	getTileSize: function () {
  		var s = this.options.tileSize;
  		return s instanceof Point ? s : new Point(s, s);
  	},

  	_updateZIndex: function () {
  		if (this._container && this.options.zIndex !== undefined && this.options.zIndex !== null) {
  			this._container.style.zIndex = this.options.zIndex;
  		}
  	},

  	_setAutoZIndex: function (compare) {
  		// go through all other layers of the same pane, set zIndex to max + 1 (front) or min - 1 (back)

  		var layers = this.getPane().children,
  		    edgeZIndex = -compare(-Infinity, Infinity); // -Infinity for max, Infinity for min

  		for (var i = 0, len = layers.length, zIndex; i < len; i++) {

  			zIndex = layers[i].style.zIndex;

  			if (layers[i] !== this._container && zIndex) {
  				edgeZIndex = compare(edgeZIndex, +zIndex);
  			}
  		}

  		if (isFinite(edgeZIndex)) {
  			this.options.zIndex = edgeZIndex + compare(-1, 1);
  			this._updateZIndex();
  		}
  	},

  	_updateOpacity: function () {
  		if (!this._map) { return; }

  		// IE doesn't inherit filter opacity properly, so we're forced to set it on tiles
  		if (Browser.ielt9) { return; }

  		setOpacity(this._container, this.options.opacity);

  		var now = +new Date(),
  		    nextFrame = false,
  		    willPrune = false;

  		for (var key in this._tiles) {
  			var tile = this._tiles[key];
  			if (!tile.current || !tile.loaded) { continue; }

  			var fade = Math.min(1, (now - tile.loaded) / 200);

  			setOpacity(tile.el, fade);
  			if (fade < 1) {
  				nextFrame = true;
  			} else {
  				if (tile.active) {
  					willPrune = true;
  				} else {
  					this._onOpaqueTile(tile);
  				}
  				tile.active = true;
  			}
  		}

  		if (willPrune && !this._noPrune) { this._pruneTiles(); }

  		if (nextFrame) {
  			cancelAnimFrame(this._fadeFrame);
  			this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
  		}
  	},

  	_onOpaqueTile: falseFn,

  	_initContainer: function () {
  		if (this._container) { return; }

  		this._container = create$1('div', 'leaflet-layer ' + (this.options.className || ''));
  		this._updateZIndex();

  		if (this.options.opacity < 1) {
  			this._updateOpacity();
  		}

  		this.getPane().appendChild(this._container);
  	},

  	_updateLevels: function () {

  		var zoom = this._tileZoom,
  		    maxZoom = this.options.maxZoom;

  		if (zoom === undefined) { return undefined; }

  		for (var z in this._levels) {
  			z = Number(z);
  			if (this._levels[z].el.children.length || z === zoom) {
  				this._levels[z].el.style.zIndex = maxZoom - Math.abs(zoom - z);
  				this._onUpdateLevel(z);
  			} else {
  				remove(this._levels[z].el);
  				this._removeTilesAtZoom(z);
  				this._onRemoveLevel(z);
  				delete this._levels[z];
  			}
  		}

  		var level = this._levels[zoom],
  		    map = this._map;

  		if (!level) {
  			level = this._levels[zoom] = {};

  			level.el = create$1('div', 'leaflet-tile-container leaflet-zoom-animated', this._container);
  			level.el.style.zIndex = maxZoom;

  			level.origin = map.project(map.unproject(map.getPixelOrigin()), zoom).round();
  			level.zoom = zoom;

  			this._setZoomTransform(level, map.getCenter(), map.getZoom());

  			// force the browser to consider the newly added element for transition
  			falseFn(level.el.offsetWidth);

  			this._onCreateLevel(level);
  		}

  		this._level = level;

  		return level;
  	},

  	_onUpdateLevel: falseFn,

  	_onRemoveLevel: falseFn,

  	_onCreateLevel: falseFn,

  	_pruneTiles: function () {
  		if (!this._map) {
  			return;
  		}

  		var key, tile;

  		var zoom = this._map.getZoom();
  		if (zoom > this.options.maxZoom ||
  			zoom < this.options.minZoom) {
  			this._removeAllTiles();
  			return;
  		}

  		for (key in this._tiles) {
  			tile = this._tiles[key];
  			tile.retain = tile.current;
  		}

  		for (key in this._tiles) {
  			tile = this._tiles[key];
  			if (tile.current && !tile.active) {
  				var coords = tile.coords;
  				if (!this._retainParent(coords.x, coords.y, coords.z, coords.z - 5)) {
  					this._retainChildren(coords.x, coords.y, coords.z, coords.z + 2);
  				}
  			}
  		}

  		for (key in this._tiles) {
  			if (!this._tiles[key].retain) {
  				this._removeTile(key);
  			}
  		}
  	},

  	_removeTilesAtZoom: function (zoom) {
  		for (var key in this._tiles) {
  			if (this._tiles[key].coords.z !== zoom) {
  				continue;
  			}
  			this._removeTile(key);
  		}
  	},

  	_removeAllTiles: function () {
  		for (var key in this._tiles) {
  			this._removeTile(key);
  		}
  	},

  	_invalidateAll: function () {
  		for (var z in this._levels) {
  			remove(this._levels[z].el);
  			this._onRemoveLevel(Number(z));
  			delete this._levels[z];
  		}
  		this._removeAllTiles();

  		this._tileZoom = undefined;
  	},

  	_retainParent: function (x, y, z, minZoom) {
  		var x2 = Math.floor(x / 2),
  		    y2 = Math.floor(y / 2),
  		    z2 = z - 1,
  		    coords2 = new Point(+x2, +y2);
  		coords2.z = +z2;

  		var key = this._tileCoordsToKey(coords2),
  		    tile = this._tiles[key];

  		if (tile && tile.active) {
  			tile.retain = true;
  			return true;

  		} else if (tile && tile.loaded) {
  			tile.retain = true;
  		}

  		if (z2 > minZoom) {
  			return this._retainParent(x2, y2, z2, minZoom);
  		}

  		return false;
  	},

  	_retainChildren: function (x, y, z, maxZoom) {

  		for (var i = 2 * x; i < 2 * x + 2; i++) {
  			for (var j = 2 * y; j < 2 * y + 2; j++) {

  				var coords = new Point(i, j);
  				coords.z = z + 1;

  				var key = this._tileCoordsToKey(coords),
  				    tile = this._tiles[key];

  				if (tile && tile.active) {
  					tile.retain = true;
  					continue;

  				} else if (tile && tile.loaded) {
  					tile.retain = true;
  				}

  				if (z + 1 < maxZoom) {
  					this._retainChildren(i, j, z + 1, maxZoom);
  				}
  			}
  		}
  	},

  	_resetView: function (e) {
  		var animating = e && (e.pinch || e.flyTo);
  		this._setView(this._map.getCenter(), this._map.getZoom(), animating, animating);
  	},

  	_animateZoom: function (e) {
  		this._setView(e.center, e.zoom, true, e.noUpdate);
  	},

  	_clampZoom: function (zoom) {
  		var options = this.options;

  		if (undefined !== options.minNativeZoom && zoom < options.minNativeZoom) {
  			return options.minNativeZoom;
  		}

  		if (undefined !== options.maxNativeZoom && options.maxNativeZoom < zoom) {
  			return options.maxNativeZoom;
  		}

  		return zoom;
  	},

  	_setView: function (center, zoom, noPrune, noUpdate) {
  		var tileZoom = Math.round(zoom);
  		if ((this.options.maxZoom !== undefined && tileZoom > this.options.maxZoom) ||
  		    (this.options.minZoom !== undefined && tileZoom < this.options.minZoom)) {
  			tileZoom = undefined;
  		} else {
  			tileZoom = this._clampZoom(tileZoom);
  		}

  		var tileZoomChanged = this.options.updateWhenZooming && (tileZoom !== this._tileZoom);

  		if (!noUpdate || tileZoomChanged) {

  			this._tileZoom = tileZoom;

  			if (this._abortLoading) {
  				this._abortLoading();
  			}

  			this._updateLevels();
  			this._resetGrid();

  			if (tileZoom !== undefined) {
  				this._update(center);
  			}

  			if (!noPrune) {
  				this._pruneTiles();
  			}

  			// Flag to prevent _updateOpacity from pruning tiles during
  			// a zoom anim or a pinch gesture
  			this._noPrune = !!noPrune;
  		}

  		this._setZoomTransforms(center, zoom);
  	},

  	_setZoomTransforms: function (center, zoom) {
  		for (var i in this._levels) {
  			this._setZoomTransform(this._levels[i], center, zoom);
  		}
  	},

  	_setZoomTransform: function (level, center, zoom) {
  		var scale = this._map.getZoomScale(zoom, level.zoom),
  		    translate = level.origin.multiplyBy(scale)
  		        .subtract(this._map._getNewPixelOrigin(center, zoom)).round();

  		if (Browser.any3d) {
  			setTransform(level.el, translate, scale);
  		} else {
  			setPosition(level.el, translate);
  		}
  	},

  	_resetGrid: function () {
  		var map = this._map,
  		    crs = map.options.crs,
  		    tileSize = this._tileSize = this.getTileSize(),
  		    tileZoom = this._tileZoom;

  		var bounds = this._map.getPixelWorldBounds(this._tileZoom);
  		if (bounds) {
  			this._globalTileRange = this._pxBoundsToTileRange(bounds);
  		}

  		this._wrapX = crs.wrapLng && !this.options.noWrap && [
  			Math.floor(map.project([0, crs.wrapLng[0]], tileZoom).x / tileSize.x),
  			Math.ceil(map.project([0, crs.wrapLng[1]], tileZoom).x / tileSize.y)
  		];
  		this._wrapY = crs.wrapLat && !this.options.noWrap && [
  			Math.floor(map.project([crs.wrapLat[0], 0], tileZoom).y / tileSize.x),
  			Math.ceil(map.project([crs.wrapLat[1], 0], tileZoom).y / tileSize.y)
  		];
  	},

  	_onMoveEnd: function () {
  		if (!this._map || this._map._animatingZoom) { return; }

  		this._update();
  	},

  	_getTiledPixelBounds: function (center) {
  		var map = this._map,
  		    mapZoom = map._animatingZoom ? Math.max(map._animateToZoom, map.getZoom()) : map.getZoom(),
  		    scale = map.getZoomScale(mapZoom, this._tileZoom),
  		    pixelCenter = map.project(center, this._tileZoom).floor(),
  		    halfSize = map.getSize().divideBy(scale * 2);

  		return new Bounds(pixelCenter.subtract(halfSize), pixelCenter.add(halfSize));
  	},

  	// Private method to load tiles in the grid's active zoom level according to map bounds
  	_update: function (center) {
  		var map = this._map;
  		if (!map) { return; }
  		var zoom = this._clampZoom(map.getZoom());

  		if (center === undefined) { center = map.getCenter(); }
  		if (this._tileZoom === undefined) { return; }	// if out of minzoom/maxzoom

  		var pixelBounds = this._getTiledPixelBounds(center),
  		    tileRange = this._pxBoundsToTileRange(pixelBounds),
  		    tileCenter = tileRange.getCenter(),
  		    queue = [],
  		    margin = this.options.keepBuffer,
  		    noPruneRange = new Bounds(tileRange.getBottomLeft().subtract([margin, -margin]),
  		                              tileRange.getTopRight().add([margin, -margin]));

  		// Sanity check: panic if the tile range contains Infinity somewhere.
  		if (!(isFinite(tileRange.min.x) &&
  		      isFinite(tileRange.min.y) &&
  		      isFinite(tileRange.max.x) &&
  		      isFinite(tileRange.max.y))) { throw new Error('Attempted to load an infinite number of tiles'); }

  		for (var key in this._tiles) {
  			var c = this._tiles[key].coords;
  			if (c.z !== this._tileZoom || !noPruneRange.contains(new Point(c.x, c.y))) {
  				this._tiles[key].current = false;
  			}
  		}

  		// _update just loads more tiles. If the tile zoom level differs too much
  		// from the map's, let _setView reset levels and prune old tiles.
  		if (Math.abs(zoom - this._tileZoom) > 1) { this._setView(center, zoom); return; }

  		// create a queue of coordinates to load tiles from
  		for (var j = tileRange.min.y; j <= tileRange.max.y; j++) {
  			for (var i = tileRange.min.x; i <= tileRange.max.x; i++) {
  				var coords = new Point(i, j);
  				coords.z = this._tileZoom;

  				if (!this._isValidTile(coords)) { continue; }

  				var tile = this._tiles[this._tileCoordsToKey(coords)];
  				if (tile) {
  					tile.current = true;
  				} else {
  					queue.push(coords);
  				}
  			}
  		}

  		// sort tile queue to load tiles in order of their distance to center
  		queue.sort(function (a, b) {
  			return a.distanceTo(tileCenter) - b.distanceTo(tileCenter);
  		});

  		if (queue.length !== 0) {
  			// if it's the first batch of tiles to load
  			if (!this._loading) {
  				this._loading = true;
  				// @event loading: Event
  				// Fired when the grid layer starts loading tiles.
  				this.fire('loading');
  			}

  			// create DOM fragment to append tiles in one batch
  			var fragment = document.createDocumentFragment();

  			for (i = 0; i < queue.length; i++) {
  				this._addTile(queue[i], fragment);
  			}

  			this._level.el.appendChild(fragment);
  		}
  	},

  	_isValidTile: function (coords) {
  		var crs = this._map.options.crs;

  		if (!crs.infinite) {
  			// don't load tile if it's out of bounds and not wrapped
  			var bounds = this._globalTileRange;
  			if ((!crs.wrapLng && (coords.x < bounds.min.x || coords.x > bounds.max.x)) ||
  			    (!crs.wrapLat && (coords.y < bounds.min.y || coords.y > bounds.max.y))) { return false; }
  		}

  		if (!this.options.bounds) { return true; }

  		// don't load tile if it doesn't intersect the bounds in options
  		var tileBounds = this._tileCoordsToBounds(coords);
  		return toLatLngBounds(this.options.bounds).overlaps(tileBounds);
  	},

  	_keyToBounds: function (key) {
  		return this._tileCoordsToBounds(this._keyToTileCoords(key));
  	},

  	_tileCoordsToNwSe: function (coords) {
  		var map = this._map,
  		    tileSize = this.getTileSize(),
  		    nwPoint = coords.scaleBy(tileSize),
  		    sePoint = nwPoint.add(tileSize),
  		    nw = map.unproject(nwPoint, coords.z),
  		    se = map.unproject(sePoint, coords.z);
  		return [nw, se];
  	},

  	// converts tile coordinates to its geographical bounds
  	_tileCoordsToBounds: function (coords) {
  		var bp = this._tileCoordsToNwSe(coords),
  		    bounds = new LatLngBounds(bp[0], bp[1]);

  		if (!this.options.noWrap) {
  			bounds = this._map.wrapLatLngBounds(bounds);
  		}
  		return bounds;
  	},
  	// converts tile coordinates to key for the tile cache
  	_tileCoordsToKey: function (coords) {
  		return coords.x + ':' + coords.y + ':' + coords.z;
  	},

  	// converts tile cache key to coordinates
  	_keyToTileCoords: function (key) {
  		var k = key.split(':'),
  		    coords = new Point(+k[0], +k[1]);
  		coords.z = +k[2];
  		return coords;
  	},

  	_removeTile: function (key) {
  		var tile = this._tiles[key];
  		if (!tile) { return; }

  		remove(tile.el);

  		delete this._tiles[key];

  		// @event tileunload: TileEvent
  		// Fired when a tile is removed (e.g. when a tile goes off the screen).
  		this.fire('tileunload', {
  			tile: tile.el,
  			coords: this._keyToTileCoords(key)
  		});
  	},

  	_initTile: function (tile) {
  		addClass(tile, 'leaflet-tile');

  		var tileSize = this.getTileSize();
  		tile.style.width = tileSize.x + 'px';
  		tile.style.height = tileSize.y + 'px';

  		tile.onselectstart = falseFn;
  		tile.onmousemove = falseFn;

  		// update opacity on tiles in IE7-8 because of filter inheritance problems
  		if (Browser.ielt9 && this.options.opacity < 1) {
  			setOpacity(tile, this.options.opacity);
  		}
  	},

  	_addTile: function (coords, container) {
  		var tilePos = this._getTilePos(coords),
  		    key = this._tileCoordsToKey(coords);

  		var tile = this.createTile(this._wrapCoords(coords), bind(this._tileReady, this, coords));

  		this._initTile(tile);

  		// if createTile is defined with a second argument ("done" callback),
  		// we know that tile is async and will be ready later; otherwise
  		if (this.createTile.length < 2) {
  			// mark tile as ready, but delay one frame for opacity animation to happen
  			requestAnimFrame(bind(this._tileReady, this, coords, null, tile));
  		}

  		setPosition(tile, tilePos);

  		// save tile in cache
  		this._tiles[key] = {
  			el: tile,
  			coords: coords,
  			current: true
  		};

  		container.appendChild(tile);
  		// @event tileloadstart: TileEvent
  		// Fired when a tile is requested and starts loading.
  		this.fire('tileloadstart', {
  			tile: tile,
  			coords: coords
  		});
  	},

  	_tileReady: function (coords, err, tile) {
  		if (err) {
  			// @event tileerror: TileErrorEvent
  			// Fired when there is an error loading a tile.
  			this.fire('tileerror', {
  				error: err,
  				tile: tile,
  				coords: coords
  			});
  		}

  		var key = this._tileCoordsToKey(coords);

  		tile = this._tiles[key];
  		if (!tile) { return; }

  		tile.loaded = +new Date();
  		if (this._map._fadeAnimated) {
  			setOpacity(tile.el, 0);
  			cancelAnimFrame(this._fadeFrame);
  			this._fadeFrame = requestAnimFrame(this._updateOpacity, this);
  		} else {
  			tile.active = true;
  			this._pruneTiles();
  		}

  		if (!err) {
  			addClass(tile.el, 'leaflet-tile-loaded');

  			// @event tileload: TileEvent
  			// Fired when a tile loads.
  			this.fire('tileload', {
  				tile: tile.el,
  				coords: coords
  			});
  		}

  		if (this._noTilesToLoad()) {
  			this._loading = false;
  			// @event load: Event
  			// Fired when the grid layer loaded all visible tiles.
  			this.fire('load');

  			if (Browser.ielt9 || !this._map._fadeAnimated) {
  				requestAnimFrame(this._pruneTiles, this);
  			} else {
  				// Wait a bit more than 0.2 secs (the duration of the tile fade-in)
  				// to trigger a pruning.
  				setTimeout(bind(this._pruneTiles, this), 250);
  			}
  		}
  	},

  	_getTilePos: function (coords) {
  		return coords.scaleBy(this.getTileSize()).subtract(this._level.origin);
  	},

  	_wrapCoords: function (coords) {
  		var newCoords = new Point(
  			this._wrapX ? wrapNum(coords.x, this._wrapX) : coords.x,
  			this._wrapY ? wrapNum(coords.y, this._wrapY) : coords.y);
  		newCoords.z = coords.z;
  		return newCoords;
  	},

  	_pxBoundsToTileRange: function (bounds) {
  		var tileSize = this.getTileSize();
  		return new Bounds(
  			bounds.min.unscaleBy(tileSize).floor(),
  			bounds.max.unscaleBy(tileSize).ceil().subtract([1, 1]));
  	},

  	_noTilesToLoad: function () {
  		for (var key in this._tiles) {
  			if (!this._tiles[key].loaded) { return false; }
  		}
  		return true;
  	}
  });

  // @factory L.gridLayer(options?: GridLayer options)
  // Creates a new instance of GridLayer with the supplied options.
  function gridLayer(options) {
  	return new GridLayer(options);
  }

  /*
   * @class TileLayer
   * @inherits GridLayer
   * @aka L.TileLayer
   * Used to load and display tile layers on the map. Note that most tile servers require attribution, which you can set under `Layer`. Extends `GridLayer`.
   *
   * @example
   *
   * ```js
   * L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png?{foo}', {foo: 'bar', attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);
   * ```
   *
   * @section URL template
   * @example
   *
   * A string of the following form:
   *
   * ```
   * 'https://{s}.somedomain.com/blabla/{z}/{x}/{y}{r}.png'
   * ```
   *
   * `{s}` means one of the available subdomains (used sequentially to help with browser parallel requests per domain limitation; subdomain values are specified in options; `a`, `b` or `c` by default, can be omitted), `{z}` — zoom level, `{x}` and `{y}` — tile coordinates. `{r}` can be used to add "&commat;2x" to the URL to load retina tiles.
   *
   * You can use custom keys in the template, which will be [evaluated](#util-template) from TileLayer options, like this:
   *
   * ```
   * L.tileLayer('https://{s}.somedomain.com/{foo}/{z}/{x}/{y}.png', {foo: 'bar'});
   * ```
   */


  var TileLayer = GridLayer.extend({

  	// @section
  	// @aka TileLayer options
  	options: {
  		// @option minZoom: Number = 0
  		// The minimum zoom level down to which this layer will be displayed (inclusive).
  		minZoom: 0,

  		// @option maxZoom: Number = 18
  		// The maximum zoom level up to which this layer will be displayed (inclusive).
  		maxZoom: 18,

  		// @option subdomains: String|String[] = 'abc'
  		// Subdomains of the tile service. Can be passed in the form of one string (where each letter is a subdomain name) or an array of strings.
  		subdomains: 'abc',

  		// @option errorTileUrl: String = ''
  		// URL to the tile image to show in place of the tile that failed to load.
  		errorTileUrl: '',

  		// @option zoomOffset: Number = 0
  		// The zoom number used in tile URLs will be offset with this value.
  		zoomOffset: 0,

  		// @option tms: Boolean = false
  		// If `true`, inverses Y axis numbering for tiles (turn this on for [TMS](https://en.wikipedia.org/wiki/Tile_Map_Service) services).
  		tms: false,

  		// @option zoomReverse: Boolean = false
  		// If set to true, the zoom number used in tile URLs will be reversed (`maxZoom - zoom` instead of `zoom`)
  		zoomReverse: false,

  		// @option detectRetina: Boolean = false
  		// If `true` and user is on a retina display, it will request four tiles of half the specified size and a bigger zoom level in place of one to utilize the high resolution.
  		detectRetina: false,

  		// @option crossOrigin: Boolean|String = false
  		// Whether the crossOrigin attribute will be added to the tiles.
  		// If a String is provided, all tiles will have their crossOrigin attribute set to the String provided. This is needed if you want to access tile pixel data.
  		// Refer to [CORS Settings](https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_settings_attributes) for valid String values.
  		crossOrigin: false,

  		// @option referrerPolicy: Boolean|String = false
  		// Whether the referrerPolicy attribute will be added to the tiles.
  		// If a String is provided, all tiles will have their referrerPolicy attribute set to the String provided.
  		// This may be needed if your map's rendering context has a strict default but your tile provider expects a valid referrer
  		// (e.g. to validate an API token).
  		// Refer to [HTMLImageElement.referrerPolicy](https://developer.mozilla.org/en-US/docs/Web/API/HTMLImageElement/referrerPolicy) for valid String values.
  		referrerPolicy: false
  	},

  	initialize: function (url, options) {

  		this._url = url;

  		options = setOptions(this, options);

  		// detecting retina displays, adjusting tileSize and zoom levels
  		if (options.detectRetina && Browser.retina && options.maxZoom > 0) {

  			options.tileSize = Math.floor(options.tileSize / 2);

  			if (!options.zoomReverse) {
  				options.zoomOffset++;
  				options.maxZoom = Math.max(options.minZoom, options.maxZoom - 1);
  			} else {
  				options.zoomOffset--;
  				options.minZoom = Math.min(options.maxZoom, options.minZoom + 1);
  			}

  			options.minZoom = Math.max(0, options.minZoom);
  		} else if (!options.zoomReverse) {
  			// make sure maxZoom is gte minZoom
  			options.maxZoom = Math.max(options.minZoom, options.maxZoom);
  		} else {
  			// make sure minZoom is lte maxZoom
  			options.minZoom = Math.min(options.maxZoom, options.minZoom);
  		}

  		if (typeof options.subdomains === 'string') {
  			options.subdomains = options.subdomains.split('');
  		}

  		this.on('tileunload', this._onTileRemove);
  	},

  	// @method setUrl(url: String, noRedraw?: Boolean): this
  	// Updates the layer's URL template and redraws it (unless `noRedraw` is set to `true`).
  	// If the URL does not change, the layer will not be redrawn unless
  	// the noRedraw parameter is set to false.
  	setUrl: function (url, noRedraw) {
  		if (this._url === url && noRedraw === undefined) {
  			noRedraw = true;
  		}

  		this._url = url;

  		if (!noRedraw) {
  			this.redraw();
  		}
  		return this;
  	},

  	// @method createTile(coords: Object, done?: Function): HTMLElement
  	// Called only internally, overrides GridLayer's [`createTile()`](#gridlayer-createtile)
  	// to return an `<img>` HTML element with the appropriate image URL given `coords`. The `done`
  	// callback is called when the tile has been loaded.
  	createTile: function (coords, done) {
  		var tile = document.createElement('img');

  		on(tile, 'load', bind(this._tileOnLoad, this, done, tile));
  		on(tile, 'error', bind(this._tileOnError, this, done, tile));

  		if (this.options.crossOrigin || this.options.crossOrigin === '') {
  			tile.crossOrigin = this.options.crossOrigin === true ? '' : this.options.crossOrigin;
  		}

  		// for this new option we follow the documented behavior
  		// more closely by only setting the property when string
  		if (typeof this.options.referrerPolicy === 'string') {
  			tile.referrerPolicy = this.options.referrerPolicy;
  		}

  		// The alt attribute is set to the empty string,
  		// allowing screen readers to ignore the decorative image tiles.
  		// https://www.w3.org/WAI/tutorials/images/decorative/
  		// https://www.w3.org/TR/html-aria/#el-img-empty-alt
  		tile.alt = '';

  		tile.src = this.getTileUrl(coords);

  		return tile;
  	},

  	// @section Extension methods
  	// @uninheritable
  	// Layers extending `TileLayer` might reimplement the following method.
  	// @method getTileUrl(coords: Object): String
  	// Called only internally, returns the URL for a tile given its coordinates.
  	// Classes extending `TileLayer` can override this function to provide custom tile URL naming schemes.
  	getTileUrl: function (coords) {
  		var data = {
  			r: Browser.retina ? '@2x' : '',
  			s: this._getSubdomain(coords),
  			x: coords.x,
  			y: coords.y,
  			z: this._getZoomForUrl()
  		};
  		if (this._map && !this._map.options.crs.infinite) {
  			var invertedY = this._globalTileRange.max.y - coords.y;
  			if (this.options.tms) {
  				data['y'] = invertedY;
  			}
  			data['-y'] = invertedY;
  		}

  		return template(this._url, extend(data, this.options));
  	},

  	_tileOnLoad: function (done, tile) {
  		// For https://github.com/Leaflet/Leaflet/issues/3332
  		if (Browser.ielt9) {
  			setTimeout(bind(done, this, null, tile), 0);
  		} else {
  			done(null, tile);
  		}
  	},

  	_tileOnError: function (done, tile, e) {
  		var errorUrl = this.options.errorTileUrl;
  		if (errorUrl && tile.getAttribute('src') !== errorUrl) {
  			tile.src = errorUrl;
  		}
  		done(e, tile);
  	},

  	_onTileRemove: function (e) {
  		e.tile.onload = null;
  	},

  	_getZoomForUrl: function () {
  		var zoom = this._tileZoom,
  		maxZoom = this.options.maxZoom,
  		zoomReverse = this.options.zoomReverse,
  		zoomOffset = this.options.zoomOffset;

  		if (zoomReverse) {
  			zoom = maxZoom - zoom;
  		}

  		return zoom + zoomOffset;
  	},

  	_getSubdomain: function (tilePoint) {
  		var index = Math.abs(tilePoint.x + tilePoint.y) % this.options.subdomains.length;
  		return this.options.subdomains[index];
  	},

  	// stops loading all tiles in the background layer
  	_abortLoading: function () {
  		var i, tile;
  		for (i in this._tiles) {
  			if (this._tiles[i].coords.z !== this._tileZoom) {
  				tile = this._tiles[i].el;

  				tile.onload = falseFn;
  				tile.onerror = falseFn;

  				if (!tile.complete) {
  					tile.src = emptyImageUrl;
  					var coords = this._tiles[i].coords;
  					remove(tile);
  					delete this._tiles[i];
  					// @event tileabort: TileEvent
  					// Fired when a tile was loading but is now not wanted.
  					this.fire('tileabort', {
  						tile: tile,
  						coords: coords
  					});
  				}
  			}
  		}
  	},

  	_removeTile: function (key) {
  		var tile = this._tiles[key];
  		if (!tile) { return; }

  		// Cancels any pending http requests associated with the tile
  		tile.el.setAttribute('src', emptyImageUrl);

  		return GridLayer.prototype._removeTile.call(this, key);
  	},

  	_tileReady: function (coords, err, tile) {
  		if (!this._map || (tile && tile.getAttribute('src') === emptyImageUrl)) {
  			return;
  		}

  		return GridLayer.prototype._tileReady.call(this, coords, err, tile);
  	}
  });


  // @factory L.tilelayer(urlTemplate: String, options?: TileLayer options)
  // Instantiates a tile layer object given a `URL template` and optionally an options object.

  function tileLayer(url, options) {
  	return new TileLayer(url, options);
  }

  /*
   * @class TileLayer.WMS
   * @inherits TileLayer
   * @aka L.TileLayer.WMS
   * Used to display [WMS](https://en.wikipedia.org/wiki/Web_Map_Service) services as tile layers on the map. Extends `TileLayer`.
   *
   * @example
   *
   * ```js
   * var nexrad = L.tileLayer.wms("http://mesonet.agron.iastate.edu/cgi-bin/wms/nexrad/n0r.cgi", {
   * 	layers: 'nexrad-n0r-900913',
   * 	format: 'image/png',
   * 	transparent: true,
   * 	attribution: "Weather data © 2012 IEM Nexrad"
   * });
   * ```
   */

  var TileLayerWMS = TileLayer.extend({

  	// @section
  	// @aka TileLayer.WMS options
  	// If any custom options not documented here are used, they will be sent to the
  	// WMS server as extra parameters in each request URL. This can be useful for
  	// [non-standard vendor WMS parameters](https://docs.geoserver.org/stable/en/user/services/wms/vendor.html).
  	defaultWmsParams: {
  		service: 'WMS',
  		request: 'GetMap',

  		// @option layers: String = ''
  		// **(required)** Comma-separated list of WMS layers to show.
  		layers: '',

  		// @option styles: String = ''
  		// Comma-separated list of WMS styles.
  		styles: '',

  		// @option format: String = 'image/jpeg'
  		// WMS image format (use `'image/png'` for layers with transparency).
  		format: 'image/jpeg',

  		// @option transparent: Boolean = false
  		// If `true`, the WMS service will return images with transparency.
  		transparent: false,

  		// @option version: String = '1.1.1'
  		// Version of the WMS service to use
  		version: '1.1.1'
  	},

  	options: {
  		// @option crs: CRS = null
  		// Coordinate Reference System to use for the WMS requests, defaults to
  		// map CRS. Don't change this if you're not sure what it means.
  		crs: null,

  		// @option uppercase: Boolean = false
  		// If `true`, WMS request parameter keys will be uppercase.
  		uppercase: false
  	},

  	initialize: function (url, options) {

  		this._url = url;

  		var wmsParams = extend({}, this.defaultWmsParams);

  		// all keys that are not TileLayer options go to WMS params
  		for (var i in options) {
  			if (!(i in this.options)) {
  				wmsParams[i] = options[i];
  			}
  		}

  		options = setOptions(this, options);

  		var realRetina = options.detectRetina && Browser.retina ? 2 : 1;
  		var tileSize = this.getTileSize();
  		wmsParams.width = tileSize.x * realRetina;
  		wmsParams.height = tileSize.y * realRetina;

  		this.wmsParams = wmsParams;
  	},

  	onAdd: function (map) {

  		this._crs = this.options.crs || map.options.crs;
  		this._wmsVersion = parseFloat(this.wmsParams.version);

  		var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs';
  		this.wmsParams[projectionKey] = this._crs.code;

  		TileLayer.prototype.onAdd.call(this, map);
  	},

  	getTileUrl: function (coords) {

  		var tileBounds = this._tileCoordsToNwSe(coords),
  		    crs = this._crs,
  		    bounds = toBounds(crs.project(tileBounds[0]), crs.project(tileBounds[1])),
  		    min = bounds.min,
  		    max = bounds.max,
  		    bbox = (this._wmsVersion >= 1.3 && this._crs === EPSG4326 ?
  		    [min.y, min.x, max.y, max.x] :
  		    [min.x, min.y, max.x, max.y]).join(','),
  		    url = TileLayer.prototype.getTileUrl.call(this, coords);
  		return url +
  			getParamString(this.wmsParams, url, this.options.uppercase) +
  			(this.options.uppercase ? '&BBOX=' : '&bbox=') + bbox;
  	},

  	// @method setParams(params: Object, noRedraw?: Boolean): this
  	// Merges an object with the new parameters and re-requests tiles on the current screen (unless `noRedraw` was set to true).
  	setParams: function (params, noRedraw) {

  		extend(this.wmsParams, params);

  		if (!noRedraw) {
  			this.redraw();
  		}

  		return this;
  	}
  });


  // @factory L.tileLayer.wms(baseUrl: String, options: TileLayer.WMS options)
  // Instantiates a WMS tile layer object given a base URL of the WMS service and a WMS parameters/options object.
  function tileLayerWMS(url, options) {
  	return new TileLayerWMS(url, options);
  }

  TileLayer.WMS = TileLayerWMS;
  tileLayer.wms = tileLayerWMS;

  /*
   * @class Renderer
   * @inherits Layer
   * @aka L.Renderer
   *
   * Base class for vector renderer implementations (`SVG`, `Canvas`). Handles the
   * DOM container of the renderer, its bounds, and its zoom animation.
   *
   * A `Renderer` works as an implicit layer group for all `Path`s - the renderer
   * itself can be added or removed to the map. All paths use a renderer, which can
   * be implicit (the map will decide the type of renderer and use it automatically)
   * or explicit (using the [`renderer`](#path-renderer) option of the path).
   *
   * Do not use this class directly, use `SVG` and `Canvas` instead.
   *
   * @event update: Event
   * Fired when the renderer updates its bounds, center and zoom, for example when
   * its map has moved
   */

  var Renderer = Layer.extend({

  	// @section
  	// @aka Renderer options
  	options: {
  		// @option padding: Number = 0.1
  		// How much to extend the clip area around the map view (relative to its size)
  		// e.g. 0.1 would be 10% of map view in each direction
  		padding: 0.1
  	},

  	initialize: function (options) {
  		setOptions(this, options);
  		stamp(this);
  		this._layers = this._layers || {};
  	},

  	onAdd: function () {
  		if (!this._container) {
  			this._initContainer(); // defined by renderer implementations

  			// always keep transform-origin as 0 0
  			addClass(this._container, 'leaflet-zoom-animated');
  		}

  		this.getPane().appendChild(this._container);
  		this._update();
  		this.on('update', this._updatePaths, this);
  	},

  	onRemove: function () {
  		this.off('update', this._updatePaths, this);
  		this._destroyContainer();
  	},

  	getEvents: function () {
  		var events = {
  			viewreset: this._reset,
  			zoom: this._onZoom,
  			moveend: this._update,
  			zoomend: this._onZoomEnd
  		};
  		if (this._zoomAnimated) {
  			events.zoomanim = this._onAnimZoom;
  		}
  		return events;
  	},

  	_onAnimZoom: function (ev) {
  		this._updateTransform(ev.center, ev.zoom);
  	},

  	_onZoom: function () {
  		this._updateTransform(this._map.getCenter(), this._map.getZoom());
  	},

  	_updateTransform: function (center, zoom) {
  		var scale = this._map.getZoomScale(zoom, this._zoom),
  		    viewHalf = this._map.getSize().multiplyBy(0.5 + this.options.padding),
  		    currentCenterPoint = this._map.project(this._center, zoom),

  		    topLeftOffset = viewHalf.multiplyBy(-scale).add(currentCenterPoint)
  				  .subtract(this._map._getNewPixelOrigin(center, zoom));

  		if (Browser.any3d) {
  			setTransform(this._container, topLeftOffset, scale);
  		} else {
  			setPosition(this._container, topLeftOffset);
  		}
  	},

  	_reset: function () {
  		this._update();
  		this._updateTransform(this._center, this._zoom);

  		for (var id in this._layers) {
  			this._layers[id]._reset();
  		}
  	},

  	_onZoomEnd: function () {
  		for (var id in this._layers) {
  			this._layers[id]._project();
  		}
  	},

  	_updatePaths: function () {
  		for (var id in this._layers) {
  			this._layers[id]._update();
  		}
  	},

  	_update: function () {
  		// Update pixel bounds of renderer container (for positioning/sizing/clipping later)
  		// Subclasses are responsible of firing the 'update' event.
  		var p = this.options.padding,
  		    size = this._map.getSize(),
  		    min = this._map.containerPointToLayerPoint(size.multiplyBy(-p)).round();

  		this._bounds = new Bounds(min, min.add(size.multiplyBy(1 + p * 2)).round());

  		this._center = this._map.getCenter();
  		this._zoom = this._map.getZoom();
  	}
  });

  /*
   * @class Canvas
   * @inherits Renderer
   * @aka L.Canvas
   *
   * Allows vector layers to be displayed with [`<canvas>`](https://developer.mozilla.org/docs/Web/API/Canvas_API).
   * Inherits `Renderer`.
   *
   * Due to [technical limitations](https://caniuse.com/canvas), Canvas is not
   * available in all web browsers, notably IE8, and overlapping geometries might
   * not display properly in some edge cases.
   *
   * @example
   *
   * Use Canvas by default for all paths in the map:
   *
   * ```js
   * var map = L.map('map', {
   * 	renderer: L.canvas()
   * });
   * ```
   *
   * Use a Canvas renderer with extra padding for specific vector geometries:
   *
   * ```js
   * var map = L.map('map');
   * var myRenderer = L.canvas({ padding: 0.5 });
   * var line = L.polyline( coordinates, { renderer: myRenderer } );
   * var circle = L.circle( center, { renderer: myRenderer } );
   * ```
   */

  var Canvas = Renderer.extend({

  	// @section
  	// @aka Canvas options
  	options: {
  		// @option tolerance: Number = 0
  		// How much to extend the click tolerance around a path/object on the map.
  		tolerance: 0
  	},

  	getEvents: function () {
  		var events = Renderer.prototype.getEvents.call(this);
  		events.viewprereset = this._onViewPreReset;
  		return events;
  	},

  	_onViewPreReset: function () {
  		// Set a flag so that a viewprereset+moveend+viewreset only updates&redraws once
  		this._postponeUpdatePaths = true;
  	},

  	onAdd: function () {
  		Renderer.prototype.onAdd.call(this);

  		// Redraw vectors since canvas is cleared upon removal,
  		// in case of removing the renderer itself from the map.
  		this._draw();
  	},

  	_initContainer: function () {
  		var container = this._container = document.createElement('canvas');

  		on(container, 'mousemove', this._onMouseMove, this);
  		on(container, 'click dblclick mousedown mouseup contextmenu', this._onClick, this);
  		on(container, 'mouseout', this._handleMouseOut, this);
  		container['_leaflet_disable_events'] = true;

  		this._ctx = container.getContext('2d');
  	},

  	_destroyContainer: function () {
  		cancelAnimFrame(this._redrawRequest);
  		delete this._ctx;
  		remove(this._container);
  		off(this._container);
  		delete this._container;
  	},

  	_updatePaths: function () {
  		if (this._postponeUpdatePaths) { return; }

  		var layer;
  		this._redrawBounds = null;
  		for (var id in this._layers) {
  			layer = this._layers[id];
  			layer._update();
  		}
  		this._redraw();
  	},

  	_update: function () {
  		if (this._map._animatingZoom && this._bounds) { return; }

  		Renderer.prototype._update.call(this);

  		var b = this._bounds,
  		    container = this._container,
  		    size = b.getSize(),
  		    m = Browser.retina ? 2 : 1;

  		setPosition(container, b.min);

  		// set canvas size (also clearing it); use double size on retina
  		container.width = m * size.x;
  		container.height = m * size.y;
  		container.style.width = size.x + 'px';
  		container.style.height = size.y + 'px';

  		if (Browser.retina) {
  			this._ctx.scale(2, 2);
  		}

  		// translate so we use the same path coordinates after canvas element moves
  		this._ctx.translate(-b.min.x, -b.min.y);

  		// Tell paths to redraw themselves
  		this.fire('update');
  	},

  	_reset: function () {
  		Renderer.prototype._reset.call(this);

  		if (this._postponeUpdatePaths) {
  			this._postponeUpdatePaths = false;
  			this._updatePaths();
  		}
  	},

  	_initPath: function (layer) {
  		this._updateDashArray(layer);
  		this._layers[stamp(layer)] = layer;

  		var order = layer._order = {
  			layer: layer,
  			prev: this._drawLast,
  			next: null
  		};
  		if (this._drawLast) { this._drawLast.next = order; }
  		this._drawLast = order;
  		this._drawFirst = this._drawFirst || this._drawLast;
  	},

  	_addPath: function (layer) {
  		this._requestRedraw(layer);
  	},

  	_removePath: function (layer) {
  		var order = layer._order;
  		var next = order.next;
  		var prev = order.prev;

  		if (next) {
  			next.prev = prev;
  		} else {
  			this._drawLast = prev;
  		}
  		if (prev) {
  			prev.next = next;
  		} else {
  			this._drawFirst = next;
  		}

  		delete layer._order;

  		delete this._layers[stamp(layer)];

  		this._requestRedraw(layer);
  	},

  	_updatePath: function (layer) {
  		// Redraw the union of the layer's old pixel
  		// bounds and the new pixel bounds.
  		this._extendRedrawBounds(layer);
  		layer._project();
  		layer._update();
  		// The redraw will extend the redraw bounds
  		// with the new pixel bounds.
  		this._requestRedraw(layer);
  	},

  	_updateStyle: function (layer) {
  		this._updateDashArray(layer);
  		this._requestRedraw(layer);
  	},

  	_updateDashArray: function (layer) {
  		if (typeof layer.options.dashArray === 'string') {
  			var parts = layer.options.dashArray.split(/[, ]+/),
  			    dashArray = [],
  			    dashValue,
  			    i;
  			for (i = 0; i < parts.length; i++) {
  				dashValue = Number(parts[i]);
  				// Ignore dash array containing invalid lengths
  				if (isNaN(dashValue)) { return; }
  				dashArray.push(dashValue);
  			}
  			layer.options._dashArray = dashArray;
  		} else {
  			layer.options._dashArray = layer.options.dashArray;
  		}
  	},

  	_requestRedraw: function (layer) {
  		if (!this._map) { return; }

  		this._extendRedrawBounds(layer);
  		this._redrawRequest = this._redrawRequest || requestAnimFrame(this._redraw, this);
  	},

  	_extendRedrawBounds: function (layer) {
  		if (layer._pxBounds) {
  			var padding = (layer.options.weight || 0) + 1;
  			this._redrawBounds = this._redrawBounds || new Bounds();
  			this._redrawBounds.extend(layer._pxBounds.min.subtract([padding, padding]));
  			this._redrawBounds.extend(layer._pxBounds.max.add([padding, padding]));
  		}
  	},

  	_redraw: function () {
  		this._redrawRequest = null;

  		if (this._redrawBounds) {
  			this._redrawBounds.min._floor();
  			this._redrawBounds.max._ceil();
  		}

  		this._clear(); // clear layers in redraw bounds
  		this._draw(); // draw layers

  		this._redrawBounds = null;
  	},

  	_clear: function () {
  		var bounds = this._redrawBounds;
  		if (bounds) {
  			var size = bounds.getSize();
  			this._ctx.clearRect(bounds.min.x, bounds.min.y, size.x, size.y);
  		} else {
  			this._ctx.save();
  			this._ctx.setTransform(1, 0, 0, 1, 0, 0);
  			this._ctx.clearRect(0, 0, this._container.width, this._container.height);
  			this._ctx.restore();
  		}
  	},

  	_draw: function () {
  		var layer, bounds = this._redrawBounds;
  		this._ctx.save();
  		if (bounds) {
  			var size = bounds.getSize();
  			this._ctx.beginPath();
  			this._ctx.rect(bounds.min.x, bounds.min.y, size.x, size.y);
  			this._ctx.clip();
  		}

  		this._drawing = true;

  		for (var order = this._drawFirst; order; order = order.next) {
  			layer = order.layer;
  			if (!bounds || (layer._pxBounds && layer._pxBounds.intersects(bounds))) {
  				layer._updatePath();
  			}
  		}

  		this._drawing = false;

  		this._ctx.restore();  // Restore state before clipping.
  	},

  	_updatePoly: function (layer, closed) {
  		if (!this._drawing) { return; }

  		var i, j, len2, p,
  		    parts = layer._parts,
  		    len = parts.length,
  		    ctx = this._ctx;

  		if (!len) { return; }

  		ctx.beginPath();

  		for (i = 0; i < len; i++) {
  			for (j = 0, len2 = parts[i].length; j < len2; j++) {
  				p = parts[i][j];
  				ctx[j ? 'lineTo' : 'moveTo'](p.x, p.y);
  			}
  			if (closed) {
  				ctx.closePath();
  			}
  		}

  		this._fillStroke(ctx, layer);

  		// TODO optimization: 1 fill/stroke for all features with equal style instead of 1 for each feature
  	},

  	_updateCircle: function (layer) {

  		if (!this._drawing || layer._empty()) { return; }

  		var p = layer._point,
  		    ctx = this._ctx,
  		    r = Math.max(Math.round(layer._radius), 1),
  		    s = (Math.max(Math.round(layer._radiusY), 1) || r) / r;

  		if (s !== 1) {
  			ctx.save();
  			ctx.scale(1, s);
  		}

  		ctx.beginPath();
  		ctx.arc(p.x, p.y / s, r, 0, Math.PI * 2, false);

  		if (s !== 1) {
  			ctx.restore();
  		}

  		this._fillStroke(ctx, layer);
  	},

  	_fillStroke: function (ctx, layer) {
  		var options = layer.options;

  		if (options.fill) {
  			ctx.globalAlpha = options.fillOpacity;
  			ctx.fillStyle = options.fillColor || options.color;
  			ctx.fill(options.fillRule || 'evenodd');
  		}

  		if (options.stroke && options.weight !== 0) {
  			if (ctx.setLineDash) {
  				ctx.setLineDash(layer.options && layer.options._dashArray || []);
  			}
  			ctx.globalAlpha = options.opacity;
  			ctx.lineWidth = options.weight;
  			ctx.strokeStyle = options.color;
  			ctx.lineCap = options.lineCap;
  			ctx.lineJoin = options.lineJoin;
  			ctx.stroke();
  		}
  	},

  	// Canvas obviously doesn't have mouse events for individual drawn objects,
  	// so we emulate that by calculating what's under the mouse on mousemove/click manually

  	_onClick: function (e) {
  		var point = this._map.mouseEventToLayerPoint(e), layer, clickedLayer;

  		for (var order = this._drawFirst; order; order = order.next) {
  			layer = order.layer;
  			if (layer.options.interactive && layer._containsPoint(point)) {
  				if (!(e.type === 'click' || e.type === 'preclick') || !this._map._draggableMoved(layer)) {
  					clickedLayer = layer;
  				}
  			}
  		}
  		this._fireEvent(clickedLayer ? [clickedLayer] : false, e);
  	},

  	_onMouseMove: function (e) {
  		if (!this._map || this._map.dragging.moving() || this._map._animatingZoom) { return; }

  		var point = this._map.mouseEventToLayerPoint(e);
  		this._handleMouseHover(e, point);
  	},


  	_handleMouseOut: function (e) {
  		var layer = this._hoveredLayer;
  		if (layer) {
  			// if we're leaving the layer, fire mouseout
  			removeClass(this._container, 'leaflet-interactive');
  			this._fireEvent([layer], e, 'mouseout');
  			this._hoveredLayer = null;
  			this._mouseHoverThrottled = false;
  		}
  	},

  	_handleMouseHover: function (e, point) {
  		if (this._mouseHoverThrottled) {
  			return;
  		}

  		var layer, candidateHoveredLayer;

  		for (var order = this._drawFirst; order; order = order.next) {
  			layer = order.layer;
  			if (layer.options.interactive && layer._containsPoint(point)) {
  				candidateHoveredLayer = layer;
  			}
  		}

  		if (candidateHoveredLayer !== this._hoveredLayer) {
  			this._handleMouseOut(e);

  			if (candidateHoveredLayer) {
  				addClass(this._container, 'leaflet-interactive'); // change cursor
  				this._fireEvent([candidateHoveredLayer], e, 'mouseover');
  				this._hoveredLayer = candidateHoveredLayer;
  			}
  		}

  		this._fireEvent(this._hoveredLayer ? [this._hoveredLayer] : false, e);

  		this._mouseHoverThrottled = true;
  		setTimeout(bind(function () {
  			this._mouseHoverThrottled = false;
  		}, this), 32);
  	},

  	_fireEvent: function (layers, e, type) {
  		this._map._fireDOMEvent(e, type || e.type, layers);
  	},

  	_bringToFront: function (layer) {
  		var order = layer._order;

  		if (!order) { return; }

  		var next = order.next;
  		var prev = order.prev;

  		if (next) {
  			next.prev = prev;
  		} else {
  			// Already last
  			return;
  		}
  		if (prev) {
  			prev.next = next;
  		} else if (next) {
  			// Update first entry unless this is the
  			// single entry
  			this._drawFirst = next;
  		}

  		order.prev = this._drawLast;
  		this._drawLast.next = order;

  		order.next = null;
  		this._drawLast = order;

  		this._requestRedraw(layer);
  	},

  	_bringToBack: function (layer) {
  		var order = layer._order;

  		if (!order) { return; }

  		var next = order.next;
  		var prev = order.prev;

  		if (prev) {
  			prev.next = next;
  		} else {
  			// Already first
  			return;
  		}
  		if (next) {
  			next.prev = prev;
  		} else if (prev) {
  			// Update last entry unless this is the
  			// single entry
  			this._drawLast = prev;
  		}

  		order.prev = null;

  		order.next = this._drawFirst;
  		this._drawFirst.prev = order;
  		this._drawFirst = order;

  		this._requestRedraw(layer);
  	}
  });

  // @factory L.canvas(options?: Renderer options)
  // Creates a Canvas renderer with the given options.
  function canvas(options) {
  	return Browser.canvas ? new Canvas(options) : null;
  }

  /*
   * Thanks to Dmitry Baranovsky and his Raphael library for inspiration!
   */


  var vmlCreate = (function () {
  	try {
  		document.namespaces.add('lvml', 'urn:schemas-microsoft-com:vml');
  		return function (name) {
  			return document.createElement('<lvml:' + name + ' class="lvml">');
  		};
  	} catch (e) {
  		// Do not return fn from catch block so `e` can be garbage collected
  		// See https://github.com/Leaflet/Leaflet/pull/7279
  	}
  	return function (name) {
  		return document.createElement('<' + name + ' xmlns="urn:schemas-microsoft.com:vml" class="lvml">');
  	};
  })();


  /*
   * @class SVG
   *
   *
   * VML was deprecated in 2012, which means VML functionality exists only for backwards compatibility
   * with old versions of Internet Explorer.
   */

  // mixin to redefine some SVG methods to handle VML syntax which is similar but with some differences
  var vmlMixin = {

  	_initContainer: function () {
  		this._container = create$1('div', 'leaflet-vml-container');
  	},

  	_update: function () {
  		if (this._map._animatingZoom) { return; }
  		Renderer.prototype._update.call(this);
  		this.fire('update');
  	},

  	_initPath: function (layer) {
  		var container = layer._container = vmlCreate('shape');

  		addClass(container, 'leaflet-vml-shape ' + (this.options.className || ''));

  		container.coordsize = '1 1';

  		layer._path = vmlCreate('path');
  		container.appendChild(layer._path);

  		this._updateStyle(layer);
  		this._layers[stamp(layer)] = layer;
  	},

  	_addPath: function (layer) {
  		var container = layer._container;
  		this._container.appendChild(container);

  		if (layer.options.interactive) {
  			layer.addInteractiveTarget(container);
  		}
  	},

  	_removePath: function (layer) {
  		var container = layer._container;
  		remove(container);
  		layer.removeInteractiveTarget(container);
  		delete this._layers[stamp(layer)];
  	},

  	_updateStyle: function (layer) {
  		var stroke = layer._stroke,
  		    fill = layer._fill,
  		    options = layer.options,
  		    container = layer._container;

  		container.stroked = !!options.stroke;
  		container.filled = !!options.fill;

  		if (options.stroke) {
  			if (!stroke) {
  				stroke = layer._stroke = vmlCreate('stroke');
  			}
  			container.appendChild(stroke);
  			stroke.weight = options.weight + 'px';
  			stroke.color = options.color;
  			stroke.opacity = options.opacity;

  			if (options.dashArray) {
  				stroke.dashStyle = isArray(options.dashArray) ?
  				    options.dashArray.join(' ') :
  				    options.dashArray.replace(/( *, *)/g, ' ');
  			} else {
  				stroke.dashStyle = '';
  			}
  			stroke.endcap = options.lineCap.replace('butt', 'flat');
  			stroke.joinstyle = options.lineJoin;

  		} else if (stroke) {
  			container.removeChild(stroke);
  			layer._stroke = null;
  		}

  		if (options.fill) {
  			if (!fill) {
  				fill = layer._fill = vmlCreate('fill');
  			}
  			container.appendChild(fill);
  			fill.color = options.fillColor || options.color;
  			fill.opacity = options.fillOpacity;

  		} else if (fill) {
  			container.removeChild(fill);
  			layer._fill = null;
  		}
  	},

  	_updateCircle: function (layer) {
  		var p = layer._point.round(),
  		    r = Math.round(layer._radius),
  		    r2 = Math.round(layer._radiusY || r);

  		this._setPath(layer, layer._empty() ? 'M0 0' :
  			'AL ' + p.x + ',' + p.y + ' ' + r + ',' + r2 + ' 0,' + (65535 * 360));
  	},

  	_setPath: function (layer, path) {
  		layer._path.v = path;
  	},

  	_bringToFront: function (layer) {
  		toFront(layer._container);
  	},

  	_bringToBack: function (layer) {
  		toBack(layer._container);
  	}
  };

  var create = Browser.vml ? vmlCreate : svgCreate;

  /*
   * @class SVG
   * @inherits Renderer
   * @aka L.SVG
   *
   * Allows vector layers to be displayed with [SVG](https://developer.mozilla.org/docs/Web/SVG).
   * Inherits `Renderer`.
   *
   * Due to [technical limitations](https://caniuse.com/svg), SVG is not
   * available in all web browsers, notably Android 2.x and 3.x.
   *
   * Although SVG is not available on IE7 and IE8, these browsers support
   * [VML](https://en.wikipedia.org/wiki/Vector_Markup_Language)
   * (a now deprecated technology), and the SVG renderer will fall back to VML in
   * this case.
   *
   * @example
   *
   * Use SVG by default for all paths in the map:
   *
   * ```js
   * var map = L.map('map', {
   * 	renderer: L.svg()
   * });
   * ```
   *
   * Use a SVG renderer with extra padding for specific vector geometries:
   *
   * ```js
   * var map = L.map('map');
   * var myRenderer = L.svg({ padding: 0.5 });
   * var line = L.polyline( coordinates, { renderer: myRenderer } );
   * var circle = L.circle( center, { renderer: myRenderer } );
   * ```
   */

  var SVG = Renderer.extend({

  	_initContainer: function () {
  		this._container = create('svg');

  		// makes it possible to click through svg root; we'll reset it back in individual paths
  		this._container.setAttribute('pointer-events', 'none');

  		this._rootGroup = create('g');
  		this._container.appendChild(this._rootGroup);
  	},

  	_destroyContainer: function () {
  		remove(this._container);
  		off(this._container);
  		delete this._container;
  		delete this._rootGroup;
  		delete this._svgSize;
  	},

  	_update: function () {
  		if (this._map._animatingZoom && this._bounds) { return; }

  		Renderer.prototype._update.call(this);

  		var b = this._bounds,
  		    size = b.getSize(),
  		    container = this._container;

  		// set size of svg-container if changed
  		if (!this._svgSize || !this._svgSize.equals(size)) {
  			this._svgSize = size;
  			container.setAttribute('width', size.x);
  			container.setAttribute('height', size.y);
  		}

  		// movement: update container viewBox so that we don't have to change coordinates of individual layers
  		setPosition(container, b.min);
  		container.setAttribute('viewBox', [b.min.x, b.min.y, size.x, size.y].join(' '));

  		this.fire('update');
  	},

  	// methods below are called by vector layers implementations

  	_initPath: function (layer) {
  		var path = layer._path = create('path');

  		// @namespace Path
  		// @option className: String = null
  		// Custom class name set on an element. Only for SVG renderer.
  		if (layer.options.className) {
  			addClass(path, layer.options.className);
  		}

  		if (layer.options.interactive) {
  			addClass(path, 'leaflet-interactive');
  		}

  		this._updateStyle(layer);
  		this._layers[stamp(layer)] = layer;
  	},

  	_addPath: function (layer) {
  		if (!this._rootGroup) { this._initContainer(); }
  		this._rootGroup.appendChild(layer._path);
  		layer.addInteractiveTarget(layer._path);
  	},

  	_removePath: function (layer) {
  		remove(layer._path);
  		layer.removeInteractiveTarget(layer._path);
  		delete this._layers[stamp(layer)];
  	},

  	_updatePath: function (layer) {
  		layer._project();
  		layer._update();
  	},

  	_updateStyle: function (layer) {
  		var path = layer._path,
  		    options = layer.options;

  		if (!path) { return; }

  		if (options.stroke) {
  			path.setAttribute('stroke', options.color);
  			path.setAttribute('stroke-opacity', options.opacity);
  			path.setAttribute('stroke-width', options.weight);
  			path.setAttribute('stroke-linecap', options.lineCap);
  			path.setAttribute('stroke-linejoin', options.lineJoin);

  			if (options.dashArray) {
  				path.setAttribute('stroke-dasharray', options.dashArray);
  			} else {
  				path.removeAttribute('stroke-dasharray');
  			}

  			if (options.dashOffset) {
  				path.setAttribute('stroke-dashoffset', options.dashOffset);
  			} else {
  				path.removeAttribute('stroke-dashoffset');
  			}
  		} else {
  			path.setAttribute('stroke', 'none');
  		}

  		if (options.fill) {
  			path.setAttribute('fill', options.fillColor || options.color);
  			path.setAttribute('fill-opacity', options.fillOpacity);
  			path.setAttribute('fill-rule', options.fillRule || 'evenodd');
  		} else {
  			path.setAttribute('fill', 'none');
  		}
  	},

  	_updatePoly: function (layer, closed) {
  		this._setPath(layer, pointsToPath(layer._parts, closed));
  	},

  	_updateCircle: function (layer) {
  		var p = layer._point,
  		    r = Math.max(Math.round(layer._radius), 1),
  		    r2 = Math.max(Math.round(layer._radiusY), 1) || r,
  		    arc = 'a' + r + ',' + r2 + ' 0 1,0 ';

  		// drawing a circle with two half-arcs
  		var d = layer._empty() ? 'M0 0' :
  			'M' + (p.x - r) + ',' + p.y +
  			arc + (r * 2) + ',0 ' +
  			arc + (-r * 2) + ',0 ';

  		this._setPath(layer, d);
  	},

  	_setPath: function (layer, path) {
  		layer._path.setAttribute('d', path);
  	},

  	// SVG does not have the concept of zIndex so we resort to changing the DOM order of elements
  	_bringToFront: function (layer) {
  		toFront(layer._path);
  	},

  	_bringToBack: function (layer) {
  		toBack(layer._path);
  	}
  });

  if (Browser.vml) {
  	SVG.include(vmlMixin);
  }

  // @namespace SVG
  // @factory L.svg(options?: Renderer options)
  // Creates a SVG renderer with the given options.
  function svg(options) {
  	return Browser.svg || Browser.vml ? new SVG(options) : null;
  }

  Map.include({
  	// @namespace Map; @method getRenderer(layer: Path): Renderer
  	// Returns the instance of `Renderer` that should be used to render the given
  	// `Path`. It will ensure that the `renderer` options of the map and paths
  	// are respected, and that the renderers do exist on the map.
  	getRenderer: function (layer) {
  		// @namespace Path; @option renderer: Renderer
  		// Use this specific instance of `Renderer` for this path. Takes
  		// precedence over the map's [default renderer](#map-renderer).
  		var renderer = layer.options.renderer || this._getPaneRenderer(layer.options.pane) || this.options.renderer || this._renderer;

  		if (!renderer) {
  			renderer = this._renderer = this._createRenderer();
  		}

  		if (!this.hasLayer(renderer)) {
  			this.addLayer(renderer);
  		}
  		return renderer;
  	},

  	_getPaneRenderer: function (name) {
  		if (name === 'overlayPane' || name === undefined) {
  			return false;
  		}

  		var renderer = this._paneRenderers[name];
  		if (renderer === undefined) {
  			renderer = this._createRenderer({pane: name});
  			this._paneRenderers[name] = renderer;
  		}
  		return renderer;
  	},

  	_createRenderer: function (options) {
  		// @namespace Map; @option preferCanvas: Boolean = false
  		// Whether `Path`s should be rendered on a `Canvas` renderer.
  		// By default, all `Path`s are rendered in a `SVG` renderer.
  		return (this.options.preferCanvas && canvas(options)) || svg(options);
  	}
  });

  /*
   * L.Rectangle extends Polygon and creates a rectangle when passed a LatLngBounds object.
   */

  /*
   * @class Rectangle
   * @aka L.Rectangle
   * @inherits Polygon
   *
   * A class for drawing rectangle overlays on a map. Extends `Polygon`.
   *
   * @example
   *
   * ```js
   * // define rectangle geographical bounds
   * var bounds = [[54.559322, -5.767822], [56.1210604, -3.021240]];
   *
   * // create an orange rectangle
   * L.rectangle(bounds, {color: "#ff7800", weight: 1}).addTo(map);
   *
   * // zoom the map to the rectangle bounds
   * map.fitBounds(bounds);
   * ```
   *
   */


  var Rectangle = Polygon.extend({
  	initialize: function (latLngBounds, options) {
  		Polygon.prototype.initialize.call(this, this._boundsToLatLngs(latLngBounds), options);
  	},

  	// @method setBounds(latLngBounds: LatLngBounds): this
  	// Redraws the rectangle with the passed bounds.
  	setBounds: function (latLngBounds) {
  		return this.setLatLngs(this._boundsToLatLngs(latLngBounds));
  	},

  	_boundsToLatLngs: function (latLngBounds) {
  		latLngBounds = toLatLngBounds(latLngBounds);
  		return [
  			latLngBounds.getSouthWest(),
  			latLngBounds.getNorthWest(),
  			latLngBounds.getNorthEast(),
  			latLngBounds.getSouthEast()
  		];
  	}
  });


  // @factory L.rectangle(latLngBounds: LatLngBounds, options?: Polyline options)
  function rectangle(latLngBounds, options) {
  	return new Rectangle(latLngBounds, options);
  }

  SVG.create = create;
  SVG.pointsToPath = pointsToPath;

  GeoJSON.geometryToLayer = geometryToLayer;
  GeoJSON.coordsToLatLng = coordsToLatLng;
  GeoJSON.coordsToLatLngs = coordsToLatLngs;
  GeoJSON.latLngToCoords = latLngToCoords;
  GeoJSON.latLngsToCoords = latLngsToCoords;
  GeoJSON.getFeature = getFeature;
  GeoJSON.asFeature = asFeature;

  /*
   * L.Handler.BoxZoom is used to add shift-drag zoom interaction to the map
   * (zoom to a selected bounding box), enabled by default.
   */

  // @namespace Map
  // @section Interaction Options
  Map.mergeOptions({
  	// @option boxZoom: Boolean = true
  	// Whether the map can be zoomed to a rectangular area specified by
  	// dragging the mouse while pressing the shift key.
  	boxZoom: true
  });

  var BoxZoom = Handler.extend({
  	initialize: function (map) {
  		this._map = map;
  		this._container = map._container;
  		this._pane = map._panes.overlayPane;
  		this._resetStateTimeout = 0;
  		map.on('unload', this._destroy, this);
  	},

  	addHooks: function () {
  		on(this._container, 'mousedown', this._onMouseDown, this);
  	},

  	removeHooks: function () {
  		off(this._container, 'mousedown', this._onMouseDown, this);
  	},

  	moved: function () {
  		return this._moved;
  	},

  	_destroy: function () {
  		remove(this._pane);
  		delete this._pane;
  	},

  	_resetState: function () {
  		this._resetStateTimeout = 0;
  		this._moved = false;
  	},

  	_clearDeferredResetState: function () {
  		if (this._resetStateTimeout !== 0) {
  			clearTimeout(this._resetStateTimeout);
  			this._resetStateTimeout = 0;
  		}
  	},

  	_onMouseDown: function (e) {
  		if (!e.shiftKey || ((e.which !== 1) && (e.button !== 1))) { return false; }

  		// Clear the deferred resetState if it hasn't executed yet, otherwise it
  		// will interrupt the interaction and orphan a box element in the container.
  		this._clearDeferredResetState();
  		this._resetState();

  		disableTextSelection();
  		disableImageDrag();

  		this._startPoint = this._map.mouseEventToContainerPoint(e);

  		on(document, {
  			contextmenu: stop,
  			mousemove: this._onMouseMove,
  			mouseup: this._onMouseUp,
  			keydown: this._onKeyDown
  		}, this);
  	},

  	_onMouseMove: function (e) {
  		if (!this._moved) {
  			this._moved = true;

  			this._box = create$1('div', 'leaflet-zoom-box', this._container);
  			addClass(this._container, 'leaflet-crosshair');

  			this._map.fire('boxzoomstart');
  		}

  		this._point = this._map.mouseEventToContainerPoint(e);

  		var bounds = new Bounds(this._point, this._startPoint),
  		    size = bounds.getSize();

  		setPosition(this._box, bounds.min);

  		this._box.style.width  = size.x + 'px';
  		this._box.style.height = size.y + 'px';
  	},

  	_finish: function () {
  		if (this._moved) {
  			remove(this._box);
  			removeClass(this._container, 'leaflet-crosshair');
  		}

  		enableTextSelection();
  		enableImageDrag();

  		off(document, {
  			contextmenu: stop,
  			mousemove: this._onMouseMove,
  			mouseup: this._onMouseUp,
  			keydown: this._onKeyDown
  		}, this);
  	},

  	_onMouseUp: function (e) {
  		if ((e.which !== 1) && (e.button !== 1)) { return; }

  		this._finish();

  		if (!this._moved) { return; }
  		// Postpone to next JS tick so internal click event handling
  		// still see it as "moved".
  		this._clearDeferredResetState();
  		this._resetStateTimeout = setTimeout(bind(this._resetState, this), 0);

  		var bounds = new LatLngBounds(
  		        this._map.containerPointToLatLng(this._startPoint),
  		        this._map.containerPointToLatLng(this._point));

  		this._map
  			.fitBounds(bounds)
  			.fire('boxzoomend', {boxZoomBounds: bounds});
  	},

  	_onKeyDown: function (e) {
  		if (e.keyCode === 27) {
  			this._finish();
  			this._clearDeferredResetState();
  			this._resetState();
  		}
  	}
  });

  // @section Handlers
  // @property boxZoom: Handler
  // Box (shift-drag with mouse) zoom handler.
  Map.addInitHook('addHandler', 'boxZoom', BoxZoom);

  /*
   * L.Handler.DoubleClickZoom is used to handle double-click zoom on the map, enabled by default.
   */

  // @namespace Map
  // @section Interaction Options

  Map.mergeOptions({
  	// @option doubleClickZoom: Boolean|String = true
  	// Whether the map can be zoomed in by double clicking on it and
  	// zoomed out by double clicking while holding shift. If passed
  	// `'center'`, double-click zoom will zoom to the center of the
  	//  view regardless of where the mouse was.
  	doubleClickZoom: true
  });

  var DoubleClickZoom = Handler.extend({
  	addHooks: function () {
  		this._map.on('dblclick', this._onDoubleClick, this);
  	},

  	removeHooks: function () {
  		this._map.off('dblclick', this._onDoubleClick, this);
  	},

  	_onDoubleClick: function (e) {
  		var map = this._map,
  		    oldZoom = map.getZoom(),
  		    delta = map.options.zoomDelta,
  		    zoom = e.originalEvent.shiftKey ? oldZoom - delta : oldZoom + delta;

  		if (map.options.doubleClickZoom === 'center') {
  			map.setZoom(zoom);
  		} else {
  			map.setZoomAround(e.containerPoint, zoom);
  		}
  	}
  });

  // @section Handlers
  //
  // Map properties include interaction handlers that allow you to control
  // interaction behavior in runtime, enabling or disabling certain features such
  // as dragging or touch zoom (see `Handler` methods). For example:
  //
  // ```js
  // map.doubleClickZoom.disable();
  // ```
  //
  // @property doubleClickZoom: Handler
  // Double click zoom handler.
  Map.addInitHook('addHandler', 'doubleClickZoom', DoubleClickZoom);

  /*
   * L.Handler.MapDrag is used to make the map draggable (with panning inertia), enabled by default.
   */

  // @namespace Map
  // @section Interaction Options
  Map.mergeOptions({
  	// @option dragging: Boolean = true
  	// Whether the map is draggable with mouse/touch or not.
  	dragging: true,

  	// @section Panning Inertia Options
  	// @option inertia: Boolean = *
  	// If enabled, panning of the map will have an inertia effect where
  	// the map builds momentum while dragging and continues moving in
  	// the same direction for some time. Feels especially nice on touch
  	// devices. Enabled by default.
  	inertia: true,

  	// @option inertiaDeceleration: Number = 3000
  	// The rate with which the inertial movement slows down, in pixels/second².
  	inertiaDeceleration: 3400, // px/s^2

  	// @option inertiaMaxSpeed: Number = Infinity
  	// Max speed of the inertial movement, in pixels/second.
  	inertiaMaxSpeed: Infinity, // px/s

  	// @option easeLinearity: Number = 0.2
  	easeLinearity: 0.2,

  	// TODO refactor, move to CRS
  	// @option worldCopyJump: Boolean = false
  	// With this option enabled, the map tracks when you pan to another "copy"
  	// of the world and seamlessly jumps to the original one so that all overlays
  	// like markers and vector layers are still visible.
  	worldCopyJump: false,

  	// @option maxBoundsViscosity: Number = 0.0
  	// If `maxBounds` is set, this option will control how solid the bounds
  	// are when dragging the map around. The default value of `0.0` allows the
  	// user to drag outside the bounds at normal speed, higher values will
  	// slow down map dragging outside bounds, and `1.0` makes the bounds fully
  	// solid, preventing the user from dragging outside the bounds.
  	maxBoundsViscosity: 0.0
  });

  var Drag = Handler.extend({
  	addHooks: function () {
  		if (!this._draggable) {
  			var map = this._map;

  			this._draggable = new Draggable(map._mapPane, map._container);

  			this._draggable.on({
  				dragstart: this._onDragStart,
  				drag: this._onDrag,
  				dragend: this._onDragEnd
  			}, this);

  			this._draggable.on('predrag', this._onPreDragLimit, this);
  			if (map.options.worldCopyJump) {
  				this._draggable.on('predrag', this._onPreDragWrap, this);
  				map.on('zoomend', this._onZoomEnd, this);

  				map.whenReady(this._onZoomEnd, this);
  			}
  		}
  		addClass(this._map._container, 'leaflet-grab leaflet-touch-drag');
  		this._draggable.enable();
  		this._positions = [];
  		this._times = [];
  	},

  	removeHooks: function () {
  		removeClass(this._map._container, 'leaflet-grab');
  		removeClass(this._map._container, 'leaflet-touch-drag');
  		this._draggable.disable();
  	},

  	moved: function () {
  		return this._draggable && this._draggable._moved;
  	},

  	moving: function () {
  		return this._draggable && this._draggable._moving;
  	},

  	_onDragStart: function () {
  		var map = this._map;

  		map._stop();
  		if (this._map.options.maxBounds && this._map.options.maxBoundsViscosity) {
  			var bounds = toLatLngBounds(this._map.options.maxBounds);

  			this._offsetLimit = toBounds(
  				this._map.latLngToContainerPoint(bounds.getNorthWest()).multiplyBy(-1),
  				this._map.latLngToContainerPoint(bounds.getSouthEast()).multiplyBy(-1)
  					.add(this._map.getSize()));

  			this._viscosity = Math.min(1.0, Math.max(0.0, this._map.options.maxBoundsViscosity));
  		} else {
  			this._offsetLimit = null;
  		}

  		map
  		    .fire('movestart')
  		    .fire('dragstart');

  		if (map.options.inertia) {
  			this._positions = [];
  			this._times = [];
  		}
  	},

  	_onDrag: function (e) {
  		if (this._map.options.inertia) {
  			var time = this._lastTime = +new Date(),
  			    pos = this._lastPos = this._draggable._absPos || this._draggable._newPos;

  			this._positions.push(pos);
  			this._times.push(time);

  			this._prunePositions(time);
  		}

  		this._map
  		    .fire('move', e)
  		    .fire('drag', e);
  	},

  	_prunePositions: function (time) {
  		while (this._positions.length > 1 && time - this._times[0] > 50) {
  			this._positions.shift();
  			this._times.shift();
  		}
  	},

  	_onZoomEnd: function () {
  		var pxCenter = this._map.getSize().divideBy(2),
  		    pxWorldCenter = this._map.latLngToLayerPoint([0, 0]);

  		this._initialWorldOffset = pxWorldCenter.subtract(pxCenter).x;
  		this._worldWidth = this._map.getPixelWorldBounds().getSize().x;
  	},

  	_viscousLimit: function (value, threshold) {
  		return value - (value - threshold) * this._viscosity;
  	},

  	_onPreDragLimit: function () {
  		if (!this._viscosity || !this._offsetLimit) { return; }

  		var offset = this._draggable._newPos.subtract(this._draggable._startPos);

  		var limit = this._offsetLimit;
  		if (offset.x < limit.min.x) { offset.x = this._viscousLimit(offset.x, limit.min.x); }
  		if (offset.y < limit.min.y) { offset.y = this._viscousLimit(offset.y, limit.min.y); }
  		if (offset.x > limit.max.x) { offset.x = this._viscousLimit(offset.x, limit.max.x); }
  		if (offset.y > limit.max.y) { offset.y = this._viscousLimit(offset.y, limit.max.y); }

  		this._draggable._newPos = this._draggable._startPos.add(offset);
  	},

  	_onPreDragWrap: function () {
  		// TODO refactor to be able to adjust map pane position after zoom
  		var worldWidth = this._worldWidth,
  		    halfWidth = Math.round(worldWidth / 2),
  		    dx = this._initialWorldOffset,
  		    x = this._draggable._newPos.x,
  		    newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
  		    newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
  		    newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;

  		this._draggable._absPos = this._draggable._newPos.clone();
  		this._draggable._newPos.x = newX;
  	},

  	_onDragEnd: function (e) {
  		var map = this._map,
  		    options = map.options,

  		    noInertia = !options.inertia || e.noInertia || this._times.length < 2;

  		map.fire('dragend', e);

  		if (noInertia) {
  			map.fire('moveend');

  		} else {
  			this._prunePositions(+new Date());

  			var direction = this._lastPos.subtract(this._positions[0]),
  			    duration = (this._lastTime - this._times[0]) / 1000,
  			    ease = options.easeLinearity,

  			    speedVector = direction.multiplyBy(ease / duration),
  			    speed = speedVector.distanceTo([0, 0]),

  			    limitedSpeed = Math.min(options.inertiaMaxSpeed, speed),
  			    limitedSpeedVector = speedVector.multiplyBy(limitedSpeed / speed),

  			    decelerationDuration = limitedSpeed / (options.inertiaDeceleration * ease),
  			    offset = limitedSpeedVector.multiplyBy(-decelerationDuration / 2).round();

  			if (!offset.x && !offset.y) {
  				map.fire('moveend');

  			} else {
  				offset = map._limitOffset(offset, map.options.maxBounds);

  				requestAnimFrame(function () {
  					map.panBy(offset, {
  						duration: decelerationDuration,
  						easeLinearity: ease,
  						noMoveStart: true,
  						animate: true
  					});
  				});
  			}
  		}
  	}
  });

  // @section Handlers
  // @property dragging: Handler
  // Map dragging handler (by both mouse and touch).
  Map.addInitHook('addHandler', 'dragging', Drag);

  /*
   * L.Map.Keyboard is handling keyboard interaction with the map, enabled by default.
   */

  // @namespace Map
  // @section Keyboard Navigation Options
  Map.mergeOptions({
  	// @option keyboard: Boolean = true
  	// Makes the map focusable and allows users to navigate the map with keyboard
  	// arrows and `+`/`-` keys.
  	keyboard: true,

  	// @option keyboardPanDelta: Number = 80
  	// Amount of pixels to pan when pressing an arrow key.
  	keyboardPanDelta: 80
  });

  var Keyboard = Handler.extend({

  	keyCodes: {
  		left:    [37],
  		right:   [39],
  		down:    [40],
  		up:      [38],
  		zoomIn:  [187, 107, 61, 171],
  		zoomOut: [189, 109, 54, 173]
  	},

  	initialize: function (map) {
  		this._map = map;

  		this._setPanDelta(map.options.keyboardPanDelta);
  		this._setZoomDelta(map.options.zoomDelta);
  	},

  	addHooks: function () {
  		var container = this._map._container;

  		// make the container focusable by tabbing
  		if (container.tabIndex <= 0) {
  			container.tabIndex = '0';
  		}

  		on(container, {
  			focus: this._onFocus,
  			blur: this._onBlur,
  			mousedown: this._onMouseDown
  		}, this);

  		this._map.on({
  			focus: this._addHooks,
  			blur: this._removeHooks
  		}, this);
  	},

  	removeHooks: function () {
  		this._removeHooks();

  		off(this._map._container, {
  			focus: this._onFocus,
  			blur: this._onBlur,
  			mousedown: this._onMouseDown
  		}, this);

  		this._map.off({
  			focus: this._addHooks,
  			blur: this._removeHooks
  		}, this);
  	},

  	_onMouseDown: function () {
  		if (this._focused) { return; }

  		var body = document.body,
  		    docEl = document.documentElement,
  		    top = body.scrollTop || docEl.scrollTop,
  		    left = body.scrollLeft || docEl.scrollLeft;

  		this._map._container.focus();

  		window.scrollTo(left, top);
  	},

  	_onFocus: function () {
  		this._focused = true;
  		this._map.fire('focus');
  	},

  	_onBlur: function () {
  		this._focused = false;
  		this._map.fire('blur');
  	},

  	_setPanDelta: function (panDelta) {
  		var keys = this._panKeys = {},
  		    codes = this.keyCodes,
  		    i, len;

  		for (i = 0, len = codes.left.length; i < len; i++) {
  			keys[codes.left[i]] = [-1 * panDelta, 0];
  		}
  		for (i = 0, len = codes.right.length; i < len; i++) {
  			keys[codes.right[i]] = [panDelta, 0];
  		}
  		for (i = 0, len = codes.down.length; i < len; i++) {
  			keys[codes.down[i]] = [0, panDelta];
  		}
  		for (i = 0, len = codes.up.length; i < len; i++) {
  			keys[codes.up[i]] = [0, -1 * panDelta];
  		}
  	},

  	_setZoomDelta: function (zoomDelta) {
  		var keys = this._zoomKeys = {},
  		    codes = this.keyCodes,
  		    i, len;

  		for (i = 0, len = codes.zoomIn.length; i < len; i++) {
  			keys[codes.zoomIn[i]] = zoomDelta;
  		}
  		for (i = 0, len = codes.zoomOut.length; i < len; i++) {
  			keys[codes.zoomOut[i]] = -zoomDelta;
  		}
  	},

  	_addHooks: function () {
  		on(document, 'keydown', this._onKeyDown, this);
  	},

  	_removeHooks: function () {
  		off(document, 'keydown', this._onKeyDown, this);
  	},

  	_onKeyDown: function (e) {
  		if (e.altKey || e.ctrlKey || e.metaKey) { return; }

  		var key = e.keyCode,
  		    map = this._map,
  		    offset;

  		if (key in this._panKeys) {
  			if (!map._panAnim || !map._panAnim._inProgress) {
  				offset = this._panKeys[key];
  				if (e.shiftKey) {
  					offset = toPoint(offset).multiplyBy(3);
  				}

  				if (map.options.maxBounds) {
  					offset = map._limitOffset(toPoint(offset), map.options.maxBounds);
  				}

  				if (map.options.worldCopyJump) {
  					var newLatLng = map.wrapLatLng(map.unproject(map.project(map.getCenter()).add(offset)));
  					map.panTo(newLatLng);
  				} else {
  					map.panBy(offset);
  				}
  			}
  		} else if (key in this._zoomKeys) {
  			map.setZoom(map.getZoom() + (e.shiftKey ? 3 : 1) * this._zoomKeys[key]);

  		} else if (key === 27 && map._popup && map._popup.options.closeOnEscapeKey) {
  			map.closePopup();

  		} else {
  			return;
  		}

  		stop(e);
  	}
  });

  // @section Handlers
  // @section Handlers
  // @property keyboard: Handler
  // Keyboard navigation handler.
  Map.addInitHook('addHandler', 'keyboard', Keyboard);

  /*
   * L.Handler.ScrollWheelZoom is used by L.Map to enable mouse scroll wheel zoom on the map.
   */

  // @namespace Map
  // @section Interaction Options
  Map.mergeOptions({
  	// @section Mouse wheel options
  	// @option scrollWheelZoom: Boolean|String = true
  	// Whether the map can be zoomed by using the mouse wheel. If passed `'center'`,
  	// it will zoom to the center of the view regardless of where the mouse was.
  	scrollWheelZoom: true,

  	// @option wheelDebounceTime: Number = 40
  	// Limits the rate at which a wheel can fire (in milliseconds). By default
  	// user can't zoom via wheel more often than once per 40 ms.
  	wheelDebounceTime: 40,

  	// @option wheelPxPerZoomLevel: Number = 60
  	// How many scroll pixels (as reported by [L.DomEvent.getWheelDelta](#domevent-getwheeldelta))
  	// mean a change of one full zoom level. Smaller values will make wheel-zooming
  	// faster (and vice versa).
  	wheelPxPerZoomLevel: 60
  });

  var ScrollWheelZoom = Handler.extend({
  	addHooks: function () {
  		on(this._map._container, 'wheel', this._onWheelScroll, this);

  		this._delta = 0;
  	},

  	removeHooks: function () {
  		off(this._map._container, 'wheel', this._onWheelScroll, this);
  	},

  	_onWheelScroll: function (e) {
  		var delta = getWheelDelta(e);

  		var debounce = this._map.options.wheelDebounceTime;

  		this._delta += delta;
  		this._lastMousePos = this._map.mouseEventToContainerPoint(e);

  		if (!this._startTime) {
  			this._startTime = +new Date();
  		}

  		var left = Math.max(debounce - (+new Date() - this._startTime), 0);

  		clearTimeout(this._timer);
  		this._timer = setTimeout(bind(this._performZoom, this), left);

  		stop(e);
  	},

  	_performZoom: function () {
  		var map = this._map,
  		    zoom = map.getZoom(),
  		    snap = this._map.options.zoomSnap || 0;

  		map._stop(); // stop panning and fly animations if any

  		// map the delta with a sigmoid function to -4..4 range leaning on -1..1
  		var d2 = this._delta / (this._map.options.wheelPxPerZoomLevel * 4),
  		    d3 = 4 * Math.log(2 / (1 + Math.exp(-Math.abs(d2)))) / Math.LN2,
  		    d4 = snap ? Math.ceil(d3 / snap) * snap : d3,
  		    delta = map._limitZoom(zoom + (this._delta > 0 ? d4 : -d4)) - zoom;

  		this._delta = 0;
  		this._startTime = null;

  		if (!delta) { return; }

  		if (map.options.scrollWheelZoom === 'center') {
  			map.setZoom(zoom + delta);
  		} else {
  			map.setZoomAround(this._lastMousePos, zoom + delta);
  		}
  	}
  });

  // @section Handlers
  // @property scrollWheelZoom: Handler
  // Scroll wheel zoom handler.
  Map.addInitHook('addHandler', 'scrollWheelZoom', ScrollWheelZoom);

  /*
   * L.Map.TapHold is used to simulate `contextmenu` event on long hold,
   * which otherwise is not fired by mobile Safari.
   */

  var tapHoldDelay = 600;

  // @namespace Map
  // @section Interaction Options
  Map.mergeOptions({
  	// @section Touch interaction options
  	// @option tapHold: Boolean
  	// Enables simulation of `contextmenu` event, default is `true` for mobile Safari.
  	tapHold: Browser.touchNative && Browser.safari && Browser.mobile,

  	// @option tapTolerance: Number = 15
  	// The max number of pixels a user can shift his finger during touch
  	// for it to be considered a valid tap.
  	tapTolerance: 15
  });

  var TapHold = Handler.extend({
  	addHooks: function () {
  		on(this._map._container, 'touchstart', this._onDown, this);
  	},

  	removeHooks: function () {
  		off(this._map._container, 'touchstart', this._onDown, this);
  	},

  	_onDown: function (e) {
  		clearTimeout(this._holdTimeout);
  		if (e.touches.length !== 1) { return; }

  		var first = e.touches[0];
  		this._startPos = this._newPos = new Point(first.clientX, first.clientY);

  		this._holdTimeout = setTimeout(bind(function () {
  			this._cancel();
  			if (!this._isTapValid()) { return; }

  			// prevent simulated mouse events https://w3c.github.io/touch-events/#mouse-events
  			on(document, 'touchend', preventDefault);
  			on(document, 'touchend touchcancel', this._cancelClickPrevent);
  			this._simulateEvent('contextmenu', first);
  		}, this), tapHoldDelay);

  		on(document, 'touchend touchcancel contextmenu', this._cancel, this);
  		on(document, 'touchmove', this._onMove, this);
  	},

  	_cancelClickPrevent: function cancelClickPrevent() {
  		off(document, 'touchend', preventDefault);
  		off(document, 'touchend touchcancel', cancelClickPrevent);
  	},

  	_cancel: function () {
  		clearTimeout(this._holdTimeout);
  		off(document, 'touchend touchcancel contextmenu', this._cancel, this);
  		off(document, 'touchmove', this._onMove, this);
  	},

  	_onMove: function (e) {
  		var first = e.touches[0];
  		this._newPos = new Point(first.clientX, first.clientY);
  	},

  	_isTapValid: function () {
  		return this._newPos.distanceTo(this._startPos) <= this._map.options.tapTolerance;
  	},

  	_simulateEvent: function (type, e) {
  		var simulatedEvent = new MouseEvent(type, {
  			bubbles: true,
  			cancelable: true,
  			view: window,
  			// detail: 1,
  			screenX: e.screenX,
  			screenY: e.screenY,
  			clientX: e.clientX,
  			clientY: e.clientY,
  			// button: 2,
  			// buttons: 2
  		});

  		simulatedEvent._simulated = true;

  		e.target.dispatchEvent(simulatedEvent);
  	}
  });

  // @section Handlers
  // @property tapHold: Handler
  // Long tap handler to simulate `contextmenu` event (useful in mobile Safari).
  Map.addInitHook('addHandler', 'tapHold', TapHold);

  /*
   * L.Handler.TouchZoom is used by L.Map to add pinch zoom on supported mobile browsers.
   */

  // @namespace Map
  // @section Interaction Options
  Map.mergeOptions({
  	// @section Touch interaction options
  	// @option touchZoom: Boolean|String = *
  	// Whether the map can be zoomed by touch-dragging with two fingers. If
  	// passed `'center'`, it will zoom to the center of the view regardless of
  	// where the touch events (fingers) were. Enabled for touch-capable web
  	// browsers.
  	touchZoom: Browser.touch,

  	// @option bounceAtZoomLimits: Boolean = true
  	// Set it to false if you don't want the map to zoom beyond min/max zoom
  	// and then bounce back when pinch-zooming.
  	bounceAtZoomLimits: true
  });

  var TouchZoom = Handler.extend({
  	addHooks: function () {
  		addClass(this._map._container, 'leaflet-touch-zoom');
  		on(this._map._container, 'touchstart', this._onTouchStart, this);
  	},

  	removeHooks: function () {
  		removeClass(this._map._container, 'leaflet-touch-zoom');
  		off(this._map._container, 'touchstart', this._onTouchStart, this);
  	},

  	_onTouchStart: function (e) {
  		var map = this._map;
  		if (!e.touches || e.touches.length !== 2 || map._animatingZoom || this._zooming) { return; }

  		var p1 = map.mouseEventToContainerPoint(e.touches[0]),
  		    p2 = map.mouseEventToContainerPoint(e.touches[1]);

  		this._centerPoint = map.getSize()._divideBy(2);
  		this._startLatLng = map.containerPointToLatLng(this._centerPoint);
  		if (map.options.touchZoom !== 'center') {
  			this._pinchStartLatLng = map.containerPointToLatLng(p1.add(p2)._divideBy(2));
  		}

  		this._startDist = p1.distanceTo(p2);
  		this._startZoom = map.getZoom();

  		this._moved = false;
  		this._zooming = true;

  		map._stop();

  		on(document, 'touchmove', this._onTouchMove, this);
  		on(document, 'touchend touchcancel', this._onTouchEnd, this);

  		preventDefault(e);
  	},

  	_onTouchMove: function (e) {
  		if (!e.touches || e.touches.length !== 2 || !this._zooming) { return; }

  		var map = this._map,
  		    p1 = map.mouseEventToContainerPoint(e.touches[0]),
  		    p2 = map.mouseEventToContainerPoint(e.touches[1]),
  		    scale = p1.distanceTo(p2) / this._startDist;

  		this._zoom = map.getScaleZoom(scale, this._startZoom);

  		if (!map.options.bounceAtZoomLimits && (
  			(this._zoom < map.getMinZoom() && scale < 1) ||
  			(this._zoom > map.getMaxZoom() && scale > 1))) {
  			this._zoom = map._limitZoom(this._zoom);
  		}

  		if (map.options.touchZoom === 'center') {
  			this._center = this._startLatLng;
  			if (scale === 1) { return; }
  		} else {
  			// Get delta from pinch to center, so centerLatLng is delta applied to initial pinchLatLng
  			var delta = p1._add(p2)._divideBy(2)._subtract(this._centerPoint);
  			if (scale === 1 && delta.x === 0 && delta.y === 0) { return; }
  			this._center = map.unproject(map.project(this._pinchStartLatLng, this._zoom).subtract(delta), this._zoom);
  		}

  		if (!this._moved) {
  			map._moveStart(true, false);
  			this._moved = true;
  		}

  		cancelAnimFrame(this._animRequest);

  		var moveFn = bind(map._move, map, this._center, this._zoom, {pinch: true, round: false}, undefined);
  		this._animRequest = requestAnimFrame(moveFn, this, true);

  		preventDefault(e);
  	},

  	_onTouchEnd: function () {
  		if (!this._moved || !this._zooming) {
  			this._zooming = false;
  			return;
  		}

  		this._zooming = false;
  		cancelAnimFrame(this._animRequest);

  		off(document, 'touchmove', this._onTouchMove, this);
  		off(document, 'touchend touchcancel', this._onTouchEnd, this);

  		// Pinch updates GridLayers' levels only when zoomSnap is off, so zoomSnap becomes noUpdate.
  		if (this._map.options.zoomAnimation) {
  			this._map._animateZoom(this._center, this._map._limitZoom(this._zoom), true, this._map.options.zoomSnap);
  		} else {
  			this._map._resetView(this._center, this._map._limitZoom(this._zoom));
  		}
  	}
  });

  // @section Handlers
  // @property touchZoom: Handler
  // Touch zoom handler.
  Map.addInitHook('addHandler', 'touchZoom', TouchZoom);

  Map.BoxZoom = BoxZoom;
  Map.DoubleClickZoom = DoubleClickZoom;
  Map.Drag = Drag;
  Map.Keyboard = Keyboard;
  Map.ScrollWheelZoom = ScrollWheelZoom;
  Map.TapHold = TapHold;
  Map.TouchZoom = TouchZoom;

  exports.Bounds = Bounds;
  exports.Browser = Browser;
  exports.CRS = CRS;
  exports.Canvas = Canvas;
  exports.Circle = Circle;
  exports.CircleMarker = CircleMarker;
  exports.Class = Class;
  exports.Control = Control;
  exports.DivIcon = DivIcon;
  exports.DivOverlay = DivOverlay;
  exports.DomEvent = DomEvent;
  exports.DomUtil = DomUtil;
  exports.Draggable = Draggable;
  exports.Evented = Evented;
  exports.FeatureGroup = FeatureGroup;
  exports.GeoJSON = GeoJSON;
  exports.GridLayer = GridLayer;
  exports.Handler = Handler;
  exports.Icon = Icon;
  exports.ImageOverlay = ImageOverlay;
  exports.LatLng = LatLng;
  exports.LatLngBounds = LatLngBounds;
  exports.Layer = Layer;
  exports.LayerGroup = LayerGroup;
  exports.LineUtil = LineUtil;
  exports.Map = Map;
  exports.Marker = Marker;
  exports.Mixin = Mixin;
  exports.Path = Path;
  exports.Point = Point;
  exports.PolyUtil = PolyUtil;
  exports.Polygon = Polygon;
  exports.Polyline = Polyline;
  exports.Popup = Popup;
  exports.PosAnimation = PosAnimation;
  exports.Projection = index;
  exports.Rectangle = Rectangle;
  exports.Renderer = Renderer;
  exports.SVG = SVG;
  exports.SVGOverlay = SVGOverlay;
  exports.TileLayer = TileLayer;
  exports.Tooltip = Tooltip;
  exports.Transformation = Transformation;
  exports.Util = Util;
  exports.VideoOverlay = VideoOverlay;
  exports.bind = bind;
  exports.bounds = toBounds;
  exports.canvas = canvas;
  exports.circle = circle;
  exports.circleMarker = circleMarker;
  exports.control = control;
  exports.divIcon = divIcon;
  exports.extend = extend;
  exports.featureGroup = featureGroup;
  exports.geoJSON = geoJSON;
  exports.geoJson = geoJson;
  exports.gridLayer = gridLayer;
  exports.icon = icon;
  exports.imageOverlay = imageOverlay;
  exports.latLng = toLatLng;
  exports.latLngBounds = toLatLngBounds;
  exports.layerGroup = layerGroup;
  exports.map = createMap;
  exports.marker = marker;
  exports.point = toPoint;
  exports.polygon = polygon;
  exports.polyline = polyline;
  exports.popup = popup;
  exports.rectangle = rectangle;
  exports.setOptions = setOptions;
  exports.stamp = stamp;
  exports.svg = svg;
  exports.svgOverlay = svgOverlay;
  exports.tileLayer = tileLayer;
  exports.tooltip = tooltip;
  exports.transformation = toTransformation;
  exports.version = version;
  exports.videoOverlay = videoOverlay;

  var oldL = window.L;
  exports.noConflict = function() {
  	window.L = oldL;
  	return this;
  }
  // Always export us to window global (see #2364)
  window.L = exports;

}));






L.Icon.Default = L.Icon.Default.extend({
  _getIconUrl: function (name) {
    var paths = {"icon-2x.png":"/assets/marker-icon-2x-00179c4c1ee830d3a108412ae0d294f55776cfeb085c60129a39aa6fc4ae2528.png","shadow.png":"/assets/marker-shadow-264f5c640339f042dd729062cfc04c17f8ea0f29882b538e3848ed8f10edb4da.png","icon.png":"/assets/marker-icon-574c3a5cca85f4114085b6841596d62f00d7c892c7b03f28cbfa301deb1dc437.png"};
    return paths[name + '.png'];
  },

  _detectIconPath: function () {
    return '';
  }
});
L.Marker = L.Marker.extend({
  options: {
    icon: new L.Icon.Default()
  }
});

L.marker = function(latlng, options) {
  return new L.Marker(latlng, options);
}

;
// This is a manifest file that'll be compiled into application.js, which will include all the files
// listed below.
//
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, or any plugin's
// vendor/assets/javascripts directory can be referenced here using a relative path.
//
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
// compiled file. JavaScript code in this file should be added after the last require_* statement.
//
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
// about supported directives.
//





































$( document ).on('turbolinks:load', function() {
	var dataTable = $('#routes').DataTable( {
		"processing": true,
		"serverSide": true,
		"responsive": true,
		"pageLength": 25,
		"cache": false,
		"language": { "url": "/assets/datatables/datatables_spanish.json" },
		"ajax": {
			'url': "/system/tables/routes.json",
			'type': 'GET',
			'beforeSend': function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+$('#routes').attr('data-toc'));
			}
		}
	});
	
	$( ".select2" ).select2({
		language: "es"
	});

	$( ".select2financial" ).select2({
		language: "es"
	});

	$( ".select2multi" ).select2({
		language: "es",
		multiple: true,
		placeholder: "Seleccione",
		formatNoMatches: function() {
			return '';
		},

	});

	$('#config_bookings').DataTable( {
		"processing": true,
		"serverSide": true,
		"pageLength": 25,
		"cache": false,
		"language": { "url": "/assets/datatables/datatables_spanish.json" },
		"ajax": {
			'url': "/commercial/config_bookings/get/"+$('#config_bookings').attr('data-id')+".json",
			'type': 'GET',
			'beforeSend': function (request) {
				request.setRequestHeader("Authorization", 'Bearer '+$('#config_bookings').attr('data-toc') );
			}
		}
	} );

	$(".select2multi_tags").select2({
		language: "es",
		multiple: true,
		placeholder: "Ingrese los servicios",
		tags: true
	});

	$('#select2one').select2({
		language: "es",
		width: '100%',
		allowClear: true,
		multiple: true,
		maximumSelectionLength: 1,
		placeholder: "Seleccione",

	});


	var clickOnPopupLink = function(){
		$('body').on('click', '.static-popup-link', function(){
			$('#myModal').modal('show');
		});
	}

	$('#notifications_wall_message_messagetype_id').on('change', function() {
		if ($(this).children("option:selected").val() == "other") {
			$(".other-messagetype").removeClass("hide");
		}else{
			$(".other-messagetype").addClass("hide");
		}
	});

	clickOnPopupLink();

	$.fn.datepicker.dates['es'] = {
		days: ["Domingo", "Lunes", "Martes", "Miércoles", "Jueves", "Viernes", "Sábado"],
		daysShort: ["Dom", "Lun", "Mar", "Mié", "Jue", "Vie", "Sáb"],
		daysMin: ["Do", "Lu", "Ma", "Mi", "Ju", "Vi", "Sa"],
		months: ["Enero", "Febrero", "Marzo", "Abril", "Mayo", "Junio", "Julio", "Agosto", "Septiembre", "Octubre", "Noviembre", "Diciembre"],
		monthsShort: ["Ene", "Feb", "Mar", "Abr", "May", "Jun", "Jul", "Ago", "Sep", "Oct", "Nov", "Dic"],
		today: "Hoy",
		monthsTitle: "Meses",
		clear: "Borrar",
		weekStart: 1,
		format: "dd/mm/yyyy"
	};
	$.fn.datepicker.defaults.language = 'es';

	var input = $("#search");
	if (typeof input.val() !== 'undefined' ){
		var len = input.val().length;
		input[0].focus();
		input[0].setSelectionRange(len, len);
	}

	$(document).on('turbolinks:before-cache', function() {
		var dataTable;
		dataTable = $($.fn.dataTable.tables(true)).DataTable();
		if (dataTable !== null) {
			dataTable.destroy();
			return dataTable = null;
		}
	});

	$(document).trigger('refresh_autonumeric');

});

function validateFormDevise() {
	var response = true
	var spanAlert = document.getElementById('alertLogin');
	var incompleteFields = 'Campos incompletos'
	var emailInput = document.getElementById('user_email');
	if (emailInput) {
		if (!emailInput.value) {
			spanAlert.innerHTML = incompleteFields;
			response = false;
		}
	}
	var passwordInput = document.getElementById('user_password');
	if (passwordInput) {
		if (!passwordInput.value) {
			spanAlert.innerHTML = incompleteFields;
			response = false;
		}
	}
	var passwordConfirmationInput = document.getElementById('user_password_confirmation');
	if (passwordConfirmationInput) {
		if (!passwordConfirmationInput.value) {
			spanAlert.innerHTML = incompleteFields;
			response = false;
		}else{
			if (passwordInput.value == passwordConfirmationInput.value) {
				if (passwordInput.value.length < 6) {
					spanAlert.innerHTML = 'Contraseña debe ser mínimo de 6 caracteres';
					response = false;
				}
			}else{
				spanAlert.innerHTML = 'Contraseña no coincide';
				response = false;
			}
		}
	}
	return response;
}
$(document).ready(function () {
	$("#sidebar").mCustomScrollbar({
		theme: "minimal"
	});
	$('#sidebarCollapse').on('click', function () {
		$('#sidebar, #content').toggleClass('active');
		$('.collapse.in').toggleClass('in');
		$('a[aria-expanded=true]').attr('aria-expanded', 'false');
		if ($("#sidebar").hasClass("active")) {
			$("#header_info").css('margin-right', '10px');
		}else{
			$("#header_info").css('margin-right', '250px');
		}
	});
});


function posDataTablesFilters()
{
	var $window = $(window),
	$html = $('html');

	if ($window.width() < 514) {
		selector_filter = $("[id*='_filter']");
		selector_length = $("[id*='_length']");

		if(selector_filter!=null&&selector_length!=null)
		{
			selector_length.removeClass('dataTables_length');
			selector_filter.removeClass('dataTables_filter');
		}
		return
	}
	else{
		selector_filter = $("[id*='_filter']");
		selector_length = $("[id*='_length']");
		selector_length.removeClass('dataTables_length');
		selector_filter.removeClass('dataTables_filter');
		selector_length.addClass('dataTables_length');
		selector_filter.addClass('dataTables_filter');
	}
}
$(document).ready(function () {
	var $window = $(window),
	$html = $('html');

	posDataTablesFilters();

	$window.resize(function resize(){
		posDataTablesFilters();
	}).trigger('resize');
});
$( document ).on('turbolinks:load', function() {
	posDataTablesFilters();
	setTimeout(() => {
		posDataTablesFilters();
	}, 500);
});
// application.js

$(document).ready(function() {
	const form = $('#call-endpoint-form');

	form.on('submit', function(event) {
    	event.preventDefault(); 

    	// Obtiene los valores de los campos del formulario
    	const phonePrefix = $('#phonePrefix').val();
    	const phoneNumber = $('#callee').val();
    	const stageValue = $('#stage').val();


    	// Combina el prefijo y el número de teléfono
    	const fullPhoneNumber = phonePrefix + phoneNumber;

    	// Verifica que el número de teléfono sea válido
    	const phoneRegex = /^[0-9]{10}$/;
    	if (!phoneRegex.test(phoneNumber)) {
    		Swal.fire({
    			title: 'Error!',
    			text: 'El número de teléfono no es valido.',
    			icon: 'error',
    			confirmButtonText: 'Cerrar'
    		});
    		return;
    	}

    	// Realiza la llamada al endpoint utilizando AJAX
    	$.ajax({
    		url: '/call_endpoint',
    		type: 'POST',
    		dataType: 'json',
    		data: { callee: fullPhoneNumber, stage: stageValue },
    		success: function(data, textStatus, jqXHR) {
    			console.log('Llamada al endpoint exitosa');
    			
    			// Cierra el modal
        		$('#callModal').modal('hide');
    		},
    		error: function(jqXHR, textStatus, errorThrown) {
    			console.log('Error al llamar al endpoint');
    			Swal.fire({
    				title: 'Error!',
    				text: 'Error al llamar al endpoint.',
    				icon: 'error',
    				confirmButtonText: 'Cerrar'
    			});
    		}
    	});
    });

  
});
