/**
 * Materialize Stepper - A little plugin that implements a stepper to Materializecss framework.
 * @version v3.1.0
 * @author Igor Marcossi (Kinark) <igormarcossi@gmail.com>.
 * @link https://github.com/Kinark/Materialize-stepper
 * 
 * Licensed under the MIT License (https://github.com/Kinark/Materialize-stepper/blob/master/LICENSE).
 */

"use strict";

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }

function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }

function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }

/** Class representing an MStepper */
var MStepper =
/*#__PURE__*/
function () {
  /**
   * Constructor for Materialize Stepper.
   * @param {HTMLElement} elem - Element in which stepper will be initialized.
   * @param {object} [options] - Stepper options.
   * @param {number} [options.firstActive=0] - Default active step.
   * @param {boolean} [options.autoFocusInput=false] - Auto focus on first input of each step.
   * @param {boolean} [options.showFeedbackPreloader=true] - Set if a loading screen will appear while feedbacks functions are running.
   * @param {boolean} [options.autoFormCreation=true] - Auto generation of a form around the stepper.
   * @param {function} [options.validationFunction=null] - Function to be called everytime a nextstep occurs. It receives 2 arguments, in this sequece: stepperForm, activeStep.
   * @param {string} [options.feedbackPreloader] - Preloader used when step is waiting for feedback function. If not defined, Materializecss spinner-blue-only will be used.
   */
  function MStepper(elem) {
    var _this = this;

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

    _classCallCheck(this, MStepper);

    _defineProperty(this, "_init", function () {
      var _formWrapperManager = _this._formWrapperManager,
          getSteps = _this.getSteps,
          options = _this.options,
          _methodsBindingManager = _this._methodsBindingManager,
          _openAction = _this._openAction;

      var _getSteps = getSteps(),
          steps = _getSteps.steps; // Calls the _formWrapperManager


      _this.form = _formWrapperManager(); // Opens the first step (or other specified in the constructor)

      _openAction(steps[options.firstActive], undefined, undefined, true); // Gathers the steps and send them to the methodsBinder


      _methodsBindingManager(steps);
    });

    _defineProperty(this, "_methodsBindingManager", function (steps) {
      var unbind = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
      var classes = _this.classes,
          _formSubmitHandler = _this._formSubmitHandler,
          _nextStepProxy = _this._nextStepProxy,
          _prevStepProxy = _this._prevStepProxy,
          _stepTitleClickHandler = _this._stepTitleClickHandler,
          form = _this.form,
          options = _this.options;
      var addMultipleEventListeners = MStepper.addMultipleEventListeners,
          removeMultipleEventListeners = MStepper.removeMultipleEventListeners,
          nodesIterator = MStepper.nodesIterator,
          tabbingDisabler = MStepper.tabbingDisabler;
      var bindOrUnbind = unbind ? removeMultipleEventListeners : addMultipleEventListeners; // Sets the binding function

      var bindEvents = function bindEvents(step) {
        var nextBtns = step.getElementsByClassName(classes.NEXTSTEPBTN);
        var prevBtns = step.getElementsByClassName(classes.PREVSTEPBTN);
        var stepsTitle = step.getElementsByClassName(classes.STEPTITLE);
        var inputs = step.querySelectorAll('input, select, textarea, button');
        var submitButtons = step.querySelectorAll('button[type="submit"]');
        bindOrUnbind(nextBtns, 'click', _nextStepProxy, false);
        bindOrUnbind(prevBtns, 'click', _prevStepProxy, false); // Adding suggested feature in #62

        if (options.stepTitleNavigation) bindOrUnbind(stepsTitle, 'click', _stepTitleClickHandler); // Prevents the tabbing issue (https://github.com/Kinark/Materialize-stepper/issues/49)

        if (inputs.length) bindOrUnbind(inputs[inputs.length - 1], 'keydown', tabbingDisabler); // Binds to the submit button an internal handler to manage validation

        if (submitButtons && form && options.validationFunction) bindOrUnbind(submitButtons, 'keydown', _formSubmitHandler);
        return step;
      }; // Calls the binder function in the right way (if it's a unique step or multiple ones)


      if (steps instanceof Element) bindEvents(steps);else nodesIterator(steps, function (step) {
        return bindEvents(step);
      });
    });

    _defineProperty(this, "_formSubmitHandler", function (e) {
      if (!_this._validationFunctionCaller()) e.preventDefault();
    });

    _defineProperty(this, "resetStepper", function () {
      if (_this.form) {
        _this.form.reset();

        _this.openStep(_this.options.firstActive);
      }
    });

    _defineProperty(this, "updateStepper", function () {
      var getSteps = _this.getSteps,
          _methodsBindingManager = _this._methodsBindingManager; // Gathers the current steps from the stepper

      var _getSteps2 = getSteps(),
          steps = _getSteps2.steps; // Removes any bound methods


      _methodsBindingManager(steps, true); // Send the steps again to the methodsBindingManager


      _methodsBindingManager(steps);
    });

    _defineProperty(this, "_openAction", function (step, cb) {
      var closeActiveStep = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
      var skipAutoFocus = arguments.length > 3 ? arguments[3] : undefined;
      var _slideDown = _this._slideDown,
          classes = _this.classes,
          getSteps = _this.getSteps,
          _closeAction = _this._closeAction,
          stepper = _this.stepper,
          events = _this.events,
          options = _this.options; // Gets the active step element

      var activeStep = getSteps().active.step; // If the active step is the same as the one that has been asked to be opened, returns the step

      if (activeStep && activeStep.isSameNode(step)) return step; // Gets the step content div inside the step

      var stepContent = step.getElementsByClassName(classes.STEPCONTENT)[0];
      step.classList.remove(classes.DONESTEP); // Checks if the step is currently horizontal or vertical

      if (window.innerWidth < 993 || !stepper.classList.contains(classes.HORIZONTALSTEPPER)) {
        // The stepper is running in vertical mode
        // Calls the slideDown private method if the stepper is vertical
        _slideDown(stepContent, classes.ACTIVESTEP, step, cb); // Beginning of autoFocusInput


        if (!skipAutoFocus) {
          _slideDown(stepContent, classes.ACTIVESTEP, step, function () {
            // Gets the inputs from the nextStep to focus on the first one (temporarily disabled)
            var nextStepInputs = stepContent.querySelector('input, select, textarea'); // Focus on the first input of the next step (temporarily disabled)

            if (options.autoFocusInput && nextStepInputs) nextStepInputs.focus();
            if (cb && typeof cb === 'function') cb();
          });
        } // Enf of autoFocusInput

      } else {
        // The stepper is running in horizontal mode
        // Adds the class 'active' from the step, since all the animation is made by the CSS
        step.classList.add(classes.ACTIVESTEP);
      } // If it was requested to close the active step as well, does it (default=true)


      if (activeStep && closeActiveStep) {
        _closeAction(activeStep); // We are changing steps, so dispatch the change event.


        stepper.dispatchEvent(events.STEPCHANGE);
      } // Dispatch OPEN Event


      stepper.dispatchEvent(events.STEPOPEN);
      return step;
    });

    _defineProperty(this, "_closeAction", function (step, cb) {
      var _slideUp = _this._slideUp,
          classes = _this.classes,
          stepper = _this.stepper,
          events = _this.events,
          _smartListenerUnbind = _this._smartListenerUnbind,
          _smartListenerBind = _this._smartListenerBind; // Gets the step content div inside the step

      var stepContent = step.getElementsByClassName(classes.STEPCONTENT)[0]; // Checks if the step is currently horizontal or vertical

      if (window.innerWidth < 993 || !stepper.classList.contains(classes.HORIZONTALSTEPPER)) {
        // The stepper is running in vertical mode
        // Calls the slideUp private method on the step
        _slideUp(stepContent, classes.ACTIVESTEP, step, cb);
      } else {
        // The stepper is running in horizontal mode
        // If there's a callback, handles it
        if (cb) {
          // Defines a function to be called after the transition's end
          var waitForTransitionToCb = function waitForTransitionToCb(e) {
            // If the transition is not 'left', returns
            if (e.propertyName !== 'left') return; // Unbinds the listener from the element

            _smartListenerUnbind(stepContent, 'transitionend', waitForTransitionToCb); // Calls the callback


            cb();
          }; // Binds the callback caller function to the event 'transitionend'


          _smartListenerBind(stepContent, 'transitionend', waitForTransitionToCb);
        } // Removes the class 'active' from the step, since all the animation is made by the CSS


        step.classList.remove(classes.ACTIVESTEP);
      } // Dispatch Event


      stepper.dispatchEvent(events.STEPCLOSE);
      return step;
    });

    _defineProperty(this, "_nextStepProxy", function (e) {
      return _this.nextStep(undefined, undefined, e);
    });

    _defineProperty(this, "_prevStepProxy", function (e) {
      return _this.prevStep(undefined, e);
    });

    _defineProperty(this, "_stepTitleClickHandler", function (e) {
      var getSteps = _this.getSteps,
          classes = _this.classes,
          nextStep = _this.nextStep,
          prevStep = _this.prevStep,
          stepper = _this.stepper,
          _openAction = _this._openAction;

      var _getSteps3 = getSteps(),
          steps = _getSteps3.steps,
          active = _getSteps3.active;

      var clickedStep = e.target.closest(".".concat(classes.STEP)); // Checks if the stepper is linear or not

      if (stepper.classList.contains(classes.LINEAR)) {
        // Linear stepper detected
        // Get the index of the active step
        var clickedStepIndex = Array.prototype.indexOf.call(steps, clickedStep); // If the step clicked is the next one, calls nextStep(), if it's the previous one, calls prevStep(), otherwise do nothing

        if (clickedStepIndex == active.index + 1) nextStep();else if (clickedStepIndex == active.index - 1) prevStep();
      } else {
        // Non-linear stepper detected
        // Opens the step clicked
        _openAction(clickedStep);
      }
    });

    _defineProperty(this, "nextStep", function (cb, skipFeedback, e) {
      if (e && e.preventDefault) e.preventDefault();
      var options = _this.options,
          getSteps = _this.getSteps,
          activateFeedback = _this.activateFeedback,
          form = _this.form,
          wrongStep = _this.wrongStep,
          classes = _this.classes,
          _openAction = _this._openAction,
          stepper = _this.stepper,
          events = _this.events,
          destroyFeedback = _this.destroyFeedback,
          _validationFunctionCaller = _this._validationFunctionCaller;
      var showFeedbackPreloader = options.showFeedbackPreloader,
          validationFunction = options.validationFunction;

      var _getSteps4 = getSteps(),
          active = _getSteps4.active;

      var nextStep = getSteps().steps[active.index + 1]; // Gets the feedback function (if any) from the button

      var feedbackFunction = e && e.target ? e.target.dataset.feedback : null; // Checks if there's a validation function defined

      if (validationFunction && !_validationFunctionCaller()) {
        // There's a validation function and no feedback function
        // The validation function was already called in the if statement and it retuerned false, so returns the calling of the wrongStep method
        return wrongStep();
      } // Checks if there's a feedback function


      if (feedbackFunction && !skipFeedback) {
        // There's a feedback function and it wasn't requested to skip it
        // If showFeedbackPreloader is true (default=true), activates it
        if (showFeedbackPreloader && !active.step.dataset.nopreloader) activateFeedback(); // Calls the feedbackFunction

        window[feedbackFunction](destroyFeedback, form, active.step.querySelector(".".concat(classes.STEPCONTENT))); // Returns to prevent the nextStep method from being called

        return;
      } // Adds the class 'done' to the current step


      active.step.classList.add(classes.DONESTEP); // Opens the next one

      _openAction(nextStep, cb); // Dispatches the event


      stepper.dispatchEvent(events.NEXTSTEP);
    });

    _defineProperty(this, "prevStep", function (cb, e) {
      if (e && e.preventDefault) e.preventDefault();
      var getSteps = _this.getSteps,
          _openAction = _this._openAction,
          stepper = _this.stepper,
          events = _this.events,
          destroyFeedback = _this.destroyFeedback;
      var activeStep = getSteps().active;
      var prevStep = getSteps().steps[activeStep.index + -1]; // Destroyes the feedback preloader, if any

      destroyFeedback(); // Opens the previous step

      _openAction(prevStep, cb); // Dispatches the event


      stepper.dispatchEvent(events.PREVSTEP);
    });

    _defineProperty(this, "openStep", function (index, cb) {
      var getSteps = _this.getSteps,
          _openAction = _this._openAction,
          destroyFeedback = _this.destroyFeedback;
      var stepToOpen = getSteps().steps[index]; // Destroyes the feedback preloader, if any

      destroyFeedback(); // Opens the requested step

      _openAction(stepToOpen, cb);
    });

    _defineProperty(this, "wrongStep", function () {
      var getSteps = _this.getSteps,
          classes = _this.classes,
          stepper = _this.stepper,
          events = _this.events; // Add the WRONGSTEP class to the step

      getSteps().active.step.classList.add(classes.WRONGSTEP); // Gets all the inputs from the active step

      var inputs = getSteps().active.step.querySelectorAll('input, select, textarea'); // Defines a function to be binded to any change in any input

      var removeWrongOnInput = function removeWrongOnInput() {
        // If there's a change, removes the WRONGSTEP class
        getSteps().active.step.classList.remove(classes.WRONGSTEP); // Unbinds the listener from the element

        MStepper.removeMultipleEventListeners(inputs, 'input', removeWrongOnInput);
      }; // Binds the removeWrongOnInput function to the inputs, listening to the event 'input'


      MStepper.addMultipleEventListeners(inputs, 'input', removeWrongOnInput); // Dispatches the event

      stepper.dispatchEvent(events.STEPERROR);
    });

    _defineProperty(this, "activateFeedback", function () {
      var getSteps = _this.getSteps,
          classes = _this.classes,
          options = _this.options,
          stepper = _this.stepper,
          events = _this.events;
      var activeStep = getSteps().active.step; // Adds the FEEDBACKINGSTEP class to the step

      activeStep.classList.add(classes.FEEDBACKINGSTEP); // Gets the step content div inside the step

      var content = activeStep.getElementsByClassName(classes.STEPCONTENT)[0]; // Inserts the predefined prealoder in the step content div

      content.insertAdjacentHTML('afterBegin', "<div class=\"".concat(classes.PRELOADERWRAPPER, "\">").concat(options.feedbackPreloader, "</div>")); // Dispatches the event

      stepper.dispatchEvent(events.FEEDBACKING);
    });

    _defineProperty(this, "destroyFeedback", function (triggerNextStep) {
      var getSteps = _this.getSteps,
          classes = _this.classes,
          nextStep = _this.nextStep,
          stepper = _this.stepper,
          events = _this.events;
      var activeStep = getSteps().active.step; // If there's no activeStep or preloader, returns

      if (!activeStep || !activeStep.classList.contains(classes.FEEDBACKINGSTEP)) return; // Removes the FEEDBACKINGSTEP class from the step

      activeStep.classList.remove(classes.FEEDBACKINGSTEP); // Gets the preloader div

      var fbDiv = activeStep.getElementsByClassName(classes.PRELOADERWRAPPER)[0]; // Removes the preloader div

      fbDiv.parentNode.removeChild(fbDiv); // Calls nextStep if requested (default=false)

      if (triggerNextStep) nextStep(undefined, true); // Dispatches the event

      stepper.dispatchEvent(events.FEEDBACKDESTROYED);
    });

    _defineProperty(this, "getSteps", function () {
      var stepper = _this.stepper,
          classes = _this.classes;
      var steps = stepper.children;
      var activeStep = stepper.querySelector("li.".concat(classes.STEP, ".").concat(classes.ACTIVESTEP));
      var activeStepIndex = Array.prototype.indexOf.call(steps, activeStep);
      return {
        steps: steps,
        active: {
          step: activeStep,
          index: activeStepIndex
        }
      };
    });

    _defineProperty(this, "activateStep", function (elements, index) {
      var getSteps = _this.getSteps,
          _slideDown = _this._slideDown,
          stepper = _this.stepper,
          _methodsBindingManager = _this._methodsBindingManager;
      var nodesIterator = MStepper.nodesIterator;
      var currentSteps = getSteps().steps; // Checks if the steps will be added at the end or in the middle of the stepper

      var before = currentSteps.length > index; // Based on the previous check, sets the reference step

      var referenceStep = before ? currentSteps[index] : currentSteps[currentSteps.length - 1]; // Stores a let variable to return the right element after the activation

      var returnableElement = null; // Starts the checking of the elements parameter

      if (typeof elements === 'string') {
        // The element is in string format
        // Insert it with the insertAdjacentHTML function (and trim the string to avoid errors)
        referenceStep.insertAdjacentHTML(before ? 'beforeBegin' : 'afterEnd', elements.trim()); // Defines the inserted element as the returnableElement

        returnableElement = before ? referenceStep.previousSibling : referenceStep.nextSibling; // Activates (slideDown) the step

        _slideDown(returnableElement);
      } else if (Array.isArray(elements)) {
        // The element is in array format, probably an array of strings
        // Sets the returnableElement to be an empty array
        returnableElement = []; // Loops through the array

        elements.forEach(function (element) {
          // Inserts each element with the insertAdjacentHTML function (and trim the string to avoid errors)
          referenceStep.insertAdjacentHTML(before ? 'beforeBegin' : 'afterEnd', element.trim()); // Gets the new added element

          var addedStep = before ? referenceStep.previousSibling : referenceStep.nextSibling; // Adds each element to the returnableElement array

          returnableElement.push(addedStep); // Activates (slideDown) each element

          _slideDown(addedStep);
        });
      } else if (elements instanceof Element || elements instanceof HTMLCollection || elements instanceof NodeList) {
        // The element is an HTMLElement or an HTMLCollection
        // Sets the rigth function to add the new steps
        var rigthFunction = before ? stepper.insertBefore : stepper.appendChild; // Insert it/them with the rigthFunction and sets the returnableElement

        returnableElement = rigthFunction(elements, referenceStep); // If it's and HTMLElement, activates (slideDown) it, if it's an HTMLCollection, activates (slideDown) each of them

        if (elements instanceof Element) _slideDown(returnableElement);else nodesIterator(returnableElement, function (appendedElement) {
          return _slideDown(appendedElement);
        });
      } // Do the bidings to the new step(s)


      if (returnableElement) _methodsBindingManager(returnableElement); // Returns the added/activated elements

      return returnableElement;
    });

    _defineProperty(this, "deactivateStep", function (elements) {
      var _slideUp = _this._slideUp,
          stepper = _this.stepper,
          _methodsBindingManager = _this._methodsBindingManager;
      var nodesIterator = MStepper.nodesIterator; // Sets a function to group the orders to deactivate and remove the steps

      var doIt = function doIt(element) {
        // Checks if the step really exists in the stepper
        if (stepper.contains(elements)) {
          // Yeah, it does exist
          // Unbinds the listeners previously binded to the step
          _methodsBindingManager(element, true); // Slides up and removes afterwards


          _slideUp(element, undefined, undefined, function () {
            return stepper.removeChild(element);
          });
        }
      }; // Checks if the elements is an HTMLElement or an HTMLCollection and calls the function doIt in the right way


      if (elements instanceof Element) doIt(elements);else if (elements instanceof HTMLCollection || elements instanceof NodeList) nodesIterator(elements, function (element) {
        return doIt(element);
      }); // Returns the step(s), in case you want to activate it/them again.

      return elements;
    });

    _defineProperty(this, "_slideDown", function (element, className) {
      var classElement = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : element;
      var cb = arguments.length > 3 ? arguments[3] : undefined;
      // Gets the height of the element when it's already visible
      var height = "".concat(MStepper.getUnknownHeight(element), "px"); // Defines a function to be called after the transition's end

      var endSlideDown = function endSlideDown(e) {
        // If the transition is not 'height', returns
        if (e.propertyName !== 'height') return; // Unbinds the listener from the element

        _this._smartListenerUnbind(element, 'transitionend', endSlideDown); // Removes properties needed for the transition to occur


        MStepper.removeMultipleProperties(element, 'visibility overflow height display'); // Calls the callback() if any

        if (cb) cb();
      }; // Calls an animation frame to avoid async weird stuff


      requestAnimationFrame(function () {
        element.style.display = 'none';
        requestAnimationFrame(function () {
          // Prepare the element for animation
          element.style.overflow = 'hidden';
          element.style.height = '0';
          element.style.paddingBottom = '0';
          element.style.visibility = 'unset';
          element.style.display = 'block'; // Calls another animation frame to wait for the previous changes to take effect

          requestAnimationFrame(function () {
            // Binds the "conclusion" function to the event 'transitionend'
            _this._smartListenerBind(element, 'transitionend', endSlideDown); // Sets the final height to the element to trigger the transition


            element.style.height = height; // Removes the 'padding-bottom: 0' setted previously to trigger it too

            element.style.removeProperty('padding-bottom'); // element.style.paddingBottom = '0';
            // If a className for the slided element is required, add it

            if (className) classElement.classList.add(className);
          });
        });
      }); // Returns the original element to enable chain functions

      return element;
    });

    _defineProperty(this, "_slideUp", function (element, className) {
      var classElement = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : element;
      var cb = arguments.length > 3 ? arguments[3] : undefined;
      // Gets the element's height
      var height = "".concat(element.offsetHeight, "px"); // Defines a function to be called after the transition's end

      var endSlideUp = function endSlideUp(e) {
        // If the transition is not 'height', returns
        if (e.propertyName !== 'height') return; // Unbinds the listener from the element

        _this._smartListenerUnbind(element, 'transitionend', endSlideUp); // Sets display none for the slided element


        element.style.display = 'none'; // Removes properties needed for the transition to occur

        MStepper.removeMultipleProperties(element, 'visibility overflow height padding-bottom'); // Calls the callback() if any

        if (cb) cb();
      }; // Calls an animation frame to avoid async weird stuff


      requestAnimationFrame(function () {
        // Prepare the element for animation
        element.style.overflow = 'hidden';
        element.style.visibility = 'unset';
        element.style.display = 'block';
        element.style.height = height; // Calls another animation frame to wait for the previous changes to take effect

        requestAnimationFrame(function () {
          // Binds the "conclusion" function to the event 'transitionend'
          _this._smartListenerBind(element, 'transitionend', endSlideUp); // Sets the height to 0 the element to trigger the transition


          element.style.height = '0'; // Sets the 'padding-bottom: 0' to transition the padding

          element.style.paddingBottom = '0'; // If a removal of a className for the slided element is required, remove it

          if (className) classElement.classList.remove(className);
        });
      }); // Returns the original element to enable chain functions

      return element;
    });

    _defineProperty(this, "_formWrapperManager", function () {
      var stepper = _this.stepper,
          options = _this.options; // Checks if there's a form wrapping the stepper and gets it

      var form = stepper.closest('form'); // Checks if the form doesn't exist and the autoFormCreation option is true (default=true)

      if (!form && options.autoFormCreation) {
        // The form doesn't exist and the autoFormCreation is true
        // Gathers the form settings from the dataset of the stepper
        var dataAttrs = stepper.dataset || {};
        var method = dataAttrs.method || 'GET';
        var action = dataAttrs.action || '?'; // Creates a form element

        var wrapper = document.createElement('form'); // Defines the form's settings

        wrapper.method = method;
        wrapper.action = action; // Wraps the stepper with it

        stepper.parentNode.insertBefore(wrapper, stepper);
        wrapper.appendChild(stepper); // Returns the wrapper (the form)

        return wrapper;
      } else if (form && form.length) {
        // The form exists
        // Returns the form
        return form;
      } else {
        // The form doesn't exist and autoFormCreation is false
        // Returns null
        return null;
      }
    });

    _defineProperty(this, "_validationFunctionCaller", function () {
      var options = _this.options,
          getSteps = _this.getSteps,
          form = _this.form,
          classes = _this.classes;
      return options.validationFunction(form, getSteps().active.step.querySelector(".".concat(classes.STEPCONTENT)));
    });

    _defineProperty(this, "_smartListenerBind", function (el, event, fn) {
      var similar = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : true;
      var callFn = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
      var listenerStore = _this.listenerStore; // Builds an object with the element, event and function.

      var newListener = {
        el: el,
        event: event,
        fn: fn
      }; // Checks if similar listeners will be unbinded before the binding

      if (similar) {
        // Loops through the store searching for functions binded to the same element listening for the same event
        for (var i = 0; i < listenerStore.length; i++) {
          var listener = listenerStore[i]; // Unbind if found

          if (listener.event === event && listener.el.isSameNode(el)) listener.el.removeEventListener(listener.event, listener.fn); // Calls the binded function if requested

          if (callFn) listener.fn();
        }
      } else {
        // If similar listeners won't be unbinded, unbind duplicates
        var existentOneIndex = listenerStore.indexOf(newListener);

        if (existentOne !== -1) {
          var existentOne = listenerStore[existentOneIndex];
          existentOne.el.removeEventListener(existentOne.event, existentOne.fn);
          if (callFn) existentOne[existentOneIndex].fn();
        }
      } // Finally, binds the listener


      el.addEventListener(event, fn);
      listenerStore.push(newListener);
    });

    _defineProperty(this, "_smartListenerUnbind", function (el, event, fn) {
      var listenerStore = _this.listenerStore; // Gets the index of the listener in the stepper listenerStore

      var existentOneIndex = listenerStore.indexOf({
        el: el,
        event: event,
        fn: fn
      }); // Remove the even listener from the element

      el.removeEventListener(event, fn); // Remove the listener reference in the listenerStore

      listenerStore.splice(existentOneIndex, 1);
    });

    this.stepper = elem;
    this.options = Object.assign({
      firstActive: 0,
      autoFocusInput: true,
      showFeedbackPreloader: true,
      autoFormCreation: true,
      validationFunction: MStepper.defaultValidationFunction,
      stepTitleNavigation: true,
      feedbackPreloader: '<div class="preloader-wrapper active"> <div class="spinner-layer spinner-blue-only"> <div class="circle-clipper left"> <div class="circle"></div></div><div class="gap-patch"> <div class="circle"></div></div><div class="circle-clipper right"> <div class="circle"></div></div></div></div>'
    }, _options);
    this.classes = {
      HORIZONTALSTEPPER: 'horizontal',
      LINEAR: 'linear',
      NEXTSTEPBTN: 'next-step',
      PREVSTEPBTN: 'previous-step',
      STEPTITLE: 'step-title',
      STEP: 'step',
      STEPCONTENT: 'step-content',
      PRELOADERWRAPPER: 'wait-feedback',
      FEEDBACKINGSTEP: 'feedbacking',
      ACTIVESTEP: 'active',
      WRONGSTEP: 'wrong',
      DONESTEP: 'done'
    };
    this.events = {
      STEPCHANGE: new Event('stepchange'),
      STEPOPEN: new Event('stepopen'),
      STEPCLOSE: new Event('stepclose'),
      NEXTSTEP: new Event('nextstep'),
      PREVSTEP: new Event('prevstep'),
      STEPERROR: new Event('steperror'),
      FEEDBACKING: new Event('feedbacking'),
      FEEDBACKDESTROYED: new Event('feedbackdestroyed')
    }; // Creates an empty array to power the methods _smartListenerBind/Unbind

    this.listenerStore = []; // Creates an empty variable to store the form (or not) afterwards

    this.form = null; // Calls the initialization method

    this._init();
  }
  /**
   * Init private method. Will be called on the creating of a new instance of MStepper.
   * @returns {void}
   */


  _createClass(MStepper, null, [{
    key: "addMultipleEventListeners",

    /**
     * Util function to simplify the binding of functions to nodelists.
     * @param {(HTMLCollection|NodeList|HTMLElement)} elements - Elements to bind a listener to.
     * @param {string} event - Event name, like 'click'.
     * @param {function} fn - Function to bind to elements.
     * @returns {void}
     */
    value: function addMultipleEventListeners(elements, event, fn) {
      var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
      if (elements instanceof Element) return elements.addEventListener(event, fn, passive);

      for (var i = 0, len = elements.length; i < len; i++) {
        elements[i].addEventListener(event, fn, passive);
      }
    }
    /**
     * Util function to simplify the unbinding of functions to nodelists.
     * @param {(HTMLCollection|NodeList|HTMLElement)} elements - Elements from which the listeners will be unbind.
     * @param {string} event - Event name, like 'click'.
     * @param {function} fn - Function to unbind from elements.
     * @returns {void}
     */

  }, {
    key: "removeMultipleEventListeners",
    value: function removeMultipleEventListeners(elements, event, fn) {
      var passive = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
      if (elements instanceof Element) return elements.removeEventListener(event, fn, passive);

      for (var i = 0, len = elements.length; i < len; i++) {
        elements[i].removeEventListener(event, fn, passive);
      }
    }
    /**
     * An util function to simplify the removal of multiple properties.
     * @param {HTMLElement} el - Element target from which the properties will me removed.
     * @param {string} properties - Properties to be removed, separated by spaces, like 'height margin padding-top'.
     */

  }, {
    key: "removeMultipleProperties",
    value: function removeMultipleProperties(el, properties) {
      var propArray = properties.split(' ');

      for (var i = 0; i < propArray.length; i++) {
        el.style.removeProperty(propArray[i]);
      }
    }
    /**
     * Util function to itarate through HTMLCollections and NodeList using the same command.
     * @param {(HTMLCollection | NodeList)} nodes - List of elements to loop through.
     * @param {function} fn - Function to call for each element inside the nodes list.
     * @returns {(HTMLCollection | NodeList)} - The original nodes to enable chain functions
     */

  }, {
    key: "nodesIterator",
    value: function nodesIterator(nodes, fn) {
      for (var i = 0; i < nodes.length; i++) {
        fn(nodes[i]);
      }

      return nodes;
    }
    /**
     * Util function to find the height of a hidden DOM object.
     * @param {HTMLElement} el - Hidden HTML element (node).
     * @returns {number} - The height without "px".
     */

  }, {
    key: "getUnknownHeight",
    value: function getUnknownHeight(el) {
      // Spawns the hidden element in stealth mode
      el.style.position = 'fixed';
      el.style.display = 'block';
      el.style.top = '-999999px';
      el.style.left = '-999999px';
      el.style.height = 'auto';
      el.style.opacity = '0';
      el.style.zIndex = '-999999';
      el.style.pointerEvents = 'none'; // Gets it's height

      var height = el.offsetHeight; // Removes the stealth mode and hides the element again

      MStepper.removeMultipleProperties(el, 'position display top left height opacity z-index pointer-events');
      return height;
    }
    /**
     * Default validation function.
     * @returns {boolean}
     */

  }, {
    key: "defaultValidationFunction",
    value: function defaultValidationFunction(stepperForm, activeStepContent) {
      var inputs = activeStepContent.querySelectorAll('input, textarea, select');

      for (var i = 0; i < inputs.length; i++) {
        if (!inputs[i].checkValidity()) return false;
      }

      return true;
    }
    /**
     * Util bindable tabbing disabler.
     * @returns {void}
     */

  }, {
    key: "tabbingDisabler",
    value: function tabbingDisabler(e) {
      if (e.keyCode === 9) e.preventDefault();
    }
  }]);

  return MStepper;
}();

if (window.Element && !Element.prototype.closest) {
  Element.prototype.closest = function (s) {
    var matches = (this.document || this.ownerDocument).querySelectorAll(s),
        i,
        el = this;

    do {
      i = matches.length;

      while (--i >= 0 && matches.item(i) !== el) {}

      ;
    } while (i < 0 && (el = el.parentElement));

    return el;
  };
} // Polyfill by mozilla
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign


if (typeof Object.assign != 'function') {
  // Must be writable: true, enumerable: false, configurable: true
  Object.defineProperty(Object, "assign", {
    value: function assign(target, varArgs) {
      // .length of function is 2
      'use strict';

      if (target == null) {
        // TypeError if undefined or null
        throw new TypeError('Cannot convert undefined or null to object');
      }

      var to = Object(target);

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

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

      return to;
    },
    writable: true,
    configurable: true
  });
}