// hyperform.js.org
var hyperform = (function () {
  'use strict';

  var instances = new WeakMap();
  /**
   * wrap <form>s, window or document, that get treated with the global
   * hyperform()
   */

  function Wrapper(form, settings) {
    /* do not allow more than one instance per form. Otherwise we'd end
     * up with double event handlers, polyfills re-applied, ... */
    var existing = instances.get(form);

    if (existing) {
      existing.settings = settings;
      return existing;
    }

    this.form = form;
    this.settings = settings;
    this.observer = null;
    instances.set(form, this);
  }
  Wrapper.prototype = {
    destroy: function destroy() {
      instances["delete"](this.form);

      if (this._destruct) {
        this._destruct();
      }
    }
  };
  /**
   * try to get the appropriate wrapper for a specific element by looking up
   * its parent chain
   *
   * @return Wrapper | undefined
   */

  function get_wrapper(element) {
    var wrapped;

    if (element.form) {
      /* try a shortcut with the element's <form> */
      wrapped = instances.get(element.form);
    }
    /* walk up the parent nodes until document (including) */


    while (!wrapped && element) {
      wrapped = instances.get(element);
      element = element.parentNode;
    }

    if (!wrapped) {
      /* try the global instance, if exists. This may also be undefined. */
      wrapped = instances.get(window);
    }

    return wrapped;
  }

  /**
   * filter a form's elements for the ones needing validation prior to
   * a submit
   *
   * Returns an array of form elements.
   */

  function get_validated_elements(form) {
    var wrapped_form = get_wrapper(form);
    return Array.prototype.filter.call(form.elements, function (element) {
      /* it must have a name (or validating nameless inputs is allowed) */
      if (element.getAttribute('name') || wrapped_form && wrapped_form.settings.validateNameless) {
        return true;
      }

      return false;
    });
  }

  var registry = Object.create(null);
  /**
   * run all actions registered for a hook
   *
   * Every action gets called with a state object as `this` argument and with the
   * hook's call arguments as call arguments.
   *
   * @return mixed the returned value of the action calls or undefined
   */

  function call_hook(hook) {
    var result;
    var call_args = Array.prototype.slice.call(arguments, 1);

    if (hook in registry) {
      result = registry[hook].reduce(function (args) {
        return function (previousResult, currentAction) {
          var interimResult = currentAction.apply({
            state: previousResult,
            hook: hook
          }, args);
          return interimResult !== undefined ? interimResult : previousResult;
        };
      }(call_args), result);
    }

    return result;
  }
  /**
   * Filter a value through hooked functions
   *
   * Allows for additional parameters:
   * js> do_filter('foo', null, current_element)
   */

  function do_filter(hook, initial_value) {
    var result = initial_value;
    var call_args = Array.prototype.slice.call(arguments, 1);

    if (hook in registry) {
      result = registry[hook].reduce(function (previousResult, currentAction) {
        call_args[0] = previousResult;
        var interimResult = currentAction.apply({
          state: previousResult,
          hook: hook
        }, call_args);
        return interimResult !== undefined ? interimResult : previousResult;
      }, result);
    }

    return result;
  }
  /**
   * remove an action again
   */

  function remove_hook(hook, action) {
    if (hook in registry) {
      for (var i = 0; i < registry[hook].length; i++) {
        if (registry[hook][i] === action) {
          registry[hook].splice(i, 1);
          break;
        }
      }
    }
  }
  /**
   * add an action to a hook
   */

  function add_hook(hook, action, position) {
    if (!(hook in registry)) {
      registry[hook] = [];
    }

    if (position === undefined) {
      position = registry[hook].length;
    }

    registry[hook].splice(position, 0, action);
  }

  /**
   * return either the data of a hook call or the result of action, if the
   * former is undefined
   *
   * @return function a function wrapper around action
   */

  function return_hook_or (hook, action) {
    return function () {
      var data = call_hook(hook, Array.prototype.slice.call(arguments));

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

      return action.apply(this, arguments);
    };
  }

  /* the following code is borrowed from the WebComponents project, licensed
   * under the BSD license. Source:
   * <https://github.com/webcomponents/webcomponentsjs/blob/5283db1459fa2323e5bfc8b9b5cc1753ed85e3d0/src/WebComponents/dom.js#L53-L78>
   */
  // defaultPrevented is broken in IE.
  // https://connect.microsoft.com/IE/feedback/details/790389/event-defaultprevented-returns-false-after-preventdefault-was-called

  var workingDefaultPrevented = function () {
    var e = document.createEvent('Event');
    e.initEvent('foo', true, true);
    e.preventDefault();
    return e.defaultPrevented;
  }();

  if (!workingDefaultPrevented) {
    var origPreventDefault = window.Event.prototype.preventDefault;

    window.Event.prototype.preventDefault = function () {
      if (!this.cancelable) {
        return;
      }

      origPreventDefault.call(this);
      Object.defineProperty(this, 'defaultPrevented', {
        get: function get() {
          return true;
        },
        configurable: true
      });
    };
  }
  /* end of borrowed code */


  function create_event(name) {
    var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
        _ref$bubbles = _ref.bubbles,
        bubbles = _ref$bubbles === void 0 ? true : _ref$bubbles,
        _ref$cancelable = _ref.cancelable,
        cancelable = _ref$cancelable === void 0 ? false : _ref$cancelable;

    var event = document.createEvent('Event');
    event.initEvent(name, bubbles, cancelable);
    return event;
  }
  function trigger_event (element, event) {
    var _ref2 = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
        _ref2$bubbles = _ref2.bubbles,
        bubbles = _ref2$bubbles === void 0 ? true : _ref2$bubbles,
        _ref2$cancelable = _ref2.cancelable,
        cancelable = _ref2$cancelable === void 0 ? false : _ref2$cancelable;

    var payload = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : {};

    if (!(event instanceof window.Event)) {
      event = create_event(event, {
        bubbles: bubbles,
        cancelable: cancelable
      });
    }

    for (var key in payload) {
      if (payload.hasOwnProperty(key)) {
        event[key] = payload[key];
      }
    }

    element.dispatchEvent(event);
    return event;
  }

  /* and datetime-local? Spec says “Nah!” */

  var dates = ['datetime', 'date', 'month', 'week', 'time'];
  var plain_numbers = ['number', 'range'];
  /* everything that returns something meaningful for valueAsNumber and
   * can have the step attribute */

  var numbers = dates.concat(plain_numbers, 'datetime-local');
  /* the spec says to only check those for syntax in validity.typeMismatch.
   * ¯\_(ツ)_/¯ */

  var type_checked = ['email', 'url'];
  /* check these for validity.badInput */

  var input_checked = ['email', 'date', 'month', 'week', 'time', 'datetime', 'datetime-local', 'number', 'range', 'color'];
  var text = ['text', 'search', 'tel', 'password'].concat(type_checked);
  /* input element types, that are candidates for the validation API.
   * Missing from this set are: button, hidden, menu (from <button>), reset and
   * the types for non-<input> elements. */

  var validation_candidates = ['checkbox', 'color', 'file', 'image', 'radio', 'submit'].concat(numbers, text);
  /* all known types of <input> */

  var inputs = ['button', 'hidden', 'reset'].concat(validation_candidates);
  /* apparently <select> and <textarea> have types of their own */

  var non_inputs = ['select-one', 'select-multiple', 'textarea'];

  /**
   * get the element's type in a backwards-compatible way
   */

  function get_type (element) {
    if (element instanceof window.HTMLTextAreaElement) {
      return 'textarea';
    } else if (element instanceof window.HTMLSelectElement) {
      return element.hasAttribute('multiple') ? 'select-multiple' : 'select-one';
    } else if (element instanceof window.HTMLButtonElement) {
      return (element.getAttribute('type') || 'submit').toLowerCase();
    } else if (element instanceof window.HTMLInputElement) {
      var attr = (element.getAttribute('type') || '').toLowerCase();

      if (attr && inputs.indexOf(attr) > -1) {
        return attr;
      } else {
        /* perhaps the DOM has in-depth knowledge. Take that before returning
         * 'text'. */
        return element.type || 'text';
      }
    }

    return '';
  }

  /**
   * check if an element should be ignored due to any of its parents
   *
   * Checks <fieldset disabled> and <datalist>.
   */

  function is_in_disallowed_parent(element) {
    var p = element.parentNode;

    while (p && p.nodeType === 1) {
      if (p instanceof window.HTMLFieldSetElement && p.hasAttribute('disabled')) {
        /* quick return, if it's a child of a disabled fieldset */
        return true;
      } else if (p.nodeName.toUpperCase() === 'DATALIST') {
        /* quick return, if it's a child of a datalist
         * Do not use HTMLDataListElement to support older browsers,
         * too.
         * @see https://html.spec.whatwg.org/multipage/forms.html#the-datalist-element:barred-from-constraint-validation
         */
        return true;
      } else if (p === element.form) {
        /* the outer boundary. We can stop looking for relevant elements. */
        break;
      }

      p = p.parentNode;
    }

    return false;
  }
  /**
   * check if an element is a candidate for constraint validation
   *
   * @see https://html.spec.whatwg.org/multipage/forms.html#barred-from-constraint-validation
   */


  function is_validation_candidate (element) {
    /* allow a shortcut via filters, e.g. to validate type=hidden fields */
    var filtered = do_filter('is_validation_candidate', null, element);

    if (filtered !== null) {
      return !!filtered;
    }
    /* it must be any of those elements */


    if (element instanceof window.HTMLSelectElement || element instanceof window.HTMLTextAreaElement || element instanceof window.HTMLButtonElement || element instanceof window.HTMLInputElement) {
      var type = get_type(element);
      /* its type must be in the whitelist */

      if (non_inputs.indexOf(type) > -1 || validation_candidates.indexOf(type) > -1) {
        /* it mustn't be disabled or readonly */
        if (!element.hasAttribute('disabled') && !element.hasAttribute('readonly')) {
          var wrapped_form = get_wrapper(element);

          if (
          /* the parent form doesn't allow non-standard "novalidate" attributes... */
          wrapped_form && !wrapped_form.settings.novalidateOnElements ||
          /* ...or it doesn't have such an attribute/property */
          !element.hasAttribute('novalidate') && !element.noValidate) {
            /* it isn't part of a <fieldset disabled> */
            if (!is_in_disallowed_parent(element)) {
              /* then it's a candidate */
              return true;
            }
          }
        }
      }
    }
    /* this is no HTML5 validation candidate... */


    return false;
  }

  function _typeof(obj) {
    "@babel/helpers - typeof";

    if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") {
      _typeof = function (obj) {
        return typeof obj;
      };
    } else {
      _typeof = function (obj) {
        return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
      };
    }

    return _typeof(obj);
  }

  function mark (obj) {
    if (['object', 'function'].indexOf(_typeof(obj)) > -1) {
      delete obj.__hyperform;
      Object.defineProperty(obj, '__hyperform', {
        configurable: true,
        enumerable: false,
        value: true
      });
    }

    return obj;
  }

  function format_date (date) {
    var part = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;

    switch (part) {
      case 'date':
        return (date.toLocaleDateString || date.toDateString).call(date);

      case 'time':
        return (date.toLocaleTimeString || date.toTimeString).call(date);

      case 'month':
        return 'toLocaleDateString' in date ? date.toLocaleDateString(undefined, {
          year: 'numeric',
          month: '2-digit'
        }) : date.toDateString();
      // case 'week':
      // TODO

      default:
        return (date.toLocaleString || date.toString).call(date);
    }
  }

  function sprintf (str) {
    for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
      args[_key - 1] = arguments[_key];
    }

    var args_length = args.length;
    var global_index = 0;
    return str.replace(/%([0-9]+\$)?([sl])/g, function (match, position, type) {
      var local_index = global_index;

      if (position) {
        local_index = Number(position.replace(/\$$/, '')) - 1;
      }

      global_index += 1;
      var arg = '';

      if (args_length > local_index) {
        arg = args[local_index];
      }

      if (arg instanceof Date || typeof arg === 'number' || arg instanceof Number) {
        /* try getting a localized representation of dates and numbers, if the
         * browser supports this */
        if (type === 'l') {
          arg = (arg.toLocaleString || arg.toString).call(arg);
        } else {
          arg = arg.toString();
        }
      }

      return arg;
    });
  }

  /* For a given date, get the ISO week number
   *
   * Source: http://stackoverflow.com/a/6117889/113195
   *
   * Based on information at:
   *
   *    http://www.merlyn.demon.co.uk/weekcalc.htm#WNR
   *
   * Algorithm is to find nearest thursday, it's year
   * is the year of the week number. Then get weeks
   * between that date and the first day of that year.
   *
   * Note that dates in one year can be weeks of previous
   * or next year, overlap is up to 3 days.
   *
   * e.g. 2014/12/29 is Monday in week  1 of 2015
   *      2012/1/1   is Sunday in week 52 of 2011
   */

  function get_week_of_year (d) {
    /* Copy date so don't modify original */
    d = new Date(+d);
    d.setUTCHours(0, 0, 0);
    /* Set to nearest Thursday: current date + 4 - current day number
     * Make Sunday's day number 7 */

    d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
    /* Get first day of year */

    var yearStart = new Date(d.getUTCFullYear(), 0, 1);
    /* Calculate full weeks to nearest Thursday */

    var weekNo = Math.ceil(((d - yearStart) / 86400000 + 1) / 7);
    /* Return array of year and week number */

    return [d.getUTCFullYear(), weekNo];
  }

  function pad(num) {
    var size = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 2;
    var s = num + '';

    while (s.length < size) {
      s = '0' + s;
    }

    return s;
  }
  /**
   * calculate a string from a date according to HTML5
   */


  function date_to_string(date, element_type) {
    if (!(date instanceof Date)) {
      return null;
    }

    switch (element_type) {
      case 'datetime':
        return date_to_string(date, 'date') + 'T' + date_to_string(date, 'time');

      case 'datetime-local':
        return sprintf('%s-%s-%sT%s:%s:%s.%s', date.getFullYear(), pad(date.getMonth() + 1), pad(date.getDate()), pad(date.getHours()), pad(date.getMinutes()), pad(date.getSeconds()), pad(date.getMilliseconds(), 3)).replace(/(:00)?\.000$/, '');

      case 'date':
        return sprintf('%s-%s-%s', date.getUTCFullYear(), pad(date.getUTCMonth() + 1), pad(date.getUTCDate()));

      case 'month':
        return sprintf('%s-%s', date.getUTCFullYear(), pad(date.getUTCMonth() + 1));

      case 'week':
        {
          var params = get_week_of_year(date);
          return sprintf.call(null, '%s-W%s', params[0], pad(params[1]));
        }

      case 'time':
        return sprintf('%s:%s:%s.%s', pad(date.getUTCHours()), pad(date.getUTCMinutes()), pad(date.getUTCSeconds()), pad(date.getUTCMilliseconds(), 3)).replace(/(:00)?\.000$/, '');
    }

    return null;
  }

  /**
   * return a new Date() representing the ISO date for a week number
   *
   * @see http://stackoverflow.com/a/16591175/113195
   */

  function get_date_from_week (week, year) {
    var date = new Date(Date.UTC(year, 0, 1 + (week - 1) * 7));

    if (date.getUTCDay() <= 4
    /* thursday */
    ) {
        date.setUTCDate(date.getUTCDate() - date.getUTCDay() + 1);
      } else {
      date.setUTCDate(date.getUTCDate() + 8 - date.getUTCDay());
    }

    return date;
  }

  /**
   * calculate a date from a string according to HTML5
   */

  function string_to_date (string, element_type) {
    var date;

    switch (element_type) {
      case 'datetime':
        if (!/^([0-9]{4})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):([0-5][0-9])(?::([0-5][0-9])(?:\.([0-9]{1,3}))?)?$/.test(string)) {
          return null;
        }

        date = new Date(string + 'z');
        return isNaN(date.valueOf()) ? null : date;

      case 'date':
        if (!/^([0-9]{4})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$/.test(string)) {
          return null;
        }

        date = new Date(string);
        return isNaN(date.valueOf()) ? null : date;

      case 'month':
        if (!/^([0-9]{4})-(0[1-9]|1[012])$/.test(string)) {
          return null;
        }

        date = new Date(string);
        return isNaN(date.valueOf()) ? null : date;

      case 'week':
        if (!/^([0-9]{4})-W(0[1-9]|[1234][0-9]|5[0-3])$/.test(string)) {
          return null;
        }

        return get_date_from_week(Number(RegExp.$2), Number(RegExp.$1));

      case 'time':
        if (!/^([01][0-9]|2[0-3]):([0-5][0-9])(?::([0-5][0-9])(?:\.([0-9]{1,3}))?)?$/.test(string)) {
          return null;
        }

        date = new Date('1970-01-01T' + string + 'z');
        return date;
    }

    return null;
  }

  /**
   * calculate a number from a string according to HTML5
   */

  function string_to_number (string, element_type) {
    var rval = string_to_date(string, element_type);

    if (rval !== null) {
      return +rval;
    }
    /* not parseFloat, because we want NaN for invalid values like "1.2xxy" */


    return Number(string);
  }

  /**
   * the following validation messages are from Firefox source,
   * http://mxr.mozilla.org/mozilla-central/source/dom/locales/en-US/chrome/dom/dom.properties
   * released under MPL license, http://mozilla.org/MPL/2.0/.
   */

  var catalog = {
    en: {
      TextTooLong: 'Please shorten this text to %l characters or less (you are currently using %l characters).',
      ValueMissing: 'Please fill out this field.',
      CheckboxMissing: 'Please check this box if you want to proceed.',
      RadioMissing: 'Please select one of these options.',
      FileMissing: 'Please select a file.',
      SelectMissing: 'Please select an item in the list.',
      InvalidEmail: 'Please enter an email address.',
      InvalidURL: 'Please enter a URL.',
      PatternMismatch: 'Please match the requested format.',
      PatternMismatchWithTitle: 'Please match the requested format: %l.',
      NumberRangeOverflow: 'Please select a value that is no more than %l.',
      DateRangeOverflow: 'Please select a value that is no later than %l.',
      TimeRangeOverflow: 'Please select a value that is no later than %l.',
      NumberRangeUnderflow: 'Please select a value that is no less than %l.',
      DateRangeUnderflow: 'Please select a value that is no earlier than %l.',
      TimeRangeUnderflow: 'Please select a value that is no earlier than %l.',
      StepMismatch: 'Please select a valid value. The two nearest valid values are %l and %l.',
      StepMismatchOneValue: 'Please select a valid value. The nearest valid value is %l.',
      BadInputNumber: 'Please enter a number.'
    }
  };
  /**
   * the global language Hyperform will use
   */

  var language = 'en';
  /**
   * the base language according to BCP47, i.e., only the piece before the first hyphen
   */

  var base_lang = 'en';
  /**
   * set the language for Hyperform’s messages
   */

  function set_language(newlang) {
    language = newlang;
    base_lang = newlang.replace(/[-_].*/, '');
  }
  /**
   * add a lookup catalog "string: translation" for a language
   */

  function add_translation(lang, new_catalog) {
    if (!(lang in catalog)) {
      catalog[lang] = {};
    }

    for (var key in new_catalog) {
      if (new_catalog.hasOwnProperty(key)) {
        catalog[lang][key] = new_catalog[key];
      }
    }
  }
  /**
   * return `s` translated into the current language
   *
   * Defaults to the base language and then English if the former has no
   * translation for `s`.
   */

  function _ (s) {
    if (language in catalog && s in catalog[language]) {
      return catalog[language][s];
    } else if (base_lang in catalog && s in catalog[base_lang]) {
      return catalog[base_lang][s];
    } else if (s in catalog.en) {
      return catalog.en[s];
    }

    return s;
  }

  var default_step = {
    'datetime-local': 60,
    datetime: 60,
    time: 60
  };
  var step_scale_factor = {
    'datetime-local': 1000,
    datetime: 1000,
    date: 86400000,
    week: 604800000,
    time: 1000
  };
  var default_step_base = {
    week: -259200000
  };
  var default_min = {
    range: 0
  };
  var default_max = {
    range: 100
  };

  /**
   * get previous and next valid values for a stepped input element
   */

  function get_next_valid (element) {
    var n = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;
    var type = get_type(element);
    var aMin = element.getAttribute('min');
    var min = default_min[type] || NaN;

    if (aMin) {
      var pMin = string_to_number(aMin, type);

      if (!isNaN(pMin)) {
        min = pMin;
      }
    }

    var aMax = element.getAttribute('max');
    var max = default_max[type] || NaN;

    if (aMax) {
      var pMax = string_to_number(aMax, type);

      if (!isNaN(pMax)) {
        max = pMax;
      }
    }

    var aStep = element.getAttribute('step');
    var step = default_step[type] || 1;

    if (aStep && aStep.toLowerCase() === 'any') {
      /* quick return: we cannot calculate prev and next */
      return [_('any value'), _('any value')];
    } else if (aStep) {
      var pStep = string_to_number(aStep, type);

      if (!isNaN(pStep)) {
        step = pStep;
      }
    }

    var default_value = string_to_number(element.getAttribute('value'), type);
    var value = string_to_number(element.value || element.getAttribute('value'), type);

    if (isNaN(value)) {
      /* quick return: we cannot calculate without a solid base */
      return [_('any valid value'), _('any valid value')];
    }

    var step_base = !isNaN(min) ? min : !isNaN(default_value) ? default_value : default_step_base[type] || 0;
    var scale = step_scale_factor[type] || 1;
    var prev = step_base + Math.floor((value - step_base) / (step * scale)) * (step * scale) * n;
    var next = step_base + (Math.floor((value - step_base) / (step * scale)) + 1) * (step * scale) * n;

    if (prev < min) {
      prev = null;
    } else if (prev > max) {
      prev = max;
    }

    if (next > max) {
      next = null;
    } else if (next < min) {
      next = min;
    }
    /* convert to date objects, if appropriate */


    if (dates.indexOf(type) > -1) {
      prev = date_to_string(new Date(prev), type);
      next = date_to_string(new Date(next), type);
    }

    return [prev, next];
  }

  /**
   * patch String.length to account for non-BMP characters
   *
   * @see https://mathiasbynens.be/notes/javascript-unicode
   * We do not use the simple [...str].length, because it needs a ton of
   * polyfills in older browsers.
   */

  function unicode_string_length (str) {
    return str.match(/[\0-\uD7FF\uE000-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?:[^\uD800-\uDBFF]|^)[\uDC00-\uDFFF]/g).length;
  }

  /**
   * internal storage for custom error messages
   */

  var store = new WeakMap();
  /**
   * register custom error messages per element
   */

  var custom_messages = {
    set: function set(element, validator, message) {
      var messages = store.get(element) || {};
      messages[validator] = message;
      store.set(element, messages);
      return custom_messages;
    },
    get: function get(element, validator) {
      var _default = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : undefined;

      var messages = store.get(element);

      if (messages === undefined || !(validator in messages)) {
        var data_id = 'data-' + validator.replace(/[A-Z]/g, '-$&').toLowerCase();

        if (element.hasAttribute(data_id)) {
          /* if the element has a data-validator attribute, use this as fallback.
           * E.g., if validator == 'valueMissing', the element can specify a
           * custom validation message like this:
           *     <input data-value-missing="Oh noes!">
           */
          return element.getAttribute(data_id);
        }

        return _default;
      }

      return messages[validator];
    },
    "delete": function _delete(element) {
      var validator = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;

      if (!validator) {
        return store["delete"](element);
      }

      var messages = store.get(element) || {};

      if (validator in messages) {
        delete messages[validator];
        store.set(element, messages);
        return true;
      }

      return false;
    }
  };

  /**
   * the internal storage for messages
   */

  var store$1 = new WeakMap();
  /* jshint -W053 */

  /* allow new String() */

  /**
   * handle validation messages
   *
   * Falls back to browser-native errors, if any are available. The messages
   * are String objects so that we can mark() them.
   */

  var message_store = {
    set: function set(element, message) {
      var is_custom = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : false;

      if (element instanceof window.HTMLFieldSetElement) {
        var wrapped_form = get_wrapper(element);

        if (wrapped_form && !wrapped_form.settings.extendFieldset) {
          /* make this a no-op for <fieldset> in strict mode */
          return message_store;
        }
      }

      if (typeof message === 'string') {
        message = new String(message);
      }

      if (is_custom) {
        message.is_custom = true;
      }

      mark(message);
      store$1.set(element, message);
      /* allow the :invalid selector to match */

      if ('_original_setCustomValidity' in element) {
        element._original_setCustomValidity(message.toString());
      }

      return message_store;
    },
    get: function get(element) {
      var message = store$1.get(element);

      if (message === undefined && '_original_validationMessage' in element) {
        /* get the browser's validation message, if we have none. Maybe it
         * knows more than we. */
        message = new String(element._original_validationMessage);
      }

      return message ? message : new String('');
    },
    "delete": function _delete(element) {
      var is_custom = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;

      if ('_original_setCustomValidity' in element) {
        element._original_setCustomValidity('');
      }

      var message = store$1.get(element);

      if (message && is_custom && !message.is_custom) {
        /* do not delete "native" messages, if asked */
        return false;
      }

      return store$1["delete"](element);
    }
  };

  var internal_registry = new WeakMap();
  /**
   * A registry for custom validators
   *
   * slim wrapper around a WeakMap to ensure the values are arrays
   * (hence allowing > 1 validators per element)
   */

  var custom_validator_registry = {
    set: function set(element, validator) {
      var current = internal_registry.get(element) || [];
      current.push(validator);
      internal_registry.set(element, current);
      return custom_validator_registry;
    },
    get: function get(element) {
      return internal_registry.get(element) || [];
    },
    "delete": function _delete(element) {
      return internal_registry["delete"](element);
    }
  };

  /**
   * test whether the element suffers from bad input
   */

  function test_bad_input (element) {
    var type = get_type(element);

    if (input_checked.indexOf(type) === -1) {
      /* we're not interested, thanks! */
      return true;
    }
    /* the browser hides some bad input from the DOM, e.g. malformed numbers,
     * email addresses with invalid punycode representation, ... We try to resort
     * to the original method here. The assumption is, that a browser hiding
     * bad input will hopefully also always support a proper
     * ValidityState.badInput */


    if (!element.value) {
      if ('_original_validity' in element && !element._original_validity.__hyperform) {
        return !element._original_validity.badInput;
      }
      /* no value and no original badInput: Assume all's right. */


      return true;
    }

    var result = true;

    switch (type) {
      case 'color':
        result = /^#[a-f0-9]{6}$/.test(element.value);
        break;

      case 'number':
      case 'range':
        result = !isNaN(Number(element.value));
        break;

      case 'datetime':
      case 'date':
      case 'month':
      case 'week':
      case 'time':
        result = string_to_date(element.value, type) !== null;
        break;

      case 'datetime-local':
        result = /^([0-9]{4,})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):([0-5][0-9])(?::([0-5][0-9])(?:\.([0-9]{1,3}))?)?$/.test(element.value);
        break;
    }

    return result;
  }

  /**
   * test the max attribute
   *
   * we use Number() instead of parseFloat(), because an invalid attribute
   * value like "123abc" should result in an error.
   */

  function test_max (element) {
    var type = get_type(element);

    if (!element.value || !element.hasAttribute('max')) {
      /* we're not responsible here */
      return true;
    }

    var value, max;

    if (dates.indexOf(type) > -1) {
      value = string_to_date(element.value, type);
      value = value === null ? NaN : +value;
      max = string_to_date(element.getAttribute('max'), type);
      max = max === null ? NaN : +max;
    } else {
      value = Number(element.value);
      max = Number(element.getAttribute('max'));
    }
    /* we cannot validate invalid values and trust on badInput, if isNaN(value) */


    return isNaN(max) || isNaN(value) || value <= max;
  }

  /**
   * test the maxlength attribute
   */

  function test_maxlength (element) {
    if (!element.value || text.indexOf(get_type(element)) === -1 || !element.hasAttribute('maxlength') || !element.getAttribute('maxlength') // catch maxlength=""
    ) {
        return true;
      }

    var maxlength = parseInt(element.getAttribute('maxlength'), 10);
    /* check, if the maxlength value is usable at all.
     * We allow maxlength === 0 to basically disable input (Firefox does, too).
     */

    if (isNaN(maxlength) || maxlength < 0) {
      return true;
    }

    return unicode_string_length(element.value) <= maxlength;
  }

  /**
   * test the min attribute
   *
   * we use Number() instead of parseFloat(), because an invalid attribute
   * value like "123abc" should result in an error.
   */

  function test_min (element) {
    var type = get_type(element);

    if (!element.value || !element.hasAttribute('min')) {
      /* we're not responsible here */
      return true;
    }

    var value, min;

    if (dates.indexOf(type) > -1) {
      value = string_to_date(element.value, type);
      value = value === null ? NaN : +value;
      min = string_to_date(element.getAttribute('min'), type);
      min = min === null ? NaN : +min;
    } else {
      value = Number(element.value);
      min = Number(element.getAttribute('min'));
    }
    /* we cannot validate invalid values and trust on badInput, if isNaN(value) */


    return isNaN(min) || isNaN(value) || value >= min;
  }

  /**
   * test the minlength attribute
   */

  function test_minlength (element) {
    if (!element.value || text.indexOf(get_type(element)) === -1 || !element.hasAttribute('minlength') || !element.getAttribute('minlength') // catch minlength=""
    ) {
        return true;
      }

    var minlength = parseInt(element.getAttribute('minlength'), 10);
    /* check, if the minlength value is usable at all. */

    if (isNaN(minlength) || minlength < 0) {
      return true;
    }

    return unicode_string_length(element.value) >= minlength;
  }

  /**
   * test the pattern attribute
   */

  function test_pattern (element) {
    return !element.value || !element.hasAttribute('pattern') || new RegExp('^(?:' + element.getAttribute('pattern') + ')$').test(element.value);
  }

  /**
   * get all radio buttons (including `element`) that belong to element's
   * radio group
   */

  function get_radiogroup(element) {
    if (element.form) {
      return Array.prototype.filter.call(element.form.elements, function (radio) {
        return radio.type === 'radio' && radio.name === element.name;
      });
    }

    return [element];
  }

  function has_submittable_option(select) {
    /* Definition of the placeholder label option:
     * https://www.w3.org/TR/html5/sec-forms.html#element-attrdef-select-required
     * Being required (the first constraint in the spec) is trivially true, since
     * this function is only called for such selects.
     */
    var has_placeholder_option = !select.multiple && select.size <= 1 && select.options.length > 0 && select.options[0].parentNode == select && select.options[0].value === '';
    return (
      /* anything selected at all? That's redundant with the .some() call below,
       * but more performant in the most probable error case. */
      select.selectedIndex > -1 && Array.prototype.some.call(select.options, function (option) {
        return (
          /* it isn't the placeholder option */
          (!has_placeholder_option || option.index !== 0) &&
          /* it isn't disabled */
          !option.disabled &&
          /* and it is, in fact, selected */
          option.selected
        );
      })
    );
  }
  /**
   * test the required attribute
   */


  function test_required (element) {
    if (element.type === 'radio') {
      /* the happy (and quick) path for radios: */
      if (element.hasAttribute('required') && element.checked) {
        return true;
      }

      var radiogroup = get_radiogroup(element);
      /* if any radio in the group is required, we need any (not necessarily the
       * same) radio to be checked */

      if (radiogroup.some(function (radio) {
        return radio.hasAttribute('required');
      })) {
        return radiogroup.some(function (radio) {
          return radio.checked;
        });
      }
      /* not required, validation passes */


      return true;
    }

    if (!element.hasAttribute('required')) {
      /* nothing to do */
      return true;
    }

    if (element instanceof window.HTMLSelectElement) {
      return has_submittable_option(element);
    }

    return element.type === 'checkbox' ? element.checked : !!element.value;
  }

  /**
   * test the step attribute
   */

  function test_step (element) {
    var type = get_type(element);

    if (!element.value || numbers.indexOf(type) === -1 || (element.getAttribute('step') || '').toLowerCase() === 'any') {
      /* we're not responsible here. Note: If no step attribute is given, we
       * need to validate against the default step as per spec. */
      return true;
    }

    var step = element.getAttribute('step');

    if (step) {
      step = string_to_number(step, type);
    } else {
      step = default_step[type] || 1;
    }

    if (step <= 0 || isNaN(step)) {
      /* error in specified "step". We cannot validate against it, so the value
       * is true. */
      return true;
    }

    var scale = step_scale_factor[type] || 1;
    var value = string_to_number(element.value, type);
    var min = string_to_number(element.getAttribute('min') || element.getAttribute('value') || '', type);

    if (isNaN(value)) {
      /* we cannot compare an invalid value and trust that the badInput validator
       * takes over from here */
      return true;
    }

    if (isNaN(min)) {
      min = default_step_base[type] || 0;
    }

    if (type === 'month') {
      /* type=month has month-wide steps. See
       * https://html.spec.whatwg.org/multipage/forms.html#month-state-%28type=month%29
       */
      min = new Date(min).getUTCFullYear() * 12 + new Date(min).getUTCMonth();
      value = new Date(value).getUTCFullYear() * 12 + new Date(value).getUTCMonth();
    }

    var result = Math.abs(min - value) % (step * scale);
    return result < 0.00000001 ||
    /* crappy floating-point arithmetics! */
    result > step * scale - 0.00000001;
  }

  var ws_on_start_or_end = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
  /**
   * trim a string of whitespace
   *
   * We don't use String.trim() to remove the need to polyfill it.
   */

  function trim (str) {
    return str.replace(ws_on_start_or_end, '');
  }

  /**
   * split a string on comma and trim the components
   *
   * As specified at
   * https://html.spec.whatwg.org/multipage/infrastructure.html#split-a-string-on-commas
   * plus removing empty entries.
   */

  function comma_split (str) {
    return str.split(',').map(function (item) {
      return trim(item);
    }).filter(function (b) {
      return b;
    });
  }

  /* we use a dummy <a> where we set the href to test URL validity
   * The definition is out of the "global" scope so that JSDOM can be instantiated
   * after loading Hyperform for tests.
   */

  var url_canary;
  /* see https://html.spec.whatwg.org/multipage/forms.html#valid-e-mail-address */

  var email_pattern = /^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;
  /**
   * test the type-inherent syntax
   */

  function test_type (element) {
    var type = get_type(element);

    if (type !== 'file' && !element.value || type !== 'file' && type_checked.indexOf(type) === -1) {
      /* we're not responsible for this element */
      return true;
    }

    var is_valid = true;

    switch (type) {
      case 'url':
        {
          if (!url_canary) {
            url_canary = document.createElement('a');
          }

          var value = trim(element.value);
          url_canary.href = value;
          is_valid = url_canary.href === value || url_canary.href === value + '/';
          break;
        }

      case 'email':
        if (element.hasAttribute('multiple')) {
          is_valid = comma_split(element.value).every(function (value) {
            return email_pattern.test(value);
          });
        } else {
          is_valid = email_pattern.test(trim(element.value));
        }

        break;

      case 'file':
        if ('files' in element && element.files.length && element.hasAttribute('accept')) {
          var patterns = comma_split(element.getAttribute('accept')).map(function (pattern) {
            if (/^(audio|video|image)\/\*$/.test(pattern)) {
              pattern = new RegExp('^' + RegExp.$1 + '/.+$');
            }

            return pattern;
          });

          if (!patterns.length) {
            break;
          }

          fileloop: for (var i = 0; i < element.files.length; i++) {
            /* we need to match a whitelist, so pre-set with false */
            var file_valid = false;

            patternloop: for (var j = 0; j < patterns.length; j++) {
              var file = element.files[i];
              var pattern = patterns[j];
              var fileprop = file.type;

              if (typeof pattern === 'string' && pattern.substr(0, 1) === '.') {
                if (file.name.search('.') === -1) {
                  /* no match with any file ending */
                  continue patternloop;
                }

                fileprop = file.name.substr(file.name.lastIndexOf('.'));
              }

              if (fileprop.search(pattern) === 0) {
                /* we found one match and can quit looking */
                file_valid = true;
                break patternloop;
              }
            }

            if (!file_valid) {
              is_valid = false;
              break fileloop;
            }
          }
        }

    }

    return is_valid;
  }

  /**
   * boilerplate function for all tests but customError
   */

  function check(test, react) {
    return function (element) {
      var invalid = !test(element);

      if (invalid) {
        react(element);
      }

      return invalid;
    };
  }
  /**
   * create a common function to set error messages
   */


  function set_msg(element, msgtype, _default) {
    message_store.set(element, custom_messages.get(element, msgtype, _default));
  }

  var badInput = check(test_bad_input, function (element) {
    return set_msg(element, 'badInput', _('Please match the requested type.'));
  });

  function customError(element) {
    /* prevent infinite loops when the custom validators call setCustomValidity(),
     * which in turn calls this code again. We check, if there is an already set
     * custom validity message there. */
    if (element.__hf_custom_validation_running) {
      var msg = message_store.get(element);
      return msg && msg.is_custom;
    }
    /* check, if there are custom validators in the registry, and call
     * them. */


    var custom_validators = custom_validator_registry.get(element);
    var cvl = custom_validators.length;
    var valid = true;

    if (cvl) {
      element.__hf_custom_validation_running = true;

      for (var i = 0; i < cvl; i++) {
        var result = custom_validators[i](element);

        if (result !== undefined && !result) {
          valid = false;
          /* break on first invalid response */

          break;
        }
      }

      delete element.__hf_custom_validation_running;
    }
    /* check, if there are other validity messages already */


    if (valid) {
      var _msg = message_store.get(element);

      valid = !(_msg.toString() && 'is_custom' in _msg);
    }

    return !valid;
  }

  var patternMismatch = check(test_pattern, function (element) {
    set_msg(element, 'patternMismatch', element.title ? sprintf(_('PatternMismatchWithTitle'), element.title) : _('PatternMismatch'));
  });
  /**
   * TODO: when rangeOverflow and rangeUnderflow are both called directly and
   * successful, the inRange and outOfRange classes won't get removed, unless
   * element.validityState.valid is queried, too.
   */

  var rangeOverflow = check(test_max, function (element) {
    var type = get_type(element);
    var wrapper = get_wrapper(element);
    var outOfRangeClass = wrapper && wrapper.settings.classes.outOfRange || 'hf-out-of-range';
    var inRangeClass = wrapper && wrapper.settings.classes.inRange || 'hf-in-range';
    var msg;

    switch (type) {
      case 'date':
      case 'datetime':
      case 'datetime-local':
        msg = sprintf(_('DateRangeOverflow'), format_date(string_to_date(element.getAttribute('max'), type), type));
        break;

      case 'time':
        msg = sprintf(_('TimeRangeOverflow'), format_date(string_to_date(element.getAttribute('max'), type), type));
        break;
      // case 'number':

      default:
        msg = sprintf(_('NumberRangeOverflow'), string_to_number(element.getAttribute('max'), type));
        break;
    }

    set_msg(element, 'rangeOverflow', msg);
    element.classList.add(outOfRangeClass);
    element.classList.remove(inRangeClass);
  });
  var rangeUnderflow = check(test_min, function (element) {
    var type = get_type(element);
    var wrapper = get_wrapper(element);
    var outOfRangeClass = wrapper && wrapper.settings.classes.outOfRange || 'hf-out-of-range';
    var inRangeClass = wrapper && wrapper.settings.classes.inRange || 'hf-in-range';
    var msg;

    switch (type) {
      case 'date':
      case 'datetime':
      case 'datetime-local':
        msg = sprintf(_('DateRangeUnderflow'), format_date(string_to_date(element.getAttribute('min'), type), type));
        break;

      case 'time':
        msg = sprintf(_('TimeRangeUnderflow'), format_date(string_to_date(element.getAttribute('min'), type), type));
        break;
      // case 'number':

      default:
        msg = sprintf(_('NumberRangeUnderflow'), string_to_number(element.getAttribute('min'), type));
        break;
    }

    set_msg(element, 'rangeUnderflow', msg);
    element.classList.add(outOfRangeClass);
    element.classList.remove(inRangeClass);
  });
  var stepMismatch = check(test_step, function (element) {
    var list = get_next_valid(element);
    var min = list[0];
    var max = list[1];
    var sole = false;
    var msg;

    if (min === null) {
      sole = max;
    } else if (max === null) {
      sole = min;
    }

    if (sole !== false) {
      msg = sprintf(_('StepMismatchOneValue'), sole);
    } else {
      msg = sprintf(_('StepMismatch'), min, max);
    }

    set_msg(element, 'stepMismatch', msg);
  });
  var tooLong = check(test_maxlength, function (element) {
    set_msg(element, 'tooLong', sprintf(_('TextTooLong'), element.getAttribute('maxlength'), unicode_string_length(element.value)));
  });
  var tooShort = check(test_minlength, function (element) {
    set_msg(element, 'tooShort', sprintf(_('Please lengthen this text to %l characters or more (you are currently using %l characters).'), element.getAttribute('minlength'), unicode_string_length(element.value)));
  });
  var typeMismatch = check(test_type, function (element) {
    var msg = _('Please use the appropriate format.');

    var type = get_type(element);

    if (type === 'email') {
      if (element.hasAttribute('multiple')) {
        msg = _('Please enter a comma separated list of email addresses.');
      } else {
        msg = _('InvalidEmail');
      }
    } else if (type === 'url') {
      msg = _('InvalidURL');
    } else if (type === 'file') {
      msg = _('Please select a file of the correct type.');
    }

    set_msg(element, 'typeMismatch', msg);
  });
  var valueMissing = check(test_required, function (element) {
    var msg = _('ValueMissing');

    var type = get_type(element);

    if (type === 'checkbox') {
      msg = _('CheckboxMissing');
    } else if (type === 'radio') {
      msg = _('RadioMissing');
    } else if (type === 'file') {
      if (element.hasAttribute('multiple')) {
        msg = _('Please select one or more files.');
      } else {
        msg = _('FileMissing');
      }
    } else if (element instanceof window.HTMLSelectElement) {
      msg = _('SelectMissing');
    }

    set_msg(element, 'valueMissing', msg);
  });
  /**
   * the "valid" property calls all other validity checkers and returns true,
   * if all those return false.
   *
   * This is the major access point for _all_ other API methods, namely
   * (check|report)Validity().
   */

  var valid = function valid(element) {
    var wrapper = get_wrapper(element);
    var validClass = wrapper && wrapper.settings.classes.valid || 'hf-valid';
    var invalidClass = wrapper && wrapper.settings.classes.invalid || 'hf-invalid';
    var userInvalidClass = wrapper && wrapper.settings.classes.userInvalid || 'hf-user-invalid';
    var userValidClass = wrapper && wrapper.settings.classes.userValid || 'hf-user-valid';
    var inRangeClass = wrapper && wrapper.settings.classes.inRange || 'hf-in-range';
    var outOfRangeClass = wrapper && wrapper.settings.classes.outOfRange || 'hf-out-of-range';
    var validatedClass = wrapper && wrapper.settings.classes.validated || 'hf-validated';
    element.classList.add(validatedClass);

    for (var _i = 0, _arr = [badInput, customError, patternMismatch, rangeOverflow, rangeUnderflow, stepMismatch, tooLong, tooShort, typeMismatch, valueMissing]; _i < _arr.length; _i++) {
      var checker = _arr[_i];

      if (checker(element)) {
        element.classList.add(invalidClass);
        element.classList.remove(validClass);
        element.classList.remove(userValidClass);

        if ((element.type === 'checkbox' || element.type === 'radio') && element.checked !== element.defaultChecked ||
        /* the following test is trivially false for checkboxes/radios */
        element.value !== element.defaultValue) {
          element.classList.add(userInvalidClass);
        } else {
          element.classList.remove(userInvalidClass);
        }

        element.setAttribute('aria-invalid', 'true');
        return false;
      }
    }

    message_store["delete"](element);
    element.classList.remove(invalidClass);
    element.classList.remove(userInvalidClass);
    element.classList.remove(outOfRangeClass);
    element.classList.add(validClass);
    element.classList.add(inRangeClass);

    if (element.value !== element.defaultValue) {
      element.classList.add(userValidClass);
    } else {
      element.classList.remove(userValidClass);
    }

    element.setAttribute('aria-invalid', 'false');
    return true;
  };

  var validity_state_checkers = {
    badInput: badInput,
    customError: customError,
    patternMismatch: patternMismatch,
    rangeOverflow: rangeOverflow,
    rangeUnderflow: rangeUnderflow,
    stepMismatch: stepMismatch,
    tooLong: tooLong,
    tooShort: tooShort,
    typeMismatch: typeMismatch,
    valueMissing: valueMissing,
    valid: valid
  };

  /**
   * the validity state constructor
   */

  var ValidityState = function ValidityState(element) {
    if (!(element instanceof window.HTMLElement)) {
      throw new Error('cannot create a ValidityState for a non-element');
    }

    var cached = ValidityState.cache.get(element);

    if (cached) {
      return cached;
    }

    if (!(this instanceof ValidityState)) {
      /* working around a forgotten `new` */
      return new ValidityState(element);
    }

    this.element = element;
    ValidityState.cache.set(element, this);
  };
  /**
   * the prototype for new validityState instances
   */


  var ValidityStatePrototype = {};
  ValidityState.prototype = ValidityStatePrototype;
  ValidityState.cache = new WeakMap();
  /* small wrapper around the actual validator to check if the validator
   * should actually be called. `this` refers to the ValidityState object. */

  var checker_getter = function checker_getter(prop, func) {
    return function () {
      if (!is_validation_candidate(this.element)) {
        /* not being validated == valid by default
         * return value == false for all props except "valid", because we test
         * problems like badInput here */
        return prop === 'valid';
      }

      return func(this.element);
    };
  };
  /**
   * copy functionality from the validity checkers to the ValidityState
   * prototype
   */


  for (var prop in validity_state_checkers) {
    Object.defineProperty(ValidityStatePrototype, prop, {
      configurable: true,
      enumerable: true,
      get: checker_getter(prop, validity_state_checkers[prop]),
      set: undefined
    });
  }
  /**
   * mark the validity prototype, because that is what the client-facing
   * code deals with mostly, not the property descriptor thing */


  mark(ValidityStatePrototype);

  /**
   * check an element's validity with respect to it's form
   */

  var checkValidity = return_hook_or('checkValidity', function (element) {
    /* if this is a <form>, check validity of all child inputs */
    if (element instanceof window.HTMLFormElement) {
      return get_validated_elements(element).map(checkValidity).every(function (b) {
        return b;
      });
    }
    /* default is true, also for elements that are no validation candidates */


    var valid = ValidityState(element).valid;

    if (valid) {
      var wrapped_form = get_wrapper(element);

      if (wrapped_form && wrapped_form.settings.validEvent) {
        trigger_event(element, 'valid');
      }
    } else {
      trigger_event(element, 'invalid', {
        cancelable: true
      });
    }

    return valid;
  });

  /**
   * counter that will be incremented with every call
   *
   * Will enforce uniqueness, as long as no more than 1 hyperform scripts
   * are loaded. (In that case we still have the "random" part below.)
   */

  var uid = 0;
  /**
   * generate a random ID
   *
   * @see https://gist.github.com/gordonbrander/2230317
   */

  function generate_id () {
    var prefix = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'hf_';
    return prefix + uid++ + Math.random().toString(36).substr(2);
  }

  var warningsCache = new WeakMap();
  var DefaultRenderer = {
    /**
     * called when a warning should become visible
     */
    attachWarning: function attachWarning(warning, element) {
      /* should also work, if element is last,
       * http://stackoverflow.com/a/4793630/113195 */
      element.parentNode.insertBefore(warning, element.nextSibling);
    },

    /**
     * called when a warning should vanish
     */
    detachWarning: function detachWarning(warning, element) {
      /* be conservative here, since an overwritten attachWarning() might not
       * actually have attached the warning. */
      if (warning.parentNode) {
        warning.parentNode.removeChild(warning);
      }
    },

    /**
     * called when feedback to an element's state should be handled
     *
     * i.e., showing and hiding warnings
     */
    showWarning: function showWarning(element) {
      var whole_form_validated = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;

      /* don't render error messages on subsequent radio buttons of the
       * same group. This assumes, that element.validity.valueMissing is the only
       * possible validation failure for radio buttons. */
      if (whole_form_validated && element.type === 'radio' && get_radiogroup(element)[0] !== element) {
        return;
      }

      var msg = message_store.get(element).toString();
      var warning = warningsCache.get(element);

      if (msg) {
        if (!warning) {
          var wrapper = get_wrapper(element);
          warning = document.createElement('div');
          warning.className = wrapper && wrapper.settings.classes.warning || 'hf-warning';
          warning.id = generate_id();
          warning.setAttribute('aria-live', 'polite');
          warningsCache.set(element, warning);
        }

        element.setAttribute('aria-errormessage', warning.id);

        if (!element.hasAttribute('aria-describedby')) {
          element.setAttribute('aria-describedby', warning.id);
        }

        Renderer.setMessage(warning, msg, element);
        Renderer.attachWarning(warning, element);
      } else if (warning && warning.parentNode) {
        if (element.getAttribute('aria-describedby') === warning.id) {
          element.removeAttribute('aria-describedby');
        }

        element.removeAttribute('aria-errormessage');
        Renderer.detachWarning(warning, element);
      }
    },

    /**
     * set the warning's content
     *
     * Overwrite this method, if you want, e.g., to allow HTML in warnings
     * or preprocess the content.
     */
    setMessage: function setMessage(warning, message, element) {
      warning.textContent = message;
    }
  };
  var Renderer = {
    attachWarning: DefaultRenderer.attachWarning,
    detachWarning: DefaultRenderer.detachWarning,
    showWarning: DefaultRenderer.showWarning,
    setMessage: DefaultRenderer.setMessage,
    set: function set(renderer, action) {
      if (!action) {
        action = DefaultRenderer[renderer];
      }

      Renderer[renderer] = action;
    },
    getWarning: function getWarning(element) {
      return warningsCache.get(element);
    }
  };

  /**
   * check element's validity and report an error back to the user
   */

  function reportValidity(element) {
    /* if this is a <form>, report validity of all child inputs */
    if (element instanceof window.HTMLFormElement) {
      element.__hf_form_validation = true;
      var form_valid = get_validated_elements(element).map(reportValidity).every(function (b) {
        return b;
      });
      delete element.__hf_form_validation;
      return form_valid;
    }
    /* we copy checkValidity() here, b/c we have to check if the "invalid"
     * event was canceled. */


    var valid = ValidityState(element).valid;
    var event;

    if (valid) {
      var wrapped_form = get_wrapper(element);

      if (wrapped_form && wrapped_form.settings.validEvent) {
        event = trigger_event(element, 'valid', {
          cancelable: true
        });
      }
    } else {
      event = trigger_event(element, 'invalid', {
        cancelable: true
      });
    }

    if (!event || !event.defaultPrevented) {
      Renderer.showWarning(element, element.form && element.form.__hf_form_validation);
    }

    return valid;
  }

  /**
   * set a custom validity message or delete it with an empty string
   */

  function setCustomValidity(element, msg) {
    if (!msg) {
      message_store["delete"](element, true);
    } else {
      message_store.set(element, msg, true);
    }
    /* live-update the warning */


    var warning = Renderer.getWarning(element);

    if (warning) {
      Renderer.setMessage(warning, msg, element);
    }
    /* update any classes if the validity state changes */


    validity_state_checkers.valid(element);
  }

  /**
   * implement the valueAsDate functionality
   *
   * @see https://html.spec.whatwg.org/multipage/forms.html#dom-input-valueasdate
   */

  function valueAsDate(element) {
    var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
    var type = get_type(element);

    if (dates.indexOf(type) > -1) {
      if (value !== undefined) {
        /* setter: value must be null or a Date() */
        if (value === null) {
          element.value = '';
        } else if (value instanceof Date) {
          if (isNaN(value.getTime())) {
            element.value = '';
          } else {
            element.value = date_to_string(value, type);
          }
        } else {
          throw new window.DOMException('valueAsDate setter encountered invalid value', 'TypeError');
        }

        return;
      }

      var value_date = string_to_date(element.value, type);
      return value_date instanceof Date ? value_date : null;
    } else if (value !== undefined) {
      /* trying to set a date on a not-date input fails */
      throw new window.DOMException('valueAsDate setter cannot set date on this element', 'InvalidStateError');
    }

    return null;
  }

  /**
   * implement the valueAsNumber functionality
   *
   * @see https://html.spec.whatwg.org/multipage/forms.html#dom-input-valueasnumber
   */

  function valueAsNumber(element) {
    var value = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : undefined;
    var type = get_type(element);

    if (numbers.indexOf(type) > -1) {
      if (type === 'range' && element.hasAttribute('multiple')) {
        /* @see https://html.spec.whatwg.org/multipage/forms.html#do-not-apply */
        return NaN;
      }

      if (value !== undefined) {
        /* setter: value must be NaN or a finite number */
        if (isNaN(value)) {
          element.value = '';
        } else if (typeof value === 'number' && window.isFinite(value)) {
          try {
            /* try setting as a date, but... */
            valueAsDate(element, new Date(value));
          } catch (e) {
            /* ... when valueAsDate is not responsible, ... */
            if (!(e instanceof window.DOMException)) {
              throw e;
            }
            /* ... set it via Number.toString(). */


            element.value = value.toString();
          }
        } else {
          throw new window.DOMException('valueAsNumber setter encountered invalid value', 'TypeError');
        }

        return;
      }

      return string_to_number(element.value, type);
    } else if (value !== undefined) {
      /* trying to set a number on a not-number input fails */
      throw new window.DOMException('valueAsNumber setter cannot set number on this element', 'InvalidStateError');
    }

    return NaN;
  }

  /**
   *
   */

  function stepDown(element) {
    var n = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;

    if (numbers.indexOf(get_type(element)) === -1) {
      throw new window.DOMException('stepDown encountered invalid type', 'InvalidStateError');
    }

    if ((element.getAttribute('step') || '').toLowerCase() === 'any') {
      throw new window.DOMException('stepDown encountered step "any"', 'InvalidStateError');
    }

    var prev = get_next_valid(element, n)[0];

    if (prev !== null) {
      valueAsNumber(element, prev);
    }
  }

  /**
   *
   */

  function stepUp(element) {
    var n = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1;

    if (numbers.indexOf(get_type(element)) === -1) {
      throw new window.DOMException('stepUp encountered invalid type', 'InvalidStateError');
    }

    if ((element.getAttribute('step') || '').toLowerCase() === 'any') {
      throw new window.DOMException('stepUp encountered step "any"', 'InvalidStateError');
    }

    var next = get_next_valid(element, n)[1];

    if (next !== null) {
      valueAsNumber(element, next);
    }
  }

  /**
   * get the validation message for an element, empty string, if the element
   * satisfies all constraints.
   */

  function validationMessage(element) {
    var msg = message_store.get(element);

    if (!msg) {
      return '';
    }
    /* make it a primitive again, since message_store returns String(). */


    return msg.toString();
  }

  /**
   * check, if an element will be subject to HTML5 validation at all
   */

  function willValidate(element) {
    return is_validation_candidate(element);
  }

  /* shim layer for the Element.matches method */

  var ep = window.Element.prototype;
  var native_matches = ep.matches || ep.matchesSelector || ep.msMatchesSelector || ep.webkitMatchesSelector;
  function matches (element, selector) {
    return native_matches.call(element, selector);
  }

  /**
   * submit a form, because `element` triggered it
   *
   * This method also dispatches a submit event on the form prior to the
   * submission. The event contains the trigger element as `submittedVia`.
   *
   * If the element is a button with a name, the name=value pair will be added
   * to the submitted data.
   */

  function submit_form_via(element) {
    /* apparently, the submit event is not triggered in most browsers on
     * the submit() method, so we do it manually here to model a natural
     * submit as closely as possible.
     * Now to the fun fact: If you trigger a submit event from a form, what
     * do you think should happen?
     * 1) the form will be automagically submitted by the browser, or
     * 2) nothing.
     * And as you already suspected, the correct answer is: both! Firefox
     * opts for 1), Chrome for 2). Yay! */
    var event_got_cancelled;
    var submit_event = create_event('submit', {
      cancelable: true
    });
    /* force Firefox to not submit the form, then fake preventDefault() */

    submit_event.preventDefault();
    Object.defineProperty(submit_event, 'defaultPrevented', {
      value: false,
      writable: true
    });
    Object.defineProperty(submit_event, 'preventDefault', {
      value: function value() {
        return submit_event.defaultPrevented = event_got_cancelled = true;
      },
      writable: true
    });
    trigger_event(element.form, submit_event, {}, {
      submittedVia: element
    });

    if (!event_got_cancelled) {
      add_submit_field(element);
      window.HTMLFormElement.prototype.submit.call(element.form);
      window.setTimeout(function () {
        return remove_submit_field(element);
      });
    }
  }
  /**
   * if a submit button was clicked, add its name=value by means of a type=hidden
   * input field
   */


  function add_submit_field(button) {
    if (['image', 'submit'].indexOf(button.type) > -1 && button.name) {
      var wrapper = get_wrapper(button.form) || {};
      var submit_helper = wrapper.submit_helper;

      if (submit_helper) {
        if (submit_helper.parentNode) {
          submit_helper.parentNode.removeChild(submit_helper);
        }
      } else {
        submit_helper = document.createElement('input');
        submit_helper.type = 'hidden';
        wrapper.submit_helper = submit_helper;
      }

      submit_helper.name = button.name;
      submit_helper.value = button.value;
      button.form.appendChild(submit_helper);
    }
  }
  /**
   * remove a possible helper input, that was added by `add_submit_field`
   */


  function remove_submit_field(button) {
    if (['image', 'submit'].indexOf(button.type) > -1 && button.name) {
      var wrapper = get_wrapper(button.form) || {};
      var submit_helper = wrapper.submit_helper;

      if (submit_helper && submit_helper.parentNode) {
        submit_helper.parentNode.removeChild(submit_helper);
      }
    }
  }
  /**
   * check a form's validity and submit it
   *
   * The method triggers a cancellable `validate` event on the form. If the
   * event is cancelled, form submission will be aborted, too.
   *
   * If the form is found to contain invalid fields, focus the first field.
   */


  function check$1(button) {
    /* trigger a "validate" event on the form to be submitted */
    var val_event = trigger_event(button.form, 'validate', {
      cancelable: true
    });

    if (val_event.defaultPrevented) {
      /* skip the whole submit thing, if the validation is canceled. A user
       * can still call form.submit() afterwards. */
      return;
    }

    var valid = true;
    var first_invalid;
    button.form.__hf_form_validation = true;
    get_validated_elements(button.form).map(function (element) {
      if (!reportValidity(element)) {
        valid = false;

        if (!first_invalid && 'focus' in element) {
          first_invalid = element;
        }
      }
    });
    delete button.form.__hf_form_validation;

    if (valid) {
      submit_form_via(button);
    } else if (first_invalid) {
      /* focus the first invalid element, if validation went south */
      first_invalid.focus();
      /* tell the tale, if anyone wants to react to it */

      trigger_event(button.form, 'forminvalid');
    }
  }
  /**
   * test if node is a submit button
   */


  function is_submit_button(node) {
    return (
      /* must be an input or button element... */
      (node.nodeName === 'INPUT' || node.nodeName === 'BUTTON') && (
      /* ...and have a submitting type */
      node.type === 'image' || node.type === 'submit')
    );
  }
  /**
   * test, if the click event would trigger a submit
   */


  function is_submitting_click(event, button) {
    return (
      /* prevented default: won't trigger a submit */
      !event.defaultPrevented && (
      /* left button or middle button (submits in Chrome) */
      !('button' in event) || event.button < 2) &&
      /* must be a submit button... */
      is_submit_button(button) &&
      /* the button needs a form, that's going to be submitted */
      button.form &&
      /* again, if the form should not be validated, we're out of the game */
      !button.form.hasAttribute('novalidate')
    );
  }
  /**
   * test, if the keypress event would trigger a submit
   */


  function is_submitting_keypress(event) {
    return (
      /* prevented default: won't trigger a submit */
      !event.defaultPrevented && (
      /* ...and <Enter> was pressed... */
      event.keyCode === 13 &&
      /* ...on an <input> that is... */
      event.target.nodeName === 'INPUT' &&
      /* ...a standard text input field (not checkbox, ...) */
      text.indexOf(event.target.type) > -1 ||
      /* or <Enter> or <Space> was pressed... */
      (event.keyCode === 13 || event.keyCode === 32) &&
      /* ...on a submit button */
      is_submit_button(event.target)) &&
      /* there's a form... */
      event.target.form &&
      /* ...and the form allows validation */
      !event.target.form.hasAttribute('novalidate')
    );
  }
  /**
   * catch clicks to children of <button>s
   */


  function get_clicked_button(element) {
    if (is_submit_button(element)) {
      return element;
    } else if (matches(element, 'button:not([type]) *, button[type="submit"] *')) {
      return get_clicked_button(element.parentNode);
    } else {
      return null;
    }
  }
  /**
   * return event handler to catch explicit submission by click on a button
   */


  function get_click_handler() {
    var ignore = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : false;
    return function (event) {
      var button = get_clicked_button(event.target);

      if (button && is_submitting_click(event, button)) {
        event.preventDefault();

        if (ignore || button.hasAttribute('formnovalidate')) {
          /* if validation should be ignored, we're not interested in any checks */
          submit_form_via(button);
        } else {
          check$1(button);
        }
      }
    };
  }

  var click_handler = get_click_handler();
  var ignored_click_handler = get_click_handler(true);
  /**
   * catch implicit submission by pressing <Enter> in some situations
   */

  function get_keypress_handler(ignore) {
    return function keypress_handler(event) {
      if (is_submitting_keypress(event)) {
        event.preventDefault();
        var wrapper = get_wrapper(event.target.form) || {
          settings: {}
        };

        if (wrapper.settings.preventImplicitSubmit) {
          /* user doesn't want an implicit submit. Cancel here. */
          return;
        }
        /* check, that there is no submit button in the form. Otherwise
        * that should be clicked. */


        var el = event.target.form.elements.length;
        var submit;

        for (var i = 0; i < el; i++) {
          if (['image', 'submit'].indexOf(event.target.form.elements[i].type) > -1) {
            submit = event.target.form.elements[i];
            break;
          }
        }
        /* trigger an "implicit_submit" event on the form to be submitted */


        var implicit_event = trigger_event(event.target.form, 'implicit_submit', {
          cancelable: true
        }, {
          trigger: event.target,
          submittedVia: submit || event.target
        });

        if (implicit_event.defaultPrevented) {
          /* skip the submit, if implicit submit is canceled */
          return;
        }

        if (submit) {
          submit.click();
        } else if (ignore) {
          submit_form_via(event.target);
        } else {
          check$1(event.target);
        }
      }
    };
  }

  var keypress_handler = get_keypress_handler();
  var ignored_keypress_handler = get_keypress_handler(true);
  /**
   * catch all relevant events _prior_ to a form being submitted
   *
   * @param bool ignore bypass validation, when an attempt to submit the
   *                    form is detected. True, when the wrapper's revalidate
   *                    setting is 'never'.
   */

  function catch_submit(listening_node) {
    var ignore = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;

    if (ignore) {
      listening_node.addEventListener('click', ignored_click_handler);
      listening_node.addEventListener('keypress', ignored_keypress_handler);
    } else {
      listening_node.addEventListener('click', click_handler);
      listening_node.addEventListener('keypress', keypress_handler);
    }
  }
  /**
   * decommission the event listeners from catch_submit() again
   */

  function uncatch_submit(listening_node) {
    listening_node.removeEventListener('click', ignored_click_handler);
    listening_node.removeEventListener('keypress', ignored_keypress_handler);
    listening_node.removeEventListener('click', click_handler);
    listening_node.removeEventListener('keypress', keypress_handler);
  }

  /**
   * add `property` to an element
   *
   * ATTENTION! This function will search for an equally named property on the
   * *prototype* of an element, if element is a concrete DOM node. Do not use
   * it as general-purpose property installer.
   *
   * js> installer(element, 'foo', { value: 'bar' });
   * js> assert(element.foo === 'bar');
   */

  function install_property (element, property, descriptor) {
    descriptor.configurable = true;
    descriptor.enumerable = true;

    if ('value' in descriptor) {
      descriptor.writable = true;
    }
    /* on concrete instances, i.e., <input> elements, the naive lookup
     * yields undefined. We have to look on its prototype then. On elements
     * like the actual HTMLInputElement object the first line works. */


    var original_descriptor = Object.getOwnPropertyDescriptor(element, property);

    if (original_descriptor === undefined) {
      original_descriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(element), property);
    }

    if (original_descriptor) {
      if (original_descriptor.configurable === false) {
        /* Safari <= 9 and PhantomJS will end up here :-( Nothing to do except
         * warning */
        var wrapper = get_wrapper(element);

        if (wrapper && wrapper.settings.debug) {
          /* global console */
          console.log('[hyperform] cannot install custom property ' + property);
        }

        return false;
      }
      /* we already installed that property... */


      if (original_descriptor.get && original_descriptor.get.__hyperform || original_descriptor.value && original_descriptor.value.__hyperform) {
        return;
      }
      /* publish existing property under new name, if it's not from us */


      Object.defineProperty(element, '_original_' + property, original_descriptor);
    }

    delete element[property];
    Object.defineProperty(element, property, descriptor);
    return true;
  }

  function is_field (element) {
    return element instanceof window.HTMLButtonElement || element instanceof window.HTMLInputElement || element instanceof window.HTMLSelectElement || element instanceof window.HTMLTextAreaElement || element instanceof window.HTMLFieldSetElement || element === window.HTMLButtonElement.prototype || element === window.HTMLInputElement.prototype || element === window.HTMLSelectElement.prototype || element === window.HTMLTextAreaElement.prototype || element === window.HTMLFieldSetElement.prototype;
  }

  /**
   * remove `property` from element and restore _original_property, if present
   */

  function uninstall_property (element, property) {
    try {
      delete element[property];
    } catch (e) {
      /* Safari <= 9 and PhantomJS will end up here :-( Nothing to do except
       * warning */
      var wrapper = get_wrapper(element);

      if (wrapper && wrapper.settings.debug) {
        /* global console */
        console.log('[hyperform] cannot uninstall custom property ' + property);
      }

      return false;
    }

    var original_descriptor = Object.getOwnPropertyDescriptor(element, '_original_' + property);

    if (original_descriptor) {
      Object.defineProperty(element, property, original_descriptor);
    }
  }

  var gA = function gA(prop) {
    return function () {
      return do_filter('attr_get_' + prop, this.getAttribute(prop), this);
    };
  };

  var sA = function sA(prop) {
    return function (value) {
      this.setAttribute(prop, do_filter('attr_set_' + prop, value, this));
    };
  };

  var gAb = function gAb(prop) {
    return function () {
      return do_filter('attr_get_' + prop, this.hasAttribute(prop), this);
    };
  };

  var sAb = function sAb(prop) {
    return function (value) {
      if (do_filter('attr_set_' + prop, value, this)) {
        this.setAttribute(prop, prop);
      } else {
        this.removeAttribute(prop);
      }
    };
  };

  var gAn = function gAn(prop) {
    return function () {
      return do_filter('attr_get_' + prop, Math.max(0, Number(this.getAttribute(prop))), this);
    };
  };

  var sAn = function sAn(prop) {
    return function (value) {
      value = do_filter('attr_set_' + prop, value, this);

      if (/^[0-9]+$/.test(value)) {
        this.setAttribute(prop, value);
      }
    };
  };

  function install_properties(element) {
    for (var _i = 0, _arr = ['accept', 'max', 'min', 'pattern', 'placeholder', 'step']; _i < _arr.length; _i++) {
      var prop = _arr[_i];
      install_property(element, prop, {
        get: gA(prop),
        set: sA(prop)
      });
    }

    for (var _i2 = 0, _arr2 = ['multiple', 'required', 'readOnly']; _i2 < _arr2.length; _i2++) {
      var _prop = _arr2[_i2];
      install_property(element, _prop, {
        get: gAb(_prop.toLowerCase()),
        set: sAb(_prop.toLowerCase())
      });
    }

    for (var _i3 = 0, _arr3 = ['minLength', 'maxLength']; _i3 < _arr3.length; _i3++) {
      var _prop2 = _arr3[_i3];
      install_property(element, _prop2, {
        get: gAn(_prop2.toLowerCase()),
        set: sAn(_prop2.toLowerCase())
      });
    }
  }

  function uninstall_properties(element) {
    for (var _i4 = 0, _arr4 = ['accept', 'max', 'min', 'pattern', 'placeholder', 'step', 'multiple', 'required', 'readOnly', 'minLength', 'maxLength']; _i4 < _arr4.length; _i4++) {
      var prop = _arr4[_i4];
      uninstall_property(element, prop);
    }
  }

  var polyfills = {
    checkValidity: {
      value: mark(function () {
        return checkValidity(this);
      })
    },
    reportValidity: {
      value: mark(function () {
        return reportValidity(this);
      })
    },
    setCustomValidity: {
      value: mark(function (msg) {
        return setCustomValidity(this, msg);
      })
    },
    stepDown: {
      value: mark(function () {
        var n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
        return stepDown(this, n);
      })
    },
    stepUp: {
      value: mark(function () {
        var n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1;
        return stepUp(this, n);
      })
    },
    validationMessage: {
      get: mark(function () {
        return validationMessage(this);
      })
    },
    validity: {
      get: mark(function () {
        return ValidityState(this);
      })
    },
    valueAsDate: {
      get: mark(function () {
        return valueAsDate(this);
      }),
      set: mark(function (value) {
        valueAsDate(this, value);
      })
    },
    valueAsNumber: {
      get: mark(function () {
        return valueAsNumber(this);
      }),
      set: mark(function (value) {
        valueAsNumber(this, value);
      })
    },
    willValidate: {
      get: mark(function () {
        return willValidate(this);
      })
    }
  };
  function polyfill (element) {
    if (is_field(element)) {
      for (var prop in polyfills) {
        install_property(element, prop, polyfills[prop]);
      }

      install_properties(element);
    } else if (element instanceof window.HTMLFormElement || element === window.HTMLFormElement.prototype) {
      install_property(element, 'checkValidity', polyfills.checkValidity);
      install_property(element, 'reportValidity', polyfills.reportValidity);
    }
  }

  function polyunfill (element) {
    if (is_field(element)) {
      uninstall_property(element, 'checkValidity');
      uninstall_property(element, 'reportValidity');
      uninstall_property(element, 'setCustomValidity');
      uninstall_property(element, 'stepDown');
      uninstall_property(element, 'stepUp');
      uninstall_property(element, 'validationMessage');
      uninstall_property(element, 'validity');
      uninstall_property(element, 'valueAsDate');
      uninstall_property(element, 'valueAsNumber');
      uninstall_property(element, 'willValidate');
      uninstall_properties(element);
    } else if (element instanceof window.HTMLFormElement) {
      uninstall_property(element, 'checkValidity');
      uninstall_property(element, 'reportValidity');
    }
  }

  var element_prototypes = [window.HTMLButtonElement.prototype, window.HTMLInputElement.prototype, window.HTMLSelectElement.prototype, window.HTMLTextAreaElement.prototype, window.HTMLFieldSetElement.prototype];
  /**
   * get the appropriate function to revalidate form elements
   */

  function get_revalidator() {
    var method = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'hybrid';
    return function (event) {
      if (event.target instanceof window.HTMLButtonElement || event.target instanceof window.HTMLTextAreaElement || event.target instanceof window.HTMLSelectElement || event.target instanceof window.HTMLInputElement) {
        if (event.target.form && event.target.form.hasAttribute('novalidate')) {
          /* do nothing, if the form forbids it. This still allows manual
           * validation via, e.g., input.reportValidity(), but mirrors browser
           * behavior, that are also completely silent in this case. */
          return;
        }

        if (method === 'hybrid') {
          /* "hybrid" somewhat simulates what browsers do. See for example
           * Firefox's :-moz-ui-invalid pseudo-class:
           * https://developer.mozilla.org/en-US/docs/Web/CSS/:-moz-ui-invalid */
          if (event.type === 'blur' && event.target.value !== event.target.defaultValue || ValidityState(event.target).valid) {
            /* on blur, update the report when the value has changed from the
             * default or when the element is valid (possibly removing a still
             * standing invalidity report). */
            reportValidity(event.target);
          } else if (event.type === 'keyup' && event.keyCode !== 9 || event.type === 'change') {
            if (ValidityState(event.target).valid) {
              // report instantly, when an element becomes valid,
              // postpone report to blur event, when an element is invalid
              reportValidity(event.target);
            }
          }
        } else if (event.type !== 'keyup' || event.keyCode !== 9) {
          /* do _not_ validate, when the user "tabbed" into the field initially,
           * i.e., a keyup event with keyCode 9 */
          reportValidity(event.target);
        }
      }
    };
  }
  /**
   * run a function on all found elements
   */


  function execute_on_elements(fn, elements) {
    if (elements instanceof window.Element) {
      elements = [elements];
    }

    var elements_length = elements.length;

    for (var i = 0; i < elements_length; i++) {
      fn(elements[i]);
    }
  }
  /**
   * get a function, that removes hyperform behavior again
   */


  function get_destructor(hform) {
    var form = hform.form;
    return function () {
      uncatch_submit(form);
      form.removeEventListener('keyup', hform.revalidator);
      form.removeEventListener('change', hform.revalidator);
      form.removeEventListener('blur', hform.revalidator, true);

      if (form === window || form.nodeType === 9) {
        hform.uninstall(element_prototypes);
        polyunfill(window.HTMLFormElement);
      } else if (form instanceof window.HTMLFormElement || form instanceof window.HTMLFieldSetElement) {
        hform.uninstall(form.elements);

        if (form instanceof window.HTMLFormElement) {
          polyunfill(form);
        }
      } else if (form instanceof window.HTMLElement) {
        hform.observer.disconnect();
        var _iteratorNormalCompletion = true;
        var _didIteratorError = false;
        var _iteratorError = undefined;

        try {
          for (var _iterator = Array.prototype.slice.call(form.getElementsByTagName('form'))[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
            var subform = _step.value;
            hform.uninstall(subform.elements);
            polyunfill(subform);
          }
        } catch (err) {
          _didIteratorError = true;
          _iteratorError = err;
        } finally {
          try {
            if (!_iteratorNormalCompletion && _iterator["return"] != null) {
              _iterator["return"]();
            }
          } finally {
            if (_didIteratorError) {
              throw _iteratorError;
            }
          }
        }
      }
    };
  }
  /**
   * add hyperform behavior to a freshly initialized wrapper
   */


  function add_behavior(hform) {
    var form = hform.form;
    var settings = hform.settings;
    hform.revalidator = get_revalidator(settings.revalidate);
    hform.observer = {
      disconnect: function disconnect() {}
    };

    hform.install = function (elements) {
      return execute_on_elements(polyfill, elements);
    };

    hform.uninstall = function (elements) {
      return execute_on_elements(polyunfill, elements);
    };

    hform._destruct = get_destructor(hform);
    catch_submit(form, settings.revalidate === 'never');

    if (form === window || form.nodeType === 9) {
      /* install on the prototypes, when called for the whole document */
      hform.install(element_prototypes);
      polyfill(window.HTMLFormElement);
    } else if (form instanceof window.HTMLFormElement || form instanceof window.HTMLFieldSetElement) {
      hform.install(form.elements);

      if (form instanceof window.HTMLFormElement) {
        polyfill(form);
      }
    } else if (form instanceof window.HTMLElement) {
      var _iteratorNormalCompletion2 = true;
      var _didIteratorError2 = false;
      var _iteratorError2 = undefined;

      try {
        for (var _iterator2 = Array.prototype.slice.call(hform.form.getElementsByTagName('form'))[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
          var subform = _step2.value;
          hform.install(subform.elements);
          polyfill(subform);
        }
      } catch (err) {
        _didIteratorError2 = true;
        _iteratorError2 = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion2 && _iterator2["return"] != null) {
            _iterator2["return"]();
          }
        } finally {
          if (_didIteratorError2) {
            throw _iteratorError2;
          }
        }
      }

      hform.observer = new window.MutationObserver(function (mutationsList) {
        var _iteratorNormalCompletion3 = true;
        var _didIteratorError3 = false;
        var _iteratorError3 = undefined;

        try {
          for (var _iterator3 = mutationsList[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
            var mutation = _step3.value;

            if (mutation.type === 'childList') {
              var _iteratorNormalCompletion4 = true;
              var _didIteratorError4 = false;
              var _iteratorError4 = undefined;

              try {
                for (var _iterator4 = Array.prototype.slice.call(mutation.addedNodes)[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
                  var subform = _step4.value;

                  if (subform instanceof window.HTMLFormElement) {
                    hform.install(subform.elements);
                    polyfill(subform);
                  }
                }
              } catch (err) {
                _didIteratorError4 = true;
                _iteratorError4 = err;
              } finally {
                try {
                  if (!_iteratorNormalCompletion4 && _iterator4["return"] != null) {
                    _iterator4["return"]();
                  }
                } finally {
                  if (_didIteratorError4) {
                    throw _iteratorError4;
                  }
                }
              }

              var _iteratorNormalCompletion5 = true;
              var _didIteratorError5 = false;
              var _iteratorError5 = undefined;

              try {
                for (var _iterator5 = Array.prototype.slice.call(mutation.removedNodes)[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
                  var _subform = _step5.value;

                  if (_subform instanceof window.HTMLFormElement) {
                    hform.uninstall(_subform.elements);
                    polyunfill(_subform);
                  }
                }
              } catch (err) {
                _didIteratorError5 = true;
                _iteratorError5 = err;
              } finally {
                try {
                  if (!_iteratorNormalCompletion5 && _iterator5["return"] != null) {
                    _iterator5["return"]();
                  }
                } finally {
                  if (_didIteratorError5) {
                    throw _iteratorError5;
                  }
                }
              }
            }
          }
        } catch (err) {
          _didIteratorError3 = true;
          _iteratorError3 = err;
        } finally {
          try {
            if (!_iteratorNormalCompletion3 && _iterator3["return"] != null) {
              _iterator3["return"]();
            }
          } finally {
            if (_didIteratorError3) {
              throw _iteratorError3;
            }
          }
        }
      });
      hform.observer.observe(form, {
        subtree: true,
        childList: true
      });
    } else {
      throw new Error('Hyperform must be used with a node or window.');
    }

    if (settings.revalidate === 'oninput' || settings.revalidate === 'hybrid') {
      /* in a perfect world we'd just bind to "input", but support here is
       * abysmal: http://caniuse.com/#feat=input-event */
      form.addEventListener('keyup', hform.revalidator);
      form.addEventListener('change', hform.revalidator);
    }

    if (settings.revalidate === 'onblur' || settings.revalidate === 'hybrid') {
      /* useCapture=true, because `blur` doesn't bubble. See
       * https://developer.mozilla.org/en-US/docs/Web/Events/blur#Event_delegation
       * for a discussion */
      form.addEventListener('blur', hform.revalidator, true);
    }
  }

  var version = '0.12.0';

  /**
   * public hyperform interface:
   */

  function hyperform(form) {
    var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
        classes = _ref.classes,
        _ref$debug = _ref.debug,
        debug = _ref$debug === void 0 ? false : _ref$debug,
        extendFieldset = _ref.extendFieldset,
        novalidateOnElements = _ref.novalidateOnElements,
        _ref$preventImplicitS = _ref.preventImplicitSubmit,
        preventImplicitSubmit = _ref$preventImplicitS === void 0 ? false : _ref$preventImplicitS,
        revalidate = _ref.revalidate,
        _ref$strict = _ref.strict,
        strict = _ref$strict === void 0 ? false : _ref$strict,
        validEvent = _ref.validEvent,
        _ref$validateNameless = _ref.validateNameless,
        validateNameless = _ref$validateNameless === void 0 ? false : _ref$validateNameless;

    if (!classes) {
      classes = {};
    }

    if (extendFieldset === undefined) {
      extendFieldset = !strict;
    }

    if (novalidateOnElements === undefined) {
      novalidateOnElements = !strict;
    }

    if (preventImplicitSubmit === undefined) {
      preventImplicitSubmit = false;
    }

    if (revalidate === undefined) {
      /* other recognized values: 'oninput', 'onblur', 'onsubmit' and 'never' */
      revalidate = strict ? 'onsubmit' : 'hybrid';
    }

    if (validEvent === undefined) {
      validEvent = !strict;
    }

    var settings = {
      debug: debug,
      strict: strict,
      preventImplicitSubmit: preventImplicitSubmit,
      revalidate: revalidate,
      validEvent: validEvent,
      extendFieldset: extendFieldset,
      classes: classes,
      novalidateOnElements: novalidateOnElements,
      validateNameless: validateNameless
    };

    if (form instanceof window.NodeList || form instanceof window.HTMLCollection || form instanceof Array) {
      return Array.prototype.map.call(form, function (element) {
        return hyperform(element, settings);
      });
    }

    var wrapper = new Wrapper(form, settings);
    add_behavior(wrapper);
    return wrapper;
  }

  hyperform.version = version;
  hyperform.checkValidity = checkValidity;
  hyperform.reportValidity = reportValidity;
  hyperform.setCustomValidity = setCustomValidity;
  hyperform.stepDown = stepDown;
  hyperform.stepUp = stepUp;
  hyperform.validationMessage = validationMessage;
  hyperform.ValidityState = ValidityState;
  hyperform.valueAsDate = valueAsDate;
  hyperform.valueAsNumber = valueAsNumber;
  hyperform.willValidate = willValidate;

  hyperform.setLanguage = function (lang) {
    set_language(lang);
    return hyperform;
  };

  hyperform.addTranslation = function (lang, catalog) {
    add_translation(lang, catalog);
    return hyperform;
  };

  hyperform.setRenderer = function (renderer, action) {
    Renderer.set(renderer, action);
    return hyperform;
  };

  hyperform.addValidator = function (element, validator) {
    custom_validator_registry.set(element, validator);
    return hyperform;
  };

  hyperform.setMessage = function (element, validator, message) {
    custom_messages.set(element, validator, message);
    return hyperform;
  };

  hyperform.addHook = function (hook, action, position) {
    add_hook(hook, action, position);
    return hyperform;
  };

  hyperform.removeHook = function (hook, action) {
    remove_hook(hook, action);
    return hyperform;
  };

  if (document.currentScript && document.currentScript.hasAttribute('data-hf-autoload')) {
    hyperform(window);
  }

  return hyperform;

}());
